diff --git a/DESCRIPTION b/DESCRIPTION index 49fd2764..4846ba2b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: rayshader Type: Package Title: Create Maps and Visualize Data in 2D and 3D -Version: 0.36.1 +Version: 0.37.0 Date: 2023-08-01 Author: Tyler Morgan-Wall Maintainer: Tyler Morgan-Wall @@ -43,7 +43,9 @@ Suggests: lidR, elevatr, gridExtra, - testthat (>= 3.0.0) + testthat (>= 3.0.0), + osmdata, + raybevel LinkingTo: Rcpp, progress, RcppArmadillo RoxygenNote: 7.2.3 URL: https://www.rayshader.com, @@ -52,5 +54,6 @@ BugReports: https://github.com/tylermorganwall/rayshader/issues Remotes: tylermorganwall/rayimage, tylermorganwall/rayrender, tylermorganwall/rayvertex, + tylermorganwall/raybevel dmurdoch/rgl Config/testthat/edition: 3 diff --git a/NAMESPACE b/NAMESPACE index 1af90e55..34366a66 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -32,6 +32,8 @@ export(plot_map) export(raster_to_matrix) export(ray_shade) export(reduce_matrix_size) +export(render_beveled_polygons) +export(render_buildings) export(render_camera) export(render_clouds) export(render_compass) @@ -46,6 +48,7 @@ export(render_obj) export(render_path) export(render_points) export(render_polygons) +export(render_raymesh) export(render_resize_window) export(render_scalebar) export(render_snapshot) diff --git a/R/add_overlay.R b/R/add_overlay.R index 6f142694..617b2fb1 100644 --- a/R/add_overlay.R +++ b/R/add_overlay.R @@ -33,7 +33,7 @@ #' add_shadow(ray_shade(montereybay,zscale=50)) %>% #' plot_map() #'} -add_overlay = function(hillshade, overlay, alphalayer = 1, alphacolor=NULL, +add_overlay = function(hillshade = NULL, overlay = NULL, alphalayer = 1, alphacolor=NULL, alphamethod = "max", rescale_original = FALSE) { if(any(alphalayer > 1 || alphalayer < 0)) { stop("Argument `alphalayer` must not be less than 0 or more than 1") @@ -76,6 +76,12 @@ add_overlay = function(hillshade, overlay, alphalayer = 1, alphacolor=NULL, temp_over[alphalayer1] = 0 overlay[,,4] = temp_over } + if(is.null(hillshade)) { + return(overlay) + } + if(is.null(overlay)) { + return(hillshade) + } if(length(dim(hillshade)) == 2) { rayimage::add_image_overlay(fliplr(t(hillshade)),overlay,alpha=alphalayer, rescale_original = rescale_original) } else { diff --git a/R/colorspace_functions.R b/R/colorspace_functions.R index b61ed986..ae39f51a 100644 --- a/R/colorspace_functions.R +++ b/R/colorspace_functions.R @@ -7,14 +7,14 @@ #'@keywords internal #'@examples #'#None -darken_color = function(col, darken = 0.5) { +darken_color = function(col, darken = 0.3) { as.numeric( grDevices::convertColor( as.numeric( grDevices::convertColor( convert_color(col), - from = "sRGB", to = "Lab") - ) * c(darken,1,1), - from = "Lab", to = "sRGB") - ) + from = "sRGB", to = "Luv") + ) * c(darken,1,1), + from = "Luv", to = "sRGB") + ) } diff --git a/R/constant_shade.R b/R/constant_shade.R index 641f7aed..361a292f 100644 --- a/R/constant_shade.R +++ b/R/constant_shade.R @@ -41,7 +41,7 @@ #' plot_map() #'} constant_shade = function(heightmap, color = "white", alpha = 1) { - return_array = array(alpha, dim = c(nrow(heightmap),ncol(heightmap),4)) + return_array = array(alpha, dim = c(ncol(heightmap),nrow(heightmap),4)) const_col = convert_color(color) return_array[,,1] = const_col[1] return_array[,,2] = const_col[2] diff --git a/R/convert_rgl_to_raymesh.R b/R/convert_rgl_to_raymesh.R index 9b4b03e7..df187cc8 100644 --- a/R/convert_rgl_to_raymesh.R +++ b/R/convert_rgl_to_raymesh.R @@ -24,7 +24,7 @@ convert_rgl_to_raymesh = function(save_shadow = TRUE) { vertex_info = get_ids_with_labels() basic_load_mesh = function(row, texture_loc, - color = "white", alpha=1, obj = FALSE, specular = "white", + color = "white", alpha=1, obj = FALSE, specular = "white",shininess = 50, lit = FALSE, quads = FALSE) { id = as.character(vertex_info$id[row]) @@ -69,6 +69,19 @@ convert_rgl_to_raymesh = function(save_shadow = TRUE) { tex_indices = NULL } } + #Get type + if(is.na(lit)) { + lit = FALSE + } + if(lit) { + if(shininess < 10) { + type_val = "diffuse" + } else { + type_val = "phong" + } + } else { + type_val = "color" + } texture_loc = ifelse(!is.na(texture_loc), texture_loc, "") return(rayvertex::construct_mesh(indices = indices, @@ -79,13 +92,14 @@ convert_rgl_to_raymesh = function(save_shadow = TRUE) { norm_indices = norm_indices, material = rayvertex::material_list(texture_location = texture_loc, diffuse = color, - type = ifelse(lit, "diffuse", "color"), + type = type_val, + shininess = shininess, dissolve = alpha, specular = specular))) } for(row in 1:nrow(vertex_info)) { if(!is.na(vertex_info$lit[row])) { - lit_val = vertex_info$lit[row] + lit_val = unlist(vertex_info$lit[row]) } else { lit_val = FALSE } @@ -126,45 +140,16 @@ convert_rgl_to_raymesh = function(save_shadow = TRUE) { material = rayvertex::material_list(texture_location = texture_loc, type = "color")) } else if(vertex_info$tag[row] == "surface_tris") { - final_scene[[num_elems]] = basic_load_mesh(row, texture_loc = vertex_info$texture_file[[row]]) + final_scene[[num_elems]] = basic_load_mesh(row, + texture_loc = vertex_info$texture_file[[row]]) } else if (vertex_info$tag[row] == "basebottom") { - indices = matrix(rgl::rgl.attrib(vertex_info$id[row], "indices"), - ncol = 3L, byrow = TRUE) - 1 - vertices = rgl.attrib(vertex_info$id[row], "vertices") - textures = rgl.attrib(vertex_info$id[row], "texcoords") - dims = rgl::rgl.attrib(vertex_info$id[row], "dim") - vertices_y = vertices[,2] - nx = dims[1] - nz = dims[2] - indices = rep(0, 6 * (nz - 1) * (nx - 1)) - counter = 0 - na_counter = 0 - - for(i in seq_len(nz)[-nz]) { - for(j in seq_len(nx)[-nx]) { - if(!is.na(vertices_y[(i-1)*nx + j]) && !is.na(vertices_y[(i+1-1)*nx + j]) && - !is.na(vertices_y[(i-1)*nx + j+1]) && !is.na(vertices_y[(i+1-1)*nx + j+1])) { - cindices = (i-1)*nx + c(j, j + nx, j + 1, j + nx, j + nx + 1, j + 1) - indices[(1:6 + 6*counter)] = cindices - counter = counter + 1 - } else { - na_counter = na_counter + 2 - } - } - } - indices = indices - 1 - vertices_y[is.na(vertices_y)] = mean(vertices_y) - indices = matrix(indices, ncol=3, byrow=TRUE) - indices = indices[1:(nrow(indices)-na_counter),] texture_loc = vertex_info$texture_file[[row]] texture_loc = ifelse(!is.na(texture_loc), texture_loc, "") - final_scene[[num_elems]] = rayvertex::construct_mesh(indices = indices, - vertices = vertices, - texcoords = textures, - tex_indices = indices, - material = rayvertex::material_list(texture_location = texture_loc, - type = "color")) + final_scene[[num_elems]] = basic_load_mesh(row, + texture_loc = texture_loc, + lit = lit_val, + color = vertex_info$base_color[[row]]) } else if (vertex_info$tag[row] == "base") { final_scene[[num_elems]] = basic_load_mesh(row, lit = lit_val, @@ -225,6 +210,7 @@ convert_rgl_to_raymesh = function(save_shadow = TRUE) { } else if (vertex_info$tag[row] == "polygon3d") { final_scene[[num_elems]] = basic_load_mesh(row, texture_loc = NA, lit = lit_val, + shininess = vertex_info$shininess[[row]], color = vertex_info$tricolor[[row]]) } else if(vertex_info$tag[row] == "shadow" && save_shadow) { final_scene[[num_elems]] = basic_load_mesh(row, @@ -245,12 +231,14 @@ convert_rgl_to_raymesh = function(save_shadow = TRUE) { obj_ambient = vertex_info$obj_ambient[[row]] obj_specular = vertex_info$obj_specular[[row]] obj_emission = vertex_info$obj_emission[[row]] + shininess = vertex_info$shininess[[row]] tmp_obj = rayvertex::change_material(tmp_obj, diffuse = ifelse(is.na(obj_color), NULL, obj_color) , dissolve = ifelse(is.na(obj_alpha), NULL, obj_alpha), ambient = ifelse(is.na(obj_ambient), NULL, obj_ambient), specular = ifelse(is.na(obj_specular),NULL, obj_specular), - emission = ifelse(is.na(obj_emission),NULL, obj_emission)) + emission = ifelse(is.na(obj_emission),NULL, obj_emission), + shininess = ifelse(is.na(shininess), 50, shininess)) final_scene[[num_elems]] = tmp_obj } diff --git a/R/generate_compass_overlay.R b/R/generate_compass_overlay.R index 5a5c92ca..d9148fde 100644 --- a/R/generate_compass_overlay.R +++ b/R/generate_compass_overlay.R @@ -20,6 +20,10 @@ #'RGB image array automatically. #'@param width Default `NA`. Width of the resulting image array. Default the same dimensions as height map. #'@param height Default `NA`. Width of the resulting image array. Default the same dimensions as height map. +#'@param resolution_multiply Default `1`. If passing in `heightmap` instead of width/height, amount to +#'increase the resolution of the overlay, which should make lines/polygons finer. +#'Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +#'in the final map. #'@param halo_color Default `NA`, no halo. If a color is specified, the compass will be surrounded by a halo #'of this color. #'@param halo_expand Default `1`. Number of pixels to expand the halo. @@ -129,7 +133,7 @@ #'} generate_compass_overlay = function(x=0.85, y=0.15, size=0.075, text_size=1, bearing=0, - heightmap = NULL, width=NA, height=NA, + heightmap = NULL, width=NA, height=NA, resolution_multiply = 1, color1 = "white", color2 = "black", text_color = "black", border_color = "black", border_width = 1, halo_color = NA, halo_expand = 1, @@ -138,11 +142,14 @@ generate_compass_overlay = function(x=0.85, y=0.15, loc[1] = x loc[2] = y if(is.na(height)) { - height = ncol(heightmap) + height = ncol(heightmap) } if(is.na(width)) { width = nrow(heightmap) } + height = height * resolution_multiply + width = width * resolution_multiply + # default colors are white and black cols <- rep(c(color1,color2),8) bearing = bearing*pi/180 diff --git a/R/generate_contour_overlay.R b/R/generate_contour_overlay.R index 3a82bdc5..85b2b51e 100644 --- a/R/generate_contour_overlay.R +++ b/R/generate_contour_overlay.R @@ -10,6 +10,10 @@ #'this will automatically generate `nlevels` breaks between `levels[1]` and `levels[2]`. #'@param width Default `NA`. Width of the resulting overlay. Default the same dimensions as heightmap. #'@param height Default `NA`. Width of the resulting overlay. Default the same dimensions as heightmap. +#'@param resolution_multiply Default `1`. If passing in `heightmap` instead of width/height, amount to +#'increase the resolution of the overlay, which should make lines/polygons finer. +#'Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +#'in the final map. #'@param color Default `black`. Color. #'@param linewidth Default `1`. Line width. #'@return Semi-transparent overlay with contours. @@ -73,7 +77,7 @@ #' plot_map() #'} generate_contour_overlay = function(heightmap, levels=NA, nlevels=NA, - zscale = 1, width=NA, height=NA, + zscale = 1, width=NA, height=NA, resolution_multiply = 1, color = "black", linewidth = 1) { if(!(length(find.package("sf", quiet = TRUE)) > 0)) { stop("`sf` package required for generate_contour_overlay()") @@ -100,12 +104,15 @@ generate_contour_overlay = function(heightmap, levels=NA, nlevels=NA, levels=levels) contours = isoband::iso_to_sfg(isolineval) sf_contours = sf::st_sf(level = names(contours), geometry = sf::st_sfc(contours)) - if(is.na(width)) { - width = ncol(heightmap) - } if(is.na(height)) { - height = nrow(heightmap) + height = ncol(heightmap) + } + if(is.na(width)) { + width = nrow(heightmap) } + height = height * resolution_multiply + width = width * resolution_multiply + tempoverlay = tempfile(fileext = ".png") grDevices::png(filename = tempoverlay, width = width, height = height, units="px",bg = "transparent") graphics::par(mar = c(0,0,0,0)) diff --git a/R/generate_label_overlay.R b/R/generate_label_overlay.R index c15c56ce..dfd1e0b7 100644 --- a/R/generate_label_overlay.R +++ b/R/generate_label_overlay.R @@ -14,6 +14,10 @@ #'overlay automatically. #'@param width Default `NA`. Width of the resulting overlay. Default the same dimensions as height map. #'@param height Default `NA`. Width of the resulting overlay. Default the same dimensions as height map. +#'@param resolution_multiply Default `1`. If passing in `heightmap` instead of width/height, amount to +#'increase the resolution of the overlay, which should make lines/polygons/text finer. +#'Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +#'in the final map. #'@param text_size Default `1`. Text size. #'@param point_size Default `0`, no points. Point size. #'@param color Default `black`. Color of the labels. @@ -125,7 +129,8 @@ #' plot_map() #'} generate_label_overlay = function(labels, extent, x=NULL, y=NULL, - heightmap = NULL, width=NA, height=NA, text_size = 1, + heightmap = NULL, width=NA, height=NA, resolution_multiply = 1, + text_size = 1, color = "black", font = 1, pch = 16, point_size = 1, point_color = NA, offset = c(0,0), data_label_column = NULL, @@ -172,11 +177,14 @@ generate_label_overlay = function(labels, extent, x=NULL, y=NULL, extent = get_extent(extent) if(is.na(height)) { - height = ncol(heightmap) + height = ncol(heightmap) } if(is.na(width)) { width = nrow(heightmap) } + height = height * resolution_multiply + width = width * resolution_multiply + tempoverlay = tempfile(fileext = ".png") grDevices::png(filename = tempoverlay, width = width, height = height, units="px",bg = "transparent") graphics::par(mar = c(0,0,0,0)) diff --git a/R/generate_line_overlay.R b/R/generate_line_overlay.R index f77241a9..122c33bf 100644 --- a/R/generate_line_overlay.R +++ b/R/generate_line_overlay.R @@ -11,6 +11,10 @@ #'overlay automatically. #'@param width Default `NA`. Width of the resulting overlay. Default the same dimensions as height map. #'@param height Default `NA`. Width of the resulting overlay. Default the same dimensions as height map. +#'@param resolution_multiply Default `1`. If passing in `heightmap` instead of width/height, amount to +#'increase the resolution of the overlay, which should make lines/polygons/text finer. +#'Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +#'in the final map. #'@param color Default `black`. Color of the lines. #'@param linewidth Default `1`. Line width. #'@param lty Default `1`. Line type. `1` is solid, `2` is dashed, `3` is dotted,`4` is dot-dash, @@ -56,7 +60,7 @@ #' plot_map() #'} generate_line_overlay = function(geometry, extent, heightmap = NULL, - width=NA, height=NA, + width=NA, height=NA, resolution_multiply = 1, color = "black", linewidth = 1, lty = 1, data_column_width = NULL, offset = c(0,0)) { if(!(length(find.package("sf", quiet = TRUE)) > 0)) { @@ -74,14 +78,18 @@ generate_line_overlay = function(geometry, extent, heightmap = NULL, if(inherits(geometry,"sfg")) { geometry = sf::st_sfc(geometry) } - sf_lines_cropped = base::suppressMessages(base::suppressWarnings(sf::st_crop(geometry, extent))) + # sf_lines_cropped = base::suppressMessages(base::suppressWarnings(sf::st_crop(geometry, extent))) + sf_lines_cropped = geometry if(is.na(height)) { - height = ncol(heightmap) + height = ncol(heightmap) } if(is.na(width)) { width = nrow(heightmap) } + height = height * resolution_multiply + width = width * resolution_multiply + if(!is.null(data_column_width)) { if(data_column_width %in% colnames(sf_lines_cropped)) { widthvals = sf_lines_cropped[[data_column_width]] / max(sf_lines_cropped[[data_column_width]],na.rm = TRUE) * linewidth diff --git a/R/generate_point_overlay.R b/R/generate_point_overlay.R index 95995946..4beda2c4 100644 --- a/R/generate_point_overlay.R +++ b/R/generate_point_overlay.R @@ -11,6 +11,10 @@ #'overlay automatically. #'@param width Default `NA`. Width of the resulting overlay. Default the same dimensions as height map. #'@param height Default `NA`. Width of the resulting overlay. Default the same dimensions as height map. +#'@param resolution_multiply Default `1`. If passing in `heightmap` instead of width/height, amount to +#'increase the resolution of the overlay, which should make lines/polygons/points finer. +#'Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +#'in the final map. #'@param color Default `black`. Color of the points. #'@param size Default `1`. Point size. #'@param pch Default `20`, solid. Point symbol. @@ -39,7 +43,8 @@ #' plot_map() #'} generate_point_overlay = function(geometry, extent, heightmap = NULL, - width=NA, height=NA, pch = 20, + width=NA, height=NA, resolution_multiply = 1, + pch = 20, color = "black", size = 1, offset = c(0,0), data_column_width = NULL) { if(!(length(find.package("sf", quiet = TRUE)) > 0)) { stop("{sf} package required for generate_line_overlay()") @@ -59,11 +64,14 @@ generate_point_overlay = function(geometry, extent, heightmap = NULL, sf_point_cropped = base::suppressMessages(base::suppressWarnings(sf::st_crop(geometry, extent))) if(is.na(height)) { - height = ncol(heightmap) + height = ncol(heightmap) } if(is.na(width)) { width = nrow(heightmap) } + height = height * resolution_multiply + width = width * resolution_multiply + if(!is.null(data_column_width)) { if(data_column_width %in% colnames(sf_point_cropped)) { widthvals = sf_point_cropped[[data_column_width]] / max(sf_point_cropped[[data_column_width]],na.rm = TRUE) * size diff --git a/R/generate_polygon_overlay.R b/R/generate_polygon_overlay.R index 33486ba4..87ba8826 100644 --- a/R/generate_polygon_overlay.R +++ b/R/generate_polygon_overlay.R @@ -11,6 +11,10 @@ #'overlay automatically. #'@param width Default `NA`. Width of the resulting overlay. Default the same dimensions as height map. #'@param height Default `NA`. Width of the resulting overlay. Default the same dimensions as height map. +#'@param resolution_multiply Default `1`. If passing in `heightmap` instead of width/height, amount to +#'increase the resolution of the overlay, which should make lines/polygons/text finer. +#'Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +#'in the final map. #'@param linecolor Default `black`. Color of the lines. #'@param palette Default `black`. Single color, named vector color palette, or palette function. #'If this is a named vector and `data_column_fill` is not `NULL`, @@ -67,7 +71,8 @@ #' plot_map() #'} generate_polygon_overlay = function(geometry, extent, heightmap = NULL, - width=NA, height=NA, offset = c(0,0), data_column_fill = NULL, + width=NA, height=NA, resolution_multiply = 1, + offset = c(0,0), data_column_fill = NULL, linecolor = "black", palette = "white", linewidth = 1) { if(!(length(find.package("sf", quiet = TRUE)) > 0)) { stop("{sf} package required for generate_line_overlay()") @@ -81,14 +86,20 @@ generate_polygon_overlay = function(geometry, extent, heightmap = NULL, if(!inherits(geometry,"sf")) { stop("geometry must be {sf} object") } + if(is.numeric(extent)) { + extent = raster::extent(extent) + } sf_polygons_cropped = base::suppressMessages(base::suppressWarnings(sf::st_crop(geometry, extent))) if(is.na(height)) { - height = ncol(heightmap) + height = ncol(heightmap) } if(is.na(width)) { width = nrow(heightmap) } + height = height * resolution_multiply + width = width * resolution_multiply + if (is.function(palette)) { palette = palette(nrow(sf_polygons_cropped)) } diff --git a/R/generate_scalebar_overlay.R b/R/generate_scalebar_overlay.R index f4f1c080..4d39771b 100644 --- a/R/generate_scalebar_overlay.R +++ b/R/generate_scalebar_overlay.R @@ -32,6 +32,10 @@ #'RGB image array automatically. #'@param width Default `NA`. Width of the resulting image array. Default the same dimensions as height map. #'@param height Default `NA`. Width of the resulting image array. Default the same dimensions as height map. +#'@param resolution_multiply Default `1`. If passing in `heightmap` instead of width/height, amount to +#'increase the resolution of the overlay, which should make lines/polygons/text finer. +#'Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +#'in the final map. #'@param color1 Default `black`. Primary color of the scale bar. #'@param color2 Default `white`. Secondary color of the scale bar. #'@param text_color Default `black`. Text color. @@ -166,7 +170,7 @@ generate_scalebar_overlay = function(extent, length, x=0.05, y=0.05, bearing=90, unit="m", flip_ticks = FALSE, labels = NA, text_size=1, decimals = 0, text_offset = 1, adj = 0.5, - heightmap = NULL, width=NA, height=NA, + heightmap = NULL, width=NA, height=NA, resolution_multiply = 1, color1 = "white", color2 = "black", text_color = "black", font = 1, border_color = "black", tick_color = "black", @@ -185,11 +189,14 @@ generate_scalebar_overlay = function(extent, length, x=0.05, y=0.05, halo_offset[2] = halo_offset[2] * ydiff if(is.na(height)) { - height = ncol(heightmap) + height = ncol(heightmap) } if(is.na(width)) { width = nrow(heightmap) } + height = height * resolution_multiply + width = width * resolution_multiply + if(all(!is.na(labels)) && length(labels) != 3) { stop("If specified, `labels` must be length-3 vector") } diff --git a/R/generate_surface.R b/R/generate_surface.R index b391f8d4..e4ec12e2 100644 --- a/R/generate_surface.R +++ b/R/generate_surface.R @@ -9,12 +9,12 @@ generate_surface = function(heightmap, zscale) { nr = nrow(heightmap) nc = ncol(heightmap) - vertex_matrix_row = matrix(seq_len(nr), + vertex_matrix_row = matrix(seq_len(nr)-1, nrow = nr, - ncol = nc) - nr/2 - vertex_matrix_col = matrix(seq_len(nc), + ncol = nc) - (nr-1)/2 + vertex_matrix_col = matrix(seq_len(nc)-1, nrow = nr, - ncol = nc, byrow = TRUE) - nc/2 + ncol = nc, byrow = TRUE) - (nc-1)/2 row_vert = c(vertex_matrix_row) col_vert = c(vertex_matrix_col) verts = matrix(c(row_vert,c(heightmap)/zscale,col_vert), ncol = 3L) diff --git a/R/generate_waterline_overlay.R b/R/generate_waterline_overlay.R index 737319d4..9bba3216 100644 --- a/R/generate_waterline_overlay.R +++ b/R/generate_waterline_overlay.R @@ -8,6 +8,12 @@ #' #'@param heightmap A two-dimensional matrix, where each entry in the matrix is the elevation at that point. #'If `boolean = TRUE`, this will instead be interpreted as a logical matrix indicating areas of water. +#'@param width Default `NA`. Width of the resulting image array. Default the same dimensions as height map. +#'@param height Default `NA`. Width of the resulting image array. Default the same dimensions as height map. +#'@param resolution_multiply Default `1`. If passing in `heightmap` instead of width/height, amount to +#'increase the resolution of the overlay, which should make lines/polygons/text finer. +#'Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +#'in the final map. #'@param color Default `white`. Color of the lines. #'@param linewidth Default `1`. Line width. #'@param boolean Default `FALSE`. If `TRUE`, this is a boolean matrix (0 and 1) indicating contiguous areas in @@ -107,6 +113,7 @@ generate_waterline_overlay = function(heightmap, color = "white", linewidth=1, b fade = TRUE, alpha_dist = max, alpha = 1, falloff = 1.3, evenly_spaced = FALSE, zscale = 1, cutoff = 0.999, + width = NA, height = NA, resolution_multiply = 1, min_area=length(heightmap)/400, max_height = NULL, return_distance_matrix = FALSE) { breaks = breaks + 1 @@ -149,6 +156,8 @@ generate_waterline_overlay = function(heightmap, color = "white", linewidth=1, b } levels = scales::rescale(levels, to = c(min, max)) overlay = generate_contour_overlay(water_dist_bool, levels=levels, + width=width, height=height, + resolution_multiply = resolution_multiply, color = color, linewidth=linewidth) if(fade) { alpha_vals = water_dist_bool diff --git a/R/get_ids_with_labels.R b/R/get_ids_with_labels.R index 160a20ae..32bb8230 100644 --- a/R/get_ids_with_labels.R +++ b/R/get_ids_with_labels.R @@ -46,6 +46,7 @@ get_ids_with_labels = function(typeval = NULL) { material_properties[[i]]$obj_ambient = NA_character_ material_properties[[i]]$obj_specular = NA_character_ material_properties[[i]]$obj_emission = NA_character_ + material_properties[[i]]$shininess = NA @@ -56,7 +57,7 @@ get_ids_with_labels = function(typeval = NULL) { if(material_type[i] %in% c("floating_overlay", "floating_overlay_tris")) { material_properties[[i]]$layer_texture_file = material_type_single$texture } - if(material_type[i] == "base") { + if(material_type[i] == "base" || material_type[i] == "basebottom") { material_properties[[i]]$base_color = material_type_single$color } if(material_type[i] == "water") { @@ -98,6 +99,7 @@ get_ids_with_labels = function(typeval = NULL) { material_properties[[i]]$tricolor = material_type_single$color material_properties[[i]]$polygon_alpha = material_type_single$alpha material_properties[[i]]$lit = material_type_single$lit + material_properties[[i]]$shininess = material_type_single$shininess } if(material_type[i] %in% c("base_soil1", "base_soil2")) { material_properties[[i]]$soil_texture = material_type_single$texture @@ -114,6 +116,7 @@ get_ids_with_labels = function(typeval = NULL) { material_properties[[i]]$obj_specular = material_type_single$specular material_properties[[i]]$obj_emission = material_type_single$emission material_properties[[i]]$lit = material_type_single$lit + material_properties[[i]]$shininess = material_type_single$shininess } } diff --git a/R/get_interpolated_points_path.R b/R/get_interpolated_points_path.R index b5b95f3b..ea9fbf66 100644 --- a/R/get_interpolated_points_path.R +++ b/R/get_interpolated_points_path.R @@ -6,8 +6,8 @@ #' #' @keywords internal get_interpolated_points_path = function(points, n = 360, use_altitude = FALSE) { - points_lead = points[-1,] - points_lag = points[-nrow(points),] + points_lead = points[-1,,drop=FALSE] + points_lag = points[-nrow(points),,drop=FALSE] if(use_altitude) { path_distance = c(0,cumsum(sqrt((points_lead[,1] - points_lag[,1])^2 + (points_lead[,2] - points_lag[,2])^2 + diff --git a/R/get_polygon_data_value.R b/R/get_polygon_data_value.R new file mode 100644 index 00000000..f7fa7f21 --- /dev/null +++ b/R/get_polygon_data_value.R @@ -0,0 +1,16 @@ +#'@title Get Data Value from spatial object +#' +#'@param polygon This is an sf object +#' +#'@keywords internal +get_polygon_data_value = function(polygon, data_column_name = NULL, default_value = 0, scale_data = 1) { + if(!is.null(data_column_name)) { + data_vals = polygon[[data_column_name]] + } else { + polygon$new_data_column = default_value + data_vals = polygon$new_data_column + } + data_vals = data_vals * scale_data + stopifnot(is.numeric(data_vals)) + return(data_vals) +} diff --git a/R/make_base.R b/R/make_base.R index 8d76de70..86a7c2bb 100644 --- a/R/make_base.R +++ b/R/make_base.R @@ -12,28 +12,30 @@ make_base = function(heightmap,basedepth=0,basecolor="grey20",zscale=1, soil = F soil_freq = 0.1, soil_levels = 8, soil_color1 = "black", soil_color2 = "black", soil_gradient = 0, gradient_darken = 1) { heightmap = heightmap/zscale - edge_vals = unique(c(heightmap[1,], heightmap[,1],heightmap[nrow(heightmap),],heightmap[,ncol(heightmap)])) + nc = ncol(heightmap) + nr = nrow(heightmap) + edge_vals = unique(c(heightmap[1,], heightmap[,1],heightmap[nr,],heightmap[,nc])) if(length(edge_vals) == 1 && all(!is.na(edge_vals))) { heightlist = list() - nc = ncol(heightmap) - nr = nrow(heightmap) - heightlist[[1]] = matrix(c(1, nr, nr, basedepth,basedepth,basedepth,-1, -nc,-1),3,3) - heightlist[[2]] = matrix(c(1, nr,1,basedepth,basedepth,basedepth, -nc, -nc,-1),3,3) - heightlist[[3]] = matrix(c(1, nr,1, basedepth,basedepth,edge_vals,-1,-1,-1),3,3) - heightlist[[4]] = matrix(c( nr, nr,1,basedepth,edge_vals,edge_vals,-1,-1,-1),3,3) - heightlist[[5]] = matrix(c(1, nr, 1, edge_vals,basedepth,basedepth, -nc, -nc, -nc),3,3) - heightlist[[6]] = matrix(c( 1, nr, nr,edge_vals,edge_vals,basedepth, -nc, -nc, -nc),3,3) - heightlist[[7]] = matrix(c(nr, nr, nr, basedepth,basedepth,edge_vals,-1,-nc,-1),3,3) - heightlist[[8]] = matrix(c(nr, nr, nr,basedepth,edge_vals,edge_vals,-nc,-nc,-1),3,3) - heightlist[[9]] = matrix( c(1,1,1, edge_vals,basedepth,basedepth, -1, -nc, -1),3,3) - heightlist[[10]] = matrix(c(1,1,1, edge_vals,edge_vals,basedepth, -1, -nc, -nc),3,3) + heightlist[[1]] = matrix(c(1, nr, nr, basedepth, basedepth, basedepth, 1, nc, 1),3,3) + heightlist[[2]] = matrix(c(1, nr, 1, basedepth, basedepth, basedepth, nc, nc, 1),3,3) + heightlist[[3]] = matrix(c(1, nr,1, basedepth,basedepth,edge_vals,1,1,1),3,3) + heightlist[[4]] = matrix(c( nr, nr,1,basedepth,edge_vals,edge_vals,1,1,1),3,3) + heightlist[[5]] = matrix(c(1, nr, 1, edge_vals,basedepth,basedepth, nc, nc, nc),3,3) + heightlist[[6]] = matrix(c( 1, nr, nr,edge_vals,edge_vals,basedepth, nc, nc, nc),3,3) + heightlist[[7]] = matrix(c(nr, nr, nr, basedepth,basedepth,edge_vals,1, nc,1),3,3) + heightlist[[8]] = matrix(c(nr, nr, nr,basedepth,edge_vals,edge_vals, nc, nc,1),3,3) + heightlist[[9]] = matrix( c(1,1,1, edge_vals,basedepth,basedepth, 1, nc, 1),3,3) + heightlist[[10]] = matrix(c(1,1,1, edge_vals,edge_vals,basedepth, 1, nc, nc),3,3) fullsides = do.call(rbind,heightlist) - fullsides[,1] = fullsides[,1] - nrow(heightmap)/2 - fullsides[,3] = -fullsides[,3] - ncol(heightmap)/2 - fullsides = fullsides[nrow(fullsides):1,] + fullsides = fullsides[rev(seq_len(nrow(fullsides))),] + fullsides[,1] = fullsides[,1] - 1 + fullsides[,3] = fullsides[,3] - 1 + fullsides[,1] = fullsides[,1] - (nr-1)/2 + fullsides[,3] = fullsides[,3] - (nc-1)/2 rgl::triangles3d(fullsides, texture = NULL, - lit=FALSE,color=basecolor,front="filled",back="culled",tag = "base") + lit=FALSE,color=basecolor,front="filled",back="cull",tag = "base") } else if(all(!is.na(heightmap))) { na_matrix = is.na(heightmap) baselist = make_base_cpp(heightmap, na_matrix, basedepth) @@ -41,8 +43,8 @@ make_base = function(heightmap,basedepth=0,basecolor="grey20",zscale=1, soil = F edge_heights = as.vector(t(cbind(baselist$edge_heights,baselist$edge_heights,baselist$edge_heights))) direction_vec = as.vector(t(cbind(baselist$is_horizontal,baselist$is_horizontal,baselist$is_horizontal))) - heightlist[[length(heightlist)+1]] = matrix(c(1,nrow(heightmap),nrow(heightmap), basedepth,basedepth,basedepth,-1,-ncol(heightmap),-1),3,3) - heightlist[[length(heightlist)+1]] = matrix(c(1,nrow(heightmap),1,basedepth,basedepth,basedepth,-ncol(heightmap),-ncol(heightmap),-1),3,3) + heightlist[[length(heightlist)+1]] = matrix(c(1,nr,nr, basedepth,basedepth,basedepth,-1,-nc,-1),3,3) + heightlist[[length(heightlist)+1]] = matrix(c(1,nr,1,basedepth,basedepth,basedepth,-nc,-nc,-1),3,3) direction_vec = c(direction_vec, rep(FALSE,6)) direction_vec = rev(direction_vec) @@ -50,9 +52,12 @@ make_base = function(heightmap,basedepth=0,basecolor="grey20",zscale=1, soil = F edge_heights = rev(edge_heights) fullsides = do.call(rbind,heightlist) - fullsides[,1] = fullsides[,1] - nrow(heightmap)/2 - fullsides[,3] = -fullsides[,3] - ncol(heightmap)/2 - fullsides = fullsides[nrow(fullsides):1,] + fullsides[,3] = -fullsides[,3] + fullsides[,1] = fullsides[,1] - 1 + fullsides[,3] = fullsides[,3] - 1 + fullsides[,1] = fullsides[,1] - (nr-1)/2 + fullsides[,3] = fullsides[,3] - (nc-1)/2 + fullsides = fullsides[rev(seq_len(nrow(fullsides))),] if(soil) { horizontal_sides = fullsides[!direction_vec,] @@ -68,7 +73,7 @@ make_base = function(heightmap,basedepth=0,basecolor="grey20",zscale=1, soil = F min_hside_y = min(horizontal_sides[,2],na.rm=TRUE) max_hside_y = max(horizontal_sides[,2],na.rm=TRUE) - horizontal_texcoords_x = (horizontal_sides[,1] + nrow(heightmap)/2-1)/(nrow(heightmap)-1) + horizontal_texcoords_x = (horizontal_sides[,1] + nr/2-1)/(nr-1) # horizontal_texcoords_y = (horizontal_sides[,2]-min_hside_y)/(max_hside_y-min_hside_y) horizontal_texcoords_y = rep(0,length(horizontal_sides[,2])) @@ -82,7 +87,7 @@ make_base = function(heightmap,basedepth=0,basecolor="grey20",zscale=1, soil = F min_vside_y = min(vertical_sides[,2],na.rm=TRUE) max_vside_y = max(vertical_sides[,2],na.rm=TRUE) - vertical_texcoords_x = (vertical_sides[,3] + ncol(heightmap)/2-1)/(ncol(heightmap)-1) + vertical_texcoords_x = (vertical_sides[,3] + nc/2-1)/(nc-1) # vertical_texcoords_y = (vertical_sides[,2]-min_vside_y)/(max_vside_y-min_vside_y) vertical_texcoords_y = rep(0,length(vertical_sides[,2])) # vertical_texcoords_y[vertical_sides[,2] == basedepth] = vertical_heights[vertical_sides[,2] == basedepth]/(max_vside_y-min_vside_y) @@ -109,12 +114,16 @@ make_base = function(heightmap,basedepth=0,basecolor="grey20",zscale=1, soil = F direction_vec = rev(as.vector(t(cbind(baselist$is_horizontal,baselist$is_horizontal,baselist$is_horizontal)))) fullsides = do.call(rbind,heightlist) - fullsides[,1] = fullsides[,1] - nrow(heightmap)/2 - fullsides[,3] = -fullsides[,3] - ncol(heightmap)/2 - fullsides = fullsides[nrow(fullsides):1,] - basemat = matrix(basedepth,nrow(heightmap),ncol(heightmap)) + fullsides[,3] = -fullsides[,3] + fullsides[,1] = fullsides[,1] - 1 + fullsides[,3] = fullsides[,3] - 1 + fullsides[,1] = fullsides[,1] - (nr-1)/2 + fullsides[,3] = fullsides[,3] - (nc-1)/2 + fullsides = fullsides[rev(seq_len(nrow(fullsides))),] + + basemat = matrix(basedepth,nr,nc) basemat[is.na(heightmap)] = NA - normalmat = matrix(0,nrow(heightmap),ncol(heightmap)) + normalmat = matrix(0,nr,nc) xznormals = fliplr(heightmap) ynormals = fliplr(heightmap) xznormals[!is.na(xznormals)] = 0 @@ -134,7 +143,7 @@ make_base = function(heightmap,basedepth=0,basecolor="grey20",zscale=1, soil = F min_hside_y = min(horizontal_sides[,2],na.rm=TRUE) max_hside_y = max(horizontal_sides[,2],na.rm=TRUE) - horizontal_texcoords_x = (horizontal_sides[,1] + nrow(heightmap)/2-1)/(nrow(heightmap)-1) + horizontal_texcoords_x = (horizontal_sides[,1] + nr/2-1)/(nr-1) # horizontal_texcoords_y = (horizontal_sides[,2]-min_hside_y)/(max_hside_y-min_hside_y) horizontal_texcoords_y = rep(0,length(horizontal_sides[,2])) @@ -147,7 +156,7 @@ make_base = function(heightmap,basedepth=0,basecolor="grey20",zscale=1, soil = F min_vside_y = min(vertical_sides[,2],na.rm=TRUE) max_vside_y = max(vertical_sides[,2],na.rm=TRUE) - vertical_texcoords_x = (vertical_sides[,3] + ncol(heightmap)/2-1)/(ncol(heightmap)-1) + vertical_texcoords_x = (vertical_sides[,3] + nc/2-1)/(nc-1) # vertical_texcoords_y = (vertical_sides[,2]-min_vside_y)/(max_vside_y-min_vside_y) vertical_texcoords_y = rep(0,length(vertical_sides[,2])) vertical_texcoords_y[vertical_sides[,2] == basedepth] = vertical_heights[vertical_sides[,2] == basedepth]/(max_vside_y-min_vside_y) @@ -164,14 +173,25 @@ make_base = function(heightmap,basedepth=0,basecolor="grey20",zscale=1, soil = F } else { rgl::triangles3d(fullsides, texture = NULL, - lit=FALSE,color=basecolor,front="filled",back="filled",tag = "base") + lit=FALSE,color=basecolor,front="filled",back="cull",tag = "base") } - rgl::surface3d(x=1:nrow(basemat)-nrow(basemat)/2, - z=1:ncol(basemat)-ncol(basemat)/2, - y=basemat, - color=basecolor, - lit=FALSE,back="filled",front="filled",tag = "basebottom", - normal_x = xznormals, normal_z = xznormals, normal_y = ynormals) + basemat = matrix(basedepth,nr,nc) + basemat[is.na(heightmap)] = NA + ray_base = generate_surface(basemat, zscale = 1) + ray_base$inds = ray_base$inds[3:1,] + + rgl::triangles3d(x = ray_base$verts, + indices = ray_base$inds, + texcoords = ray_base$texcoords, + color=basecolor,back="cull", front="filled", + lit=FALSE,texture=NULL,tag = "basebottom") + + # rgl::surface3d(x=1:nrow(basemat)-1-(nr-1)/2, + # z=1:ncol(basemat)-1-(nc-1)/2, + # y=basemat, + # color=basecolor, + # lit=FALSE,back="cull",front="filled",tag = "basebottom", + # normal_x = xznormals, normal_z = xznormals, normal_y = ynormals) } } diff --git a/R/make_lines.R b/R/make_lines.R index 21dbdda4..a2f8ba5f 100644 --- a/R/make_lines.R +++ b/R/make_lines.R @@ -13,48 +13,57 @@ #'@keywords internal make_lines = function(heightmap,basedepth=0,linecolor="grey20",zscale=1,alpha=1,linewidth = 2,solid=TRUE) { heightmap = heightmap/zscale - heightval3 = heightmap[1,1] - heightval4 = heightmap[nrow(heightmap),1] - heightval1 = heightmap[1,ncol(heightmap)] - heightval2 = heightmap[nrow(heightmap),ncol(heightmap)] + nr = nrow(heightmap) + nc = ncol(heightmap) + heightval3 = heightmap[1, 1 ] + heightval4 = heightmap[nr,1 ] + heightval1 = heightmap[1, nc] + heightval2 = heightmap[nr,nc] heightlist = list() if(all(!is.na(heightmap))) { if(solid) { - heightlist[[1]] = matrix(c(1,1,basedepth,heightval3,-1,-1),2,3) - heightlist[[2]] = matrix(c(nrow(heightmap),nrow(heightmap),basedepth,heightval4,-1,-1),2,3) - heightlist[[3]] = matrix(c(1,1,basedepth,heightval1,-ncol(heightmap),-ncol(heightmap)),2,3) - heightlist[[4]] = matrix(c(nrow(heightmap),nrow(heightmap),basedepth,heightval2,-ncol(heightmap),-ncol(heightmap)),2,3) - heightlist[[5]] = matrix(c(1,1,basedepth,basedepth,-1,-ncol(heightmap)),2,3) - heightlist[[6]] = matrix(c(1,nrow(heightmap),basedepth,basedepth,-ncol(heightmap),-ncol(heightmap)),2,3) - heightlist[[7]] = matrix(c(nrow(heightmap),nrow(heightmap),basedepth,basedepth,-ncol(heightmap),-1),2,3) - heightlist[[8]] = matrix(c(nrow(heightmap),1,basedepth,basedepth,-1,-1),2,3) + heightlist[[1]] = matrix(c(1,1,basedepth,heightval3,1,1),2,3) + heightlist[[2]] = matrix(c(nr,nr,basedepth,heightval4,1,1),2,3) + heightlist[[3]] = matrix(c(1,1,basedepth,heightval1,nc,nc),2,3) + heightlist[[4]] = matrix(c(nr,nr,basedepth,heightval2,nc,nc),2,3) + heightlist[[5]] = matrix(c(1,1,basedepth,basedepth,1,nc),2,3) + heightlist[[6]] = matrix(c(1,nr,basedepth,basedepth,nc,nc),2,3) + heightlist[[7]] = matrix(c(nr,nr,basedepth,basedepth,nc,1),2,3) + heightlist[[8]] = matrix(c(nr,1,basedepth,basedepth,1,1),2,3) } else { basedepth = basedepth/zscale counter = 1 if(basedepth > heightval1) { - heightlist[[counter]] = matrix(c(1,1,basedepth,heightval1,-1,-1),2,3) + heightlist[[counter]] = matrix(c(1,1,basedepth,heightval1,1,1),2,3) counter = counter + 1 } if(basedepth > heightval2) { - heightlist[[counter]] = matrix(c(nrow(heightmap),nrow(heightmap),basedepth,heightval2,-1,-1),2,3) + heightlist[[counter]] = matrix(c(nr,nr,basedepth,heightval2,1,1),2,3) counter = counter + 1 } if(basedepth > heightval3) { - heightlist[[counter]] = matrix(c(1,1,basedepth,heightval3,-ncol(heightmap),-ncol(heightmap)),2,3) + heightlist[[counter]] = matrix(c(1,1,basedepth,heightval3,nc,nc),2,3) counter = counter + 1 } if(basedepth > heightval4) { - heightlist[[counter]] = matrix(c(nrow(heightmap),nrow(heightmap),basedepth,heightval4,-ncol(heightmap),-ncol(heightmap)),2,3) + heightlist[[counter]] = matrix(c(nr,nr,basedepth,heightval4,nc,nc),2,3) counter = counter + 1 } } + reverse_z = FALSE } else { heightlist = make_baselines_cpp(heightmap,is.na(heightmap),basedepth) + reverse_z = TRUE } if(length(heightlist) > 0) { segmentlist = do.call(rbind,heightlist) - segmentlist[,1] = segmentlist[,1] - nrow(heightmap)/2 - segmentlist[,3] = -segmentlist[,3] - ncol(heightmap)/2 + if(reverse_z) { + segmentlist[,3] = -segmentlist[,3] + } + segmentlist[,1] = segmentlist[,1] - 1 + segmentlist[,3] = segmentlist[,3] - 1 + segmentlist[,1] = segmentlist[,1] - (nr-1)/2 + segmentlist[,3] = segmentlist[,3] - (nc-1)/2 rgl::segments3d(segmentlist,color=linecolor,lwd=linewidth,alpha=alpha,depth_mask=TRUE, line_antialias=FALSE, depth_test="lequal",tag = ifelse(solid,"lines","waterlines")) } diff --git a/R/make_water.R b/R/make_water.R index 40f64164..7c29b7de 100644 --- a/R/make_water.R +++ b/R/make_water.R @@ -12,6 +12,9 @@ make_water = function(heightmap,waterheight=mean(heightmap),watercolor="lightblue",zscale=1,wateralpha=0.5) { heightmap = heightmap/zscale na_matrix = is.na(heightmap) + nr = nrow(heightmap) + nc = ncol(heightmap) + waterheight = waterheight/zscale if(all(heightmap >= waterheight, na.rm=TRUE)) { warning("No water rendered--all elevations above or equal to water level. Range of heights: ", @@ -21,39 +24,45 @@ make_water = function(heightmap,waterheight=mean(heightmap),watercolor="lightblu heightlist = make_water_cpp(heightmap, na_matrix, waterheight) if(length(heightlist) > 0) { fullsides = do.call(rbind,heightlist) - fullsides[,1] = (fullsides[,1] - nrow(heightmap)/2) - fullsides[,3] = -(fullsides[,3] + ncol(heightmap)/2 - 1) - fullsides = fullsides[nrow(fullsides):1,] - } + fullsides[,3] = -fullsides[,3] + fullsides[,1] = fullsides[,1] - 1 + fullsides[,3] = fullsides[,3] + + fullsides[,1] = fullsides[,1] - (nr-1)/2 + fullsides[,3] = fullsides[,3] - (nc-1)/2 + } + nr1 = nr-1 + nc1 = nc-1 + if(all(!na_matrix)) { - vertices = rbind(matrix(c(-nrow(heightmap)/2+1, nrow(heightmap)/2, -nrow(heightmap)/2+1, + vertices = rbind(matrix(c(-nr1/2, nr1/2, -nr1/2, waterheight,waterheight,waterheight, - ncol(heightmap)/2,-ncol(heightmap)/2+1,-ncol(heightmap)/2+1), + nc1/2, -nc1/2, -nc1/2), nrow = 3L, ncol = 3L), - matrix(c(-nrow(heightmap)/2+1, nrow(heightmap)/2, nrow(heightmap)/2, + matrix(c(-nr1/2, nr1/2, nr1/2, waterheight,waterheight,waterheight, - ncol(heightmap)/2,ncol(heightmap)/2,-ncol(heightmap)/2+1), + nc1/2, nc1/2, -nc1/2), nrow = 3L, ncol = 3L)) - indices = 1L:6L + indices = seq_len(6L) rgl::triangles3d(x = vertices, indices = indices, color=watercolor,alpha=wateralpha, lit = FALSE, - front="filled",back="culled",texture=NULL,tag = "water") + front="filled",back="cull",texture=NULL,tag = "water") if(length(heightlist) > 0) { - indices = 1L:nrow(fullsides) + indices = rev(seq_len(nrow(fullsides))) rgl::triangles3d(fullsides, indices = indices, lit=FALSE,color=watercolor,alpha=wateralpha, - front="fill",back="culled",depth_test="less",texture=NULL,tag = "water") + front="filled",back="cull",depth_test="less",texture=NULL,tag = "water") } } else { if(length(heightlist) > 0) { - indices = 1L:nrow(fullsides) + indices = rev(seq_len(nrow(fullsides))) rgl::triangles3d(fullsides,indices = indices, lit=FALSE,color=watercolor,alpha=wateralpha,front="fill",back="culled", texture=NULL,tag = "water") } - basemat = matrix(waterheight,nrow(heightmap),ncol(heightmap)) + basemat = matrix(waterheight,nr,nc) basemat[is.na(heightmap)] = NA ray_surface = generate_surface(basemat, zscale = 1) diff --git a/R/make_waterlines.R b/R/make_waterlines.R index f3aa6750..d1a6cbf0 100644 --- a/R/make_waterlines.R +++ b/R/make_waterlines.R @@ -15,10 +15,16 @@ make_waterlines = function(heightmap,waterdepth=0,linecolor="grey40",zscale=1,al heightmap = heightmap/zscale na_matrix = is.na(heightmap) heightlist = make_waterlines_cpp(heightmap,na_matrix,waterdepth/zscale) + nr = nrow(heightmap) + nc = ncol(heightmap) if(length(heightlist) > 0) { segmentlist = do.call(rbind,heightlist) - segmentlist[,1] = segmentlist[,1] - nrow(heightmap)/2 - segmentlist[,3] = -(segmentlist[,3] + ncol(heightmap)/2) + segmentlist[,3] = -segmentlist[,3] + segmentlist[,1] = segmentlist[,1] - 1 + segmentlist[,3] = segmentlist[,3] - 1 + + segmentlist[,1] = segmentlist[,1] - (nr-1)/2 + segmentlist[,3] = segmentlist[,3] - (nc-1)/2 rgl::segments3d(segmentlist,color=linecolor,lwd=linewidth,alpha=alpha,depth_mask=TRUE, line_antialias=antialias, depth_test="lequal",tag = "waterlines", lit = FALSE) diff --git a/R/plot_3d.R b/R/plot_3d.R index bfa860bb..f786e338 100644 --- a/R/plot_3d.R +++ b/R/plot_3d.R @@ -16,7 +16,7 @@ #'@param shadow Default `TRUE`. If `FALSE`, no shadow is rendered. #'@param shadowdepth Default `auto`, which sets it to `soliddepth - soliddepth/10`. Depth of the shadow layer. #'@param shadowcolor Default `auto`. Color of the shadow, automatically computed as `shadow_darkness` -#'the luminance of the `background` color in the CIELab colorspace if not specified. +#'the luminance of the `background` color in the CIELuv colorspace if not specified. #'@param shadow_darkness Default `0.5`. Darkness of the shadow, if `shadowcolor = "auto"`. #'@param shadowwidth Default `auto`, which sizes it to 1/10th the smallest dimension of `heightmap`. Width of the shadow in units of the matrix. #'@param water Default `FALSE`. If `TRUE`, a water layer is rendered. @@ -70,7 +70,6 @@ #'@param close_previous Default `TRUE`. Closes any previously open `rgl` window. If `FALSE`, #'old windows will be kept open. #'@param clear_previous Default `TRUE`. Clears the previously open `rgl` window if `plot_new = FALSE`. -#'@param ... Additional arguments to pass to the `rgl::par3d` function. #' #'@import rgl #'@export @@ -150,8 +149,7 @@ plot_3d = function(hillshade, heightmap, zscale=1, baseshape="rectangle", theta=45, phi = 45, fov=0, zoom = 1, background="white", windowsize = 600, precomputed_normals = NULL, asp = 1, triangulate = FALSE, max_error = 0, max_tri = 0, verbose = FALSE, - plot_new = TRUE, close_previous = TRUE, clear_previous = TRUE, - ...) { + plot_new = TRUE, close_previous = TRUE, clear_previous = TRUE) { if(!plot_new && clear_previous) { rgl::clear3d() } @@ -159,7 +157,6 @@ plot_3d = function(hillshade, heightmap, zscale=1, baseshape="rectangle", assign("scene_cache", NULL, envir = ray_cache_scene_envir) } #setting default zscale if montereybay is used and tell user about zscale - argnameschar = unlist(lapply(as.list(sys.call()),as.character))[-1] argnames = as.list(sys.call()) if(!is.null(attr(heightmap,"rayshader_data"))) { if (!("zscale" %in% as.character(names(argnames)))) { @@ -317,6 +314,11 @@ plot_3d = function(hillshade, heightmap, zscale=1, baseshape="rectangle", normalsx = (t(normals$x[c(-1, -nrow(normals$x)), c(-1,-ncol(normals$x))])) normalsy = (t(normals$z[c(-1, -nrow(normals$z)), c(-1,-ncol(normals$z))])) normalsz = (t(normals$y[c(-1, -nrow(normals$y)), c(-1,-ncol(normals$y))])) + replace_na_vals = is.na(normalsx) | is.na(normalsy) | is.na(normalsz) + normalsx[replace_na_vals] = 0 + normalsy[replace_na_vals] = 1 + normalsz[replace_na_vals] = 0 + ray_surface = generate_surface(heightmap, zscale = zscale) rgl::triangles3d(x = ray_surface$verts, indices = ray_surface$inds, @@ -338,22 +340,22 @@ plot_3d = function(hillshade, heightmap, zscale=1, baseshape="rectangle", tris[,2] = tris[,2]/zscale nr = nrow(heightmap) nc = ncol(heightmap) - rn = tris[,1]+1 - cn = tris[,3]+1 + # rn = tris[,1]+1 + # cn = tris[,3]+1 + # normal_comp = matrix(c(normalsz[rn + nr*(cn-1)],normalsy[rn + nr*(cn-1)],-normalsx[rn + nr*(cn-1)]),ncol=3) texcoords = tris[,c(1,3)] - texcoords[,1] = texcoords[,1]/(nrow(heightmap)-1) - texcoords[,2] = texcoords[,2]/(ncol(heightmap)-1) - tris[,1] = tris[,1] - nrow(heightmap)/2 +1 - tris[,3] = tris[,3] - ncol(heightmap)/2 + texcoords[,1] = texcoords[,1]/(nr-1) + texcoords[,2] = texcoords[,2]/(nc-1) + tris[,1] = tris[,1] - (nr-1)/2# +1 + tris[,3] = tris[,3] - (nc-1)/2 tris[,3] = -tris[,3] rgl::triangles3d(tris, texcoords = texcoords, - indices = index_vals, + indices = index_vals, back = "cull", #normals = normal_comp, texture=tempmap,lit=FALSE,color="white",tag = "surface_tris") } rgl::bg3d(color = background,texture=NULL) - # rgl::par3d(windowRect = windowsize, mouseMode = c("none", "polar", "fov", "zoom", "pull"), ...) if(solid && !triangulate) { make_base(heightmap,basedepth=soliddepth,basecolor=solidcolor,zscale=zscale, soil = soil, soil_freq = soil_freq, soil_levels = soil_levels, soil_color1=soil_color_light, diff --git a/R/plot_gg.R b/R/plot_gg.R index b05015f5..f08286f3 100644 --- a/R/plot_gg.R +++ b/R/plot_gg.R @@ -106,7 +106,6 @@ #'} #' #'#Contours and other lines will automatically be ignored. Here is the volcano dataset: -#' #'ggvolcano = volcano %>% #' reshape2::melt() %>% #' ggplot() + @@ -115,7 +114,8 @@ #' scale_x_continuous("X",expand = c(0,0)) + #' scale_y_continuous("Y",expand = c(0,0)) + #' scale_fill_gradientn("Z",colours = terrain.colors(10)) + -#' coord_fixed() +#' coord_fixed() + +#' theme(legend.position = "none") #'ggvolcano #' #'if(run_documentation()) { @@ -686,7 +686,7 @@ plot_gg = function(ggobj, ggobj_height = NULL, width = 3, height = 3, } else { mapcolor %>% add_shadow(raylayer,shadow_intensity) %>% - plot_map(keep_user_par = FALSE) + plot_map() } } else { raylayer = saved_shadow_matrix @@ -699,7 +699,7 @@ plot_gg = function(ggobj, ggobj_height = NULL, width = 3, height = 3, } else { mapcolor %>% add_shadow(raylayer,shadow_intensity) %>% - plot_map(keep_user_par = FALSE) + plot_map() } } } else { @@ -708,7 +708,7 @@ plot_gg = function(ggobj, ggobj_height = NULL, width = 3, height = 3, max_error = max_error, max_tri = max_tri, verbose = verbose, shadow = shadow, shadowdepth=shadowdepth/scale, background = background, shadowcolor = shadowcolor, ...) } else { - plot_map(mapcolor, keep_user_par = FALSE) + plot_map(mapcolor) } } if(!preview && flat_plot_render) { diff --git a/R/plot_map.R b/R/plot_map.R index 9183fbaa..dfc76764 100644 --- a/R/plot_map.R +++ b/R/plot_map.R @@ -17,8 +17,6 @@ #'@param title_bar_color Default `NULL`. If a color, this will create a colored bar under the title. #'@param title_bar_alpha Default `0.5`. Transparency of the title bar. #'@param title_position Default `northwest`. Position of the title. -#'@param keep_user_par Default `TRUE`. Whether to keep the user's `par()` settings. Set to `FALSE` if you -#'want to set up a multi-pane plot (e.g. set `par(mfrow)`). #'@param ... Additional arguments to pass to the `raster::plotRGB` function that displays the map. #'@export #'@examples @@ -52,11 +50,7 @@ plot_map = function(hillshade, rotate=0, asp = 1, title_color = "black", title_size = 30, title_font = "sans", title_style = "normal", title_bar_color = NULL, title_bar_alpha = 0.5, title_position = "northwest", - keep_user_par = FALSE, ...) { - if(keep_user_par) { - old.par = graphics::par(no.readonly = TRUE) - on.exit(graphics::par(old.par)) - } + ...) { has_title = !is.na(title_text) if(!(length(find.package("rayimage", quiet = TRUE)) > 0) && has_title) { warning("`rayimage` package required for title text") diff --git a/R/render_beveled_polygons.R b/R/render_beveled_polygons.R new file mode 100644 index 00000000..1a637413 --- /dev/null +++ b/R/render_beveled_polygons.R @@ -0,0 +1,289 @@ +#'@title Render Beveled Polygons +#' +#'@description Adds beveled polygon to the scene using the `raybevel` package. See +#'the `raybevel::generate_beveled_polygon()` function for more information. +#' +#' @param polygon `sf` object, "SpatialPolygon" `sp` object, or xy coordinates +#' of polygon represented in a way that can be processed by `xy.coords()`. If +#' xy-coordinate based polygons are open, they will be closed by adding an +#' edge from the last point to the first. +#' @param extent Either an object representing the spatial extent of the 3D scene +#' (either from the `raster`, `terra`, `sf`, or `sp` packages), +#' a length-4 numeric vector specifying `c("xmin", "xmax", "ymin", "ymax")`, or the spatial object (from +#' the previously aforementioned packages) which will be automatically converted to an extent object. +#' @param bevel_width Default `5`. Width of the bevel. +#' @param width_raw_units Default `FALSE`. Whether the bevel width should be measured in raw display units, +#' or the actual units of the map. +#' @param bevel Default `NULL`. A list with `x`/`y` components that specify a bevel profile. See `raybevel::generate_bevel()` +#' @param angle Default `45`. Angle of the bevel. +#' @param material Default `"grey80"`. If a color string, this will specify the color of the sides/base of the polygon. +#' Alternatively (for more customization), this can be a r`ayvertex::material_list()` object to specify +#' the full color/appearance/material options for the resulting `ray_mesh` mesh. +#' @param bevel_material Default `NA`, defaults to the material specified in `material`. If a color string, this will specify the color of the polygon bevel. +#' Alternatively (for more customization), this can be a `rayvertex::material_list()` object to specify +#' the full color/appearance/material options for the resulting `ray_mesh` mesh. +#' @param bevel_height Default `1`. Height from the base of the polygon to the start of the beveled top. +#' @param base_height Default `0`. Height of the base of the polygon. +#' @param set_max_height Default `FALSE`. A logical flag that controls whether to set the max height of the roof based on the `max_height` argument. +#' @param max_height Default `1`. The maximum height of the polygon. +#' @param scale_all_max Default `FALSE`. If passing in a list of multiple skeletons with polygons, whether to scale each polygon to the overall +#' max height, or whether to scale each max height to the maximum internal distance in the polygon. +#' @param raw_offsets Default `FALSE`. A logical flag indicating whether the `bevel_offsets` are already +#' in raw format and do not need to be multiplied by the maximum time of the skeleton. +#' See the documentation for `raybevel::generate_beveled_polygon()` for more info. +#' @param raw_heights Default `FALSE`. A logical flag indicating whether the `bevel_heights` are already +#' in raw format and do not need to be multiplied by the maximum time of the skeleton. +#' See the documentation for `raybevel::generate_beveled_polygon()` for more info. +#' @param heights_relative_to_centroid Default `FALSE`. Whether the heights should be measured in absolute +#' terms, or relative to the centroid of the polygon. +#' @param data_column_top Default `NULL`. A string indicating the column in the `sf` object to use +#' to specify the top of the beveled polygon. +#' @param data_column_bottom Default `NULL`. A string indicating the column in the `sf` object to use +#' to specify the bottom of the beveled polygon. +#' @param scale_data Default `1`. If specifying `data_column_top` or `data_column_bottom`, how +#' much to scale that value when rendering. +#' @param holes Default `0`. If passing in a polygon directly, this specifies which index represents +#' the holes in the polygon. See the `earcut` function in the `decido` package for more information. +#' @param heightmap Default `NULL`. Automatically extracted from the rgl window--only use if auto-extraction +#' of matrix extent isn't working. A two-dimensional matrix, where each entry in the matrix is the elevation at that point. +#' All points are assumed to be evenly spaced. +#' @param zscale Default `1`. The ratio between the x and y spacing (which are assumed to be equal) and the z axis in the original heightmap. +#' @param alpha Default `1`. Transparency of the polygons. +#' @param lit Default `TRUE`. Whether to light the polygons. +#' @param light_altitude Default `c(45, 30)`. Degree(s) from the horizon from which to light the polygons. +#' @param light_direction Default `c(315, 225)`. Degree(s) from north from which to light the polygons. +#' @param light_intensity Default `1`. Intensity of the specular highlight on the polygons. +#' @param light_relative Default `FALSE`. Whether the light direction should be taken relative to the camera, +#' or absolute. +#' @param flat_shading Default `FALSE`. Set to `TRUE` to have nicer shading on the 3D polygons. This comes +#' with the slight penalty of increasing the memory use of the scene due to vertex duplication. This +#' will not affect software or high quality renders. +#' @param clear_previous Default `FALSE`. If `TRUE`, it will clear all existing polygons. +#' @param ... Additional arguments to pass to `rgl::triangles3d()`. +#' @export +#' @examples +#' +#' # This function can also create fake "terrain" from polygons by visualizing the distance +#' # to the nearest edge. +#' if(run_documentation()) { +#' #Render the county borders as polygons in Monterey Bay as terrain +#' montereybay %>% +#' sphere_shade(texture = "desert") %>% +#' add_shadow(ray_shade(montereybay,zscale = 50)) %>% +#' plot_3d(montereybay, water = TRUE, windowsize = 800, watercolor = "dodgerblue", +#' background = "pink") +#' +#' #We will apply a negative buffer to create space between adjacent polygons. You may +#' #have to call `sf::sf_use_s2(FALSE)` before running this code to get it to run. +#' sf::sf_use_s2(FALSE) +#' mont_county_buff = sf::st_simplify(sf::st_buffer(monterey_counties_sf,-0.003), dTolerance=0.001) +#' +#' render_beveled_polygons(mont_county_buff, flat_shading = TRUE, angle = 45 , +#' heightmap = montereybay, bevel_width=2000, +#' material = "red", +#' extent = attr(montereybay,"extent"), +#' bevel_height = 5000, base_height=0, +#' zscale=200) +#' render_camera(theta = 0, phi = 90, zoom = 0.65, fov = 0) +#' render_snapshot() +#' render_camera(theta=194, phi= 35, zoom = 0.5, fov= 80) +#' render_snapshot() +#' } +#' +#' # Changing the color of the beveled top: +#' if(run_documentation()) { +#' render_beveled_polygons(mont_county_buff, flat_shading = TRUE, angle = 45 , +#' heightmap = montereybay, bevel_width=2000, +#' material = "tan", bevel_material = "darkgreen", +#' extent = attr(montereybay,"extent"), clear_previous=TRUE, +#' bevel_height = 5000, base_height=0, +#' zscale=200) +#' } +#' # We can create a nice curved surface by passing in a bevel generated with the +#' # `raybevel::generate_bevel()` function. +#' if(run_documentation()) { +#' render_beveled_polygons(mont_county_buff, flat_shading = TRUE, heightmap = montereybay, +#' bevel = raybevel::generate_bevel("exp",bevel_end = 0.4), +#' #max_height = 10, scale_all_max = TRUE, set_max_height = TRUE, +#' material = rayvertex::material_list(diffuse="red", +#' ambient = "darkred", +#' diffuse_intensity = 0.2, +#' ambient_intensity = 0.1), +#' light_intensity = 1, light_relative = FALSE, +#' extent = attr(montereybay,"extent"), bevel_height = 5000, +#' base_height=0, clear_previous = TRUE, +#' zscale=200) +#' render_snapshot() +#' } +#' +#' # While the bevels all start at the same point in the above example, +#' # they rise to different levels due to being scaled by the maximum internal distance +#' # in the polygon. Setting `scale_all_max = TRUE` ensures the bevels are all scaled to the +#' # same maximum height (in this case, 3000m above the 5000m bevel start height). +#' if(run_documentation()) { +#' render_beveled_polygons(mont_county_buff, flat_shading = TRUE, heightmap = montereybay, +#' bevel = raybevel::generate_bevel("exp",bevel_end = 0.4), +#' max_height = 3000, scale_all_max = TRUE, set_max_height = TRUE, +#' material = rayvertex::material_list(diffuse="red", +#' ambient = "darkred", +#' diffuse_intensity = 0.2, +#' ambient_intensity = 0.1), +#' light_intensity = 1, light_relative = FALSE, +#' extent = attr(montereybay,"extent"), bevel_height = 5000, +#' base_height=0, clear_previous = TRUE, +#' zscale=200) +#' render_snapshot() +#' } +#' +#' # Rendering the polygons with `render_highquality()` +#' if(run_documentation()) { +#' render_highquality() +#' } +#' +#' # We can scale the size of the polygon to a column in the `sf` object as well: +#' # raybevel::generate_bevel() function. We can scale this data down using the `scale_data` +#' # argument. Note that this is applied as well as the `zscale` argument, and that you +#' # must think carefully about your scales and values if trying to represent a meaningful +#' # data visualization with this object. +#' if(run_documentation()) { +#' render_beveled_polygons(mont_county_buff, flat_shading = TRUE, angle = 45, bevel_width=1000, +#' data_column_top = "ALAND", scale_data = 1e-5, heightmap = montereybay, +#' #max_height = 1000, scale_all_max = TRUE, set_max_height = TRUE, +#' material = rayvertex::material_list(diffuse="red"), +#' light_intensity = 1, light_relative = FALSE, +#' extent = attr(montereybay,"extent"), clear_previous = TRUE, +#' zscale=200) +#' render_snapshot() +#' } +render_beveled_polygons = function(polygon, extent, + material = "grey", + bevel_material = NA, + angle = 45, bevel_width = 5, width_raw_units = FALSE, + bevel = NA, zscale = 1, + bevel_height = 1, base_height = 0, + raw_heights = FALSE, + raw_offsets = FALSE, + heights_relative_to_centroid = TRUE, + set_max_height = FALSE, max_height = 10, + scale_all_max = TRUE, + data_column_top = NULL, data_column_bottom = NULL, + heightmap = NULL, scale_data = 1, + holes = 0, alpha = 1, lit = TRUE, flat_shading = FALSE, + light_altitude = c(45,30), light_direction = c(315,225), + light_intensity = 1, light_relative = FALSE, + clear_previous = FALSE, ...) { + top = bevel_height + bottom = base_height + if(rgl::cur3d() == 0) { + stop("No rgl window currently open.") + } + if(!(length(find.package("raybevel", quiet = TRUE)) > 0)) { + stop("raybevel required to use render_roofs()") + } + if(clear_previous) { + rgl::pop3d(tag = "obj_raymesh_beveled_polygon") + if(missing(polygon)) { + return(invisible()) + } + } + if(is.character(material)) { + material = rayvertex::material_list(diffuse = material) + } + if(is.character(bevel_material)) { + bevel_material = rayvertex::material_list(diffuse = bevel_material) + } + e = get_extent(extent) + if(heights_relative_to_centroid) { + if(is.null(heightmap)) { + stop("Must pass in heightmap argument if using relative heights") + } + centroids = sf::st_coordinates(sf::st_centroid(polygon)) + xyz = transform_into_heightmap_coords(e, heightmap, centroids[,2], centroids[,1], + altitude = NULL, offset = 0, + zscale = 1) + bottom = xyz[,2] + bottom + bottom[is.na(bottom)] = min(xyz[,2]) + } + if(length(top) != 1) { + stopifnot(length(top) == nrow(polygon)) + } + if(!is.null(data_column_top)) { + stopifnot(data_column_top %in% colnames(polygon)) + } + if(length(bottom) != 1) { + stopifnot(length(bottom) == nrow(polygon)) + } + if(!is.null(data_column_bottom)) { + stopifnot(!data_column_bottom %in% colnames(polygon)) + } + + top_values = get_polygon_data_value(polygon, data_column_name = data_column_top, + scale_data = scale_data, default_value = top) + + bottom_values = get_polygon_data_value(polygon, data_column_name = data_column_bottom, + scale_data = scale_data, default_value = bottom) + + polygon = transform_polygon_into_raycoords(polygon, + heightmap = heightmap, + e=e, top = top_values, bottom = bottom_values) + top = polygon$top / zscale + bottom = polygon$bottom / zscale + skeletons = raybevel::skeletonize(polygon) + idx_sans_missing_geometry = unlist(lapply(skeletons, \(x) attr(x,"original_sf_row_index"))) + top = top[idx_sans_missing_geometry] + bottom = bottom[idx_sans_missing_geometry] + + if(!is.list(bevel)) { + stopifnot(angle > 0 && angle < 90) + angle_slope = tanpi(angle/180) + # Setting width_raw_units to TRUE makes it easier to generate visually nice bevels but + # don't necessarily corrspond to any meaningful real world distance + if(!width_raw_units) { + bevel_width = bevel_width / zscale + } + max_height = bevel_width * angle_slope + bevel = list(x=c(0,bevel_width), y=c(0,max_height)) + raw_heights = TRUE + raw_offsets = TRUE + } else { + max_height = max_height / zscale + } + + if(!heights_relative_to_centroid) { + poly_mesh = raybevel::generate_beveled_polygon(skeletons, + bevel_offsets = bevel, + vertical_offset = top, + raw_heights = raw_heights, + raw_offsets = raw_offsets, + base_height = 0, + set_max_height = set_max_height, + max_height = max_height, + material = material, + bevel_material = bevel_material, + scale_all_max = scale_all_max, + sides = TRUE, + base = TRUE) + } else { + poly_mesh = raybevel::generate_beveled_polygon(skeletons, + bevel_offsets = bevel, + vertical_offset = top, + raw_heights = raw_heights, + raw_offsets = raw_offsets, + base_height = bottom, + set_max_height = set_max_height, + max_height = max_height, + material = material, + bevel_material = bevel_material, + scale_all_max = scale_all_max, + sides = TRUE, + base = TRUE) + } + + + render_raymesh(poly_mesh, + xyz = matrix(c(0,0,0),ncol=3), + flat_shading = flat_shading, change_material = FALSE, + lit = lit, light_altitude = light_altitude, light_direction = light_direction, + light_intensity = light_intensity, light_relative = light_relative, + rgl_tag = "_beveled_polygon", ...) +} diff --git a/R/render_buildings.R b/R/render_buildings.R new file mode 100644 index 00000000..fee18aff --- /dev/null +++ b/R/render_buildings.R @@ -0,0 +1,253 @@ +#'@title Render Buildings +#' +#'@description Adds 3D polygons with roofs to the current scene, +#'using latitude/longitude or coordinates in the reference system defined by the extent object. +#' +#' @param polygon `sf` object, "SpatialPolygon" `sp` object, or xy coordinates +#' of polygon represented in a way that can be processed by `xy.coords()`. If +#' xy-coordinate based polygons are open, they will be closed by adding an +#' edge from the last point to the first. +#' @param extent Either an object representing the spatial extent of the 3D scene +#' (either from the `raster`, `terra`, `sf`, or `sp` packages), +#' a length-4 numeric vector specifying `c("xmin", "xmax", "ymin", "ymax")`, or the spatial object (from +#' the previously aforementioned packages) which will be automatically converted to an extent object. +#' @param material Default `"grey80"`. If a color string, this will specify the color of the sides/base of the building +#' Alternatively (for more customization), this can be a r`ayvertex::material_list()` object to specify +#' the full color/appearance/material options for the resulting `ray_mesh` mesh. +#' @param roof_material Default `NA`, defaults to the material specified in `material`. If a color string, this will specify the color of the roof of the building. +#' Alternatively (for more customization), this can be a `rayvertex::material_list()` object to specify +#' the full color/appearance/material options for the resulting `ray_mesh` mesh. +#' @param roof_height Default `1`. Height from the base of the building to the start of the roof. +#' @param base_height Default `0`. Height of the base of the roof. +#' @param heights_relative_to_centroid Default `FALSE`. Whether the heights should be measured in absolute +#' terms, or relative to the centroid of the polygon. +#' @param data_column_top Default `NULL`. A string indicating the column in the `sf` object to use +#' to specify the top of the extruded polygon. +#' @param data_column_bottom Default `NULL`. A string indicating the column in the `sf` object to use +#' to specify the bottom of the extruded polygon. +#' @param scale_data Default `1`. How much to scale the `top`/`bottom` value when rendering. Use +#' `zscale` to adjust the data to account for `x`/`y` grid spacing, and this argument to scale the data +#' for visualization. +#' @param holes Default `0`. If passing in a polygon directly, this specifies which index represents +#' the holes in the polygon. See the `earcut` function in the `decido` package for more information. +#' @param heightmap Default `NULL`. Automatically extracted from the rgl window--only use if auto-extraction +#' of matrix extent isn't working. A two-dimensional matrix, where each entry in the matrix is the elevation at that point. +#' All points are assumed to be evenly spaced. +#' @param zscale Default `1`. The ratio between the x and y spacing (which are assumed to be equal) and the z axis in the original heightmap. +#' @param alpha Default `1`. Transparency of the polygons. +#' @param lit Default `TRUE`. Whether to light the polygons. +#' @param light_altitude Default `c(45, 30)`. Degree(s) from the horizon from which to light the polygons. +#' @param light_direction Default `c(315, 225)`. Degree(s) from north from which to light the polygons. +#' @param light_intensity Default `1`. Intensity of the specular highlight on the polygons. +#' @param light_relative Default `FALSE`. Whether the light direction should be taken relative to the camera, +#' or absolute. +#' @param angle Default `45`. Angle of the roof. +#' @param relative_heights Default `TRUE`. Whether the heights specified in `roof_height` and `base_height` should +#' be measured relative to the underlying heightmap. +#' @param flat_shading Default `FALSE`. Set to `TRUE` to have nicer shading on the 3D polygons. This comes +#' with the slight penalty of increasing the memory use of the scene due to vertex duplication. This +#' will not affect software or high quality renders. +#' @param ... Additional arguments to pass to `rgl::triangles3d()`. +#' @param clear_previous Default `FALSE`. If `TRUE`, it will clear all existing polygons. +#' +#' @export +#' @examples +#' if(run_documentation()) { +#' # Load and visualize building footprints from Open Street Map +#' library(osmdata) +#' library(sf) +#' library(raster) +#' +#' osm_bbox = c(-121.9472, 36.6019, -121.9179, 36.6385) +#' +#' #Get buildings from OpenStreetMap +#' opq(osm_bbox) |> +#' add_osm_feature("building") |> +#' osmdata_sf() -> +#' osm_data +#' +#' #Get roads from OpenStreetMap +#' opq(osm_bbox) |> +#' add_osm_feature("highway") |> +#' osmdata_sf() -> +#' osm_road +#' +#' #Get extent +#' building_polys = osm_data$osm_polygons +#' osm_dem = elevatr::get_elev_raster(building_polys, z = 11, clip = "bbox") +#' e = extent(building_polys) +#' +#' # Crop DEM, but note that the cropped DEM will have an extent slightly different than what's +#' # specified in `e`. Save that new extent to `new_e`. +#' osm_dem |> +#' crop(e) |> +#' extent() -> +#' new_e +#' +#' osm_dem |> +#' crop(e) |> +#' raster_to_matrix() -> +#' osm_mat +#' +#' #Visualize areas less than one meter as water (approximate tidal range) +#' osm_mat[osm_mat <= 1] = -2 +#' +#' osm_mat %>% +#' rayimage::render_resized(mag=4) |> +#' sphere_shade(texture = "desert") |> +#' add_overlay(generate_polygon_overlay(building_polys, extent = new_e, +#' heightmap = osm_mat, +#' linewidth = 6, +#' resolution_multiply = 50), rescale_original = TRUE) |> +#' add_overlay(generate_line_overlay(osm_road$osm_lines, extent = new_e, +#' heightmap = osm_mat, +#' linewidth = 6, +#' resolution_multiply = 50), rescale_original = TRUE) |> +#' plot_3d(osm_mat, water = TRUE, windowsize = 800, watercolor = "dodgerblue", +#' zscale = 10, +#' background = "pink") +#' +#' #Render buildings +#' render_buildings(building_polys, flat_shading = TRUE, +#' angle = 30 , heightmap = osm_mat, +#' material = "white", roof_material = "white", +#' extent = new_e, roof_height = 3, base_height = 0, +#' zscale=10) +#' render_camera(theta=220, phi=22, zoom=0.45, fov=0) +#' render_snapshot() +#' } +#' +#' if(run_documentation()) { +#' #Zoom in to show roof details and render with render_highquality() +#' render_camera(fov=110) +#' render_highquality(camera_location = c(18.22, 0.57, -50.83), +#' camera_lookat = c(20.88, -2.83, -38.87), +#' focal_distance = 13, +#' lightdirection = 45) +#' +#' } +render_buildings = function(polygon, extent, + material = "grey", + roof_material = NA, + angle = 45, + zscale = 1, + scale_data = 1, + relative_heights = TRUE, + heights_relative_to_centroid = FALSE, + roof_height = 1, base_height = 0, + data_column_top = NULL, data_column_bottom = NULL, + heightmap = NULL, + holes = 0, alpha = 1, lit = TRUE, flat_shading = FALSE, + light_altitude = c(45,30), light_direction = c(315,225), + light_intensity = 1, light_relative = FALSE, + clear_previous = FALSE, ...) { + top = roof_height + bottom = base_height + if(rgl::cur3d() == 0) { + stop("No rgl window currently open.") + } + if(!(length(find.package("raybevel", quiet = TRUE)) > 0)) { + stop("raybevel required to use render_roofs()") + } + if(clear_previous) { + rgl::pop3d(tag = "obj_raymesh_building") + if(missing(polygon)) { + return(invisible()) + } + } + if(is.character(material)) { + material = rayvertex::material_list(diffuse = material) + } + if(is.character(roof_material)) { + roof_material = rayvertex::material_list(diffuse = roof_material) + } + e = get_extent(extent) + if(heights_relative_to_centroid) { + if(is.null(heightmap)) { + stop("Must pass in heightmap argument if using relative heights") + } + centroids = sf::st_coordinates(sf::st_centroid(polygon)) + xyz = transform_into_heightmap_coords(e, heightmap, centroids[,2], centroids[,1], + altitude = NULL, offset = 0, + zscale = 1) + bottom = xyz[,2] + bottom + bottom[is.na(bottom)] = min(xyz[,2]) + } + if(length(top) != 1) { + stopifnot(length(top) == nrow(polygon)) + } + if(!is.null(data_column_top)) { + stopifnot(data_column_top %in% colnames(polygon)) + } + if(length(bottom) != 1) { + stopifnot(length(bottom) == nrow(polygon)) + } + if(!is.null(data_column_bottom)) { + stopifnot(!data_column_bottom %in% colnames(polygon)) + } + + top_values = get_polygon_data_value(polygon, data_column_name = data_column_top, + scale_data = scale_data, default_value = top) + + bottom_values = get_polygon_data_value(polygon, data_column_name = data_column_bottom, + scale_data = scale_data, default_value = bottom) + + polygon = transform_polygon_into_raycoords(polygon, + heightmap = heightmap, + e=e, top = top_values, bottom = bottom_values) + top = polygon$top / zscale + bottom = polygon$bottom / zscale + skeletons = raybevel::skeletonize(polygon) + idx_sans_missing_geometry = unlist(lapply(skeletons, \(x) attr(x,"original_sf_row_index"))) + top = top[idx_sans_missing_geometry] + bottom = bottom[idx_sans_missing_geometry] + + if(!heights_relative_to_centroid) { + roof_mesh = raybevel::generate_roof(skeletons, + offset = top, + base_height = 0, + angle = angle, + material = material, + roof_material = roof_material, + base = TRUE, + sides = TRUE) + } else { + roof_mesh = raybevel::generate_roof(skeletons, + offset = top, + base_height = bottom, + angle = angle, + material = material, + roof_material = roof_material, + base = TRUE, + sides = TRUE) + } + if(relative_heights && !heights_relative_to_centroid) { + if(is.null(heightmap)) { + stop("Must pass in heightmap argument if using relative heights") + } + offset_building_heightmap = function(verts, bottom_value) { + tmpval = verts + tmpval[,1] = tmpval[,1] + nrow(heightmap)/2 + 0.5 + tmpval[,3] = tmpval[,3] + ncol(heightmap)/2 + 0.5 + tmpval[tmpval[,1] < 1,1] = 1 + tmpval[tmpval[,1] > nrow(heightmap),1] = nrow(heightmap) + tmpval[tmpval[,3] < 1,3] = 1 + tmpval[tmpval[,3] > ncol(heightmap),3] = ncol(heightmap) + new_heights = rayimage::interpolate_array(t(heightmap), tmpval[,1], tmpval[,3]) / zscale + base_verts = tmpval[,2] == 0 + verts[,2] = verts[,2] + new_heights + verts[base_verts, 2] = (new_heights[base_verts] + bottom_value) + return(verts) + } + for(i in seq_len(length(roof_mesh$vertices))) { + roof_mesh$vertices[[i]] = offset_building_heightmap(roof_mesh$vertices[[i]], bottom[i]) + } + } + + render_raymesh(roof_mesh, + xyz = matrix(c(0,0,0),ncol=3), + flat_shading = flat_shading, change_material = FALSE, + lit = lit, light_altitude = light_altitude, light_direction = light_direction, + light_intensity = light_intensity, light_relative = light_relative, + rgl_tag = "_building", ...) +} diff --git a/R/render_camera.R b/R/render_camera.R index 729959f5..07783584 100644 --- a/R/render_camera.R +++ b/R/render_camera.R @@ -102,6 +102,9 @@ render_camera = function(theta = NULL, phi = NULL, zoom = NULL, fov = NULL, } else { theta = rotmat[2] } + if(abs(phi) == 90) { + theta = theta - 90 + } } } rgl::view3d(theta = theta, phi = phi, fov = fov, zoom = zoom) diff --git a/R/render_contours.R b/R/render_contours.R index 224a4f29..6cb6a2d9 100644 --- a/R/render_contours.R +++ b/R/render_contours.R @@ -53,7 +53,7 @@ #' #'if(run_documentation()) { #'#Render using `render_highquality()` for a neon light effect -#'render_highquality(light = FALSE, smooth_line = TRUE, +#'render_highquality(light = FALSE, #' line_radius = 0.1, sample_method="sobol_blue", #' path_material = rayrender::light, ground_size = 0, #' path_material_args = list(importance_sample = FALSE, @@ -95,10 +95,10 @@ render_contours = function(heightmap = NULL, levels = levels[levels > min(heightmap,na.rm = TRUE)] levels = levels[levels < max(heightmap,na.rm = TRUE)] - heightmap = flipud(t(heightmap)) - isolineval = isoband::isolines(x = 1:ncol(heightmap), - y = 1:nrow(heightmap), - z = heightmap, + heightmap2 = flipud(t(heightmap)) + isolineval = isoband::isolines(x = seq_len(ncol(heightmap2)), + y = seq_len(nrow(heightmap2)), + z = heightmap2, levels=levels) contour_heights = as.numeric(names(isolineval)) if(!is.null(palette)) { @@ -109,15 +109,30 @@ render_contours = function(heightmap = NULL, color = palette } } + for(i in seq_len(length(isolineval))) { + contour_height = contour_heights[i] + offset + render_path(lat = isolineval[[i]]$y, long = isolineval[[i]]$x, + groups = isolineval[[i]]$id, altitude = contour_height, + heightmap = heightmap, + extent = extent_heightmap, tag = "contour3d", + zscale = zscale, linewidth = linewidth, + offset = offset, antialias = antialias, color = color[i]) + } } else { - color = rep(color,length(isolineval)) - } - for(i in seq_len(length(isolineval))) { - contour_height = contour_heights[i] + offset - render_path(lat = isolineval[[i]]$y, long = isolineval[[i]]$x, - groups = isolineval[[i]]$id, altitude = contour_height, + prev_id_max = 0 + isoline_list = vector("list", length = length(isolineval)) + for(i in seq_len(length(isolineval))) { + isolineval[[i]]$id = isolineval[[i]]$id + prev_id_max + isolineval[[i]]$altitude = contour_heights[i] + offset + prev_id_max = max(isolineval[[i]]$id) + isoline_list[[i]] = data.frame(isolineval[[i]]) + } + isolines_combined = do.call("rbind",isoline_list) + render_path(lat = isolines_combined$y, long = isolines_combined$x, + groups = isolines_combined$id, altitude = isolines_combined$altitude, extent = extent_heightmap, tag = "contour3d", + heightmap = heightmap, zscale = zscale, linewidth = linewidth, - offset = offset, antialias = antialias, color = color[i]) + offset = offset, antialias = antialias, color = color) } } diff --git a/R/render_obj.R b/R/render_obj.R index 57123b63..b81d0576 100644 --- a/R/render_obj.R +++ b/R/render_obj.R @@ -39,7 +39,13 @@ #' All points are assumed to be evenly spaced. #'@param baseshape Default `rectangle`. Shape of the base. Options are `c("rectangle","circle","hex")`. #'@param color Default `black`. Color of the 3D model, if `load_material = FALSE`. -#'@param offset Default `5`. Offset of the track from the surface, if `altitude = NULL`. +#'@param lit Default `TRUE`. Whether to light the polygons. +#'@param light_altitude Default `c(45, 60)`. Degree(s) from the horizon from which to light the polygons. +#'@param light_direction Default `c(45, 60)`. Degree(s) from north from which to light the polygons. +#'@param light_intensity Default `0.3`. Intensity of the specular highlight on the polygons. +#'@param light_relative Default `FALSE`. Whether the light direction should be taken relative to the camera, +#'or absolute. +#'@param offset Default `5`. Offset of the model from the surface, if `altitude = NULL`. #'@param clear_previous Default `FALSE`. If `TRUE`, it will clear all existing points. #'@param rgl_tag Default `""`. Tag to add to the rgl scene id, will be prefixed by `"obj"` #'@param ... Additional arguments to pass to `rgl::triangles3d()`. @@ -95,7 +101,9 @@ render_obj = function(filename, extent = NULL, lat = NULL, long = NULL, altitude zscale=1, heightmap = NULL, load_material = FALSE, load_normals = TRUE, color = "grey50", offset = 0, obj_zscale = FALSE, swap_yz = NULL, angle=c(0,0,0), scale = c(1,1,1), clear_previous = FALSE, - baseshape = "rectangle", + baseshape = "rectangle", lit = FALSE, + light_altitude = c(45,30), light_direction = c(315,135), + light_intensity = 0.3, light_relative = FALSE, rgl_tag = "", ...) { if(rgl::cur3d() == 0) { @@ -310,6 +318,7 @@ render_obj = function(filename, extent = NULL, lat = NULL, long = NULL, altitude texture = texture, tag = sprintf("obj%s",rgl_tag), back = "filled", + lit = lit, ...) } else { @@ -322,6 +331,7 @@ render_obj = function(filename, extent = NULL, lat = NULL, long = NULL, altitude texture = texture, tag = sprintf("obj%s",rgl_tag), back = "filled", + lit = lit, ...) } } else { @@ -334,6 +344,7 @@ render_obj = function(filename, extent = NULL, lat = NULL, long = NULL, altitude normals = new_norm, tag = sprintf("obj%s",rgl_tag), back = "filled", + lit = lit, ...) } else { id = rgl::triangles3d(x=obj$vertices, @@ -343,10 +354,25 @@ render_obj = function(filename, extent = NULL, lat = NULL, long = NULL, altitude indices = ind_temp, tag = sprintf("obj%s",rgl_tag), back = "filled", + lit = lit, ...) } } assign(as.character(id), mat_has_norm, envir = ray_has_norm_envir) assign(as.character(id), has_texture, envir = ray_has_tex_envir) } + if(lit) { + existing_lights = rgl::ids3d(type = "lights") + for(i in seq_len(nrow(existing_lights))) { + rgl::pop3d(type="lights") + } + if(length(light_altitude) < length(light_direction)) { + stop("light_altitude and light_direction must be same length") + } + for(i in seq_len(length(light_direction))) { + rgl::light3d(theta = -light_direction[i]+180, phi = light_altitude[i], + specular = convert_color(rep(light_intensity,3), as_hex = TRUE), + viewpoint.rel = light_relative) + } + } } diff --git a/R/render_path.R b/R/render_path.R index 3973da24..fa53455a 100644 --- a/R/render_path.R +++ b/R/render_path.R @@ -107,7 +107,7 @@ #'t = seq(0,2*pi,length.out=1000) #'circle_coords_lat = moss_landing_coord[1] + 0.5 * t/8 * sin(t*6) #'circle_coords_long = moss_landing_coord[2] + 0.5 * t/8 * cos(t*6) -#'render_path(extent = attr(montereybay,"extent"), heightmap = montereybay, +#'render_path(extent = attr(montereybay,"extent"), heightmap = montereybay, #' lat = unlist(circle_coords_lat), long = unlist(circle_coords_long), #' zscale=50, color="red", antialias=TRUE,offset=100, linewidth=5) #'render_camera(theta = 160, phi=33, zoom=0.4, fov=55) @@ -115,8 +115,10 @@ #'} #' #'if(run_documentation()) { -#'#And all of these work with `render_highquality()` +#'#And all of these work with `render_highquality()`. Here, I set `use_extruded_paths = TRUE` +#'#to get thick continuous paths. #'render_highquality(clamp_value=10, line_radius=3, min_variance = 0, +#' use_extruded_paths = TRUE, #' sample_method = "sobol_blue", samples = 128) #'} #'if(run_documentation()) { @@ -124,7 +126,7 @@ #'#`point_material_args` arguments in `render_highquality()` #'render_highquality(clamp_value=10, line_radius=3, min_variance = 0, #' sample_method = "sobol_blue", samples = 128, -#' path_material = rayrender::glossy, +#' path_material = rayrender::glossy, use_extruded_paths = TRUE, #' path_material_args = list(gloss = 0.5, reflectance = 0.2)) #'} #' @@ -171,10 +173,11 @@ render_path = function(lat, long = NULL, altitude = NULL, groups = NULL, reorder_merge_tolerance = reorder_merge_tolerance, simplify_tolerance = simplify_tolerance, clear_previous = FALSE, return_coords = TRUE) - xyz = do.call(rbind,xyz) - xyz = get_interpolated_points_path(xyz, n = resample_n) + xyz = lapply(xyz, get_interpolated_points_path, n = resample_n) + xyz = do.call("rbind",lapply(xyz, + \(x) rbind(x,matrix(NA,ncol=3,nrow=1)))) if(!return_coords) { - rgl::lines3d(xyz[,1] + 0.5,xyz[,2],xyz[,3] + 0.5, + rgl::lines3d(xyz, color = color, tag = tag, lwd = linewidth, line_antialias = antialias) return(invisible()) } else { @@ -246,14 +249,14 @@ render_path = function(lat, long = NULL, altitude = NULL, groups = NULL, } split_lat = split(lat, groups) split_long = split(long, groups) - if(is.null(heightmap)) { - vertex_info = get_ids_with_labels(typeval = c("surface", "surface_tris")) - nrow_map = max(rgl::rgl.attrib(vertex_info$id[1], "vertices")[,1]) - min(rgl::rgl.attrib(vertex_info$id[1], "vertices")[,1]) - ncol_map = max(rgl::rgl.attrib(vertex_info$id[1], "vertices")[,3]) - min(rgl::rgl.attrib(vertex_info$id[1], "vertices")[,3]) + + if(length(altitude) == length(lat)) { + split_altitude = split(altitude, groups) + single_altitude = FALSE } else { - ncol_map = ncol(heightmap) - nrow_map = nrow(heightmap) + single_altitude = TRUE } + if(!is.null(altitude)) { offset = 0 } @@ -262,18 +265,20 @@ render_path = function(lat, long = NULL, altitude = NULL, groups = NULL, for(group in seq_along(split_lat)) { lat = split_lat[[group]] long = split_long[[group]] - - xyz = transform_into_heightmap_coords(extent, heightmap, lat, long, + if(!single_altitude) { + altitude = split_altitude[[group]] + } + coord_list[[group]] = transform_into_heightmap_coords(extent, heightmap, lat, long, altitude, offset, zscale, filter_bounds = FALSE) - if(!return_coords) { - rgl::lines3d(xyz[,1] + 0.5,xyz[,2],xyz[,3] + 0.5, - color = color, tag = tag, - lwd = linewidth, line_antialias = antialias) - } else { - coord_list[[group]] = xyz - } } - if(return_coords) { + if(!return_coords) { + xyz = do.call("rbind",lapply(coord_list, + \(x) rbind(x,matrix(NA,ncol=3,nrow=1)))) + xyz = xyz[-nrow(xyz),] + rgl::lines3d(xyz, + color = color, tag = tag, + lwd = linewidth, line_antialias = antialias) + } else { return(coord_list) } } diff --git a/R/render_polygons.R b/R/render_polygons.R index 10c228ce..abfd0d9c 100644 --- a/R/render_polygons.R +++ b/R/render_polygons.R @@ -34,6 +34,8 @@ #' @param light_altitude Default `c(45, 60)`. Degree(s) from the horizon from which to light the polygons. #' @param light_direction Default `c(45, 60)`. Degree(s) from north from which to light the polygons. #' @param light_intensity Default `0.3`. Intensity of the specular highlight on the polygons. +#' @param light_relative Default `FALSE`. Whether the light direction should be taken relative to the camera, +#' or absolute. #' @param clear_previous Default `FALSE`. If `TRUE`, it will clear all existing polygons. #' @export #' @examples @@ -52,7 +54,7 @@ #' #' render_polygons(mont_county_buff, #' extent = attr(montereybay,"extent"), top = 10, -#' parallel = TRUE) +#' parallel = FALSE) #' render_snapshot() #' } #' if(run_documentation()) { @@ -61,7 +63,7 @@ #' render_camera(theta=-60, phi=20, zoom = 0.85, fov=0) #' render_polygons(mont_county_buff, #' extent = attr(montereybay,"extent"), bottom = 190, top=200, -#' parallel=TRUE,clear_previous=TRUE) +#' parallel=FALSE,clear_previous=TRUE) #' render_snapshot() #' } #' if(run_documentation()) { @@ -71,7 +73,7 @@ #' render_polygons(mont_county_buff, #' extent = attr(montereybay, "extent"), data_column_top = "ALAND", #' scale_data = 300/(2.6E9), color = "chartreuse4", -#' parallel = TRUE, clear_previous = TRUE) +#' clear_previous = TRUE) #' render_snapshot() #' } #' if(run_documentation()) { @@ -84,7 +86,8 @@ render_polygons = function(polygon, extent, color = "red", top = 1, bottom = NA heightmap = NULL, scale_data = 1, parallel = FALSE, holes = 0, alpha = 1, lit = TRUE, light_altitude = c(45,30), light_direction = c(315,135), - light_intensity = 0.3, clear_previous = FALSE) { + light_intensity = 0.3, light_relative = FALSE, + clear_previous = FALSE) { if(rgl::cur3d() == 0) { stop("No rgl window currently open.") } @@ -111,7 +114,7 @@ render_polygons = function(polygon, extent, color = "red", top = 1, bottom = NA vertex_list = list() if(!parallel) { if(inherits(polygon,"data.frame")) { - for(i in 1:nrow(polygon)) { + for(i in seq_len(nrow(polygon))) { if(inherits(polygon[i,],"SpatialPolygonsDataFrame") || inherits(polygon[i,],"SpatialPolygons") || inherits(polygon[i,],"sf")) { @@ -135,7 +138,7 @@ render_polygons = function(polygon, extent, color = "red", top = 1, bottom = NA cl = parallel::makeCluster(numbercores) doParallel::registerDoParallel(cl, cores = numbercores) vertex_list = tryCatch({ - foreach::foreach(i=1:nrow(polygon), .packages = c("rayrender","sf")) %dopar% { + foreach::foreach(i=seq_len(nrow(polygon)), .packages = c("rayrender","sf")) %dopar% { if(inherits(polygon[i,],"SpatialPolygonsDataFrame") || inherits(polygon[i,],"SpatialPolygons") || inherits(polygon[i,],"sf")) { @@ -176,16 +179,16 @@ render_polygons = function(polygon, extent, color = "red", top = 1, bottom = NA } if(lit) { existing_lights = rgl::ids3d(type = "lights") - for(i in 1:nrow(existing_lights)) { + for(i in seq_len(nrow(existing_lights))) { rgl::pop3d(type="lights") } if(length(light_altitude) < length(light_direction)) { stop("light_altitude and light_direction must be same length") } - for(i in 1:length(light_direction)) { + for(i in seq_len(length(light_direction))) { rgl::light3d(theta = -light_direction[i]+180, phi = light_altitude[i], - specular = convert_color(rep(light_intensity,3), as_hex = TRUE), - viewpoint.rel = FALSE) + specular = convert_color(rep(light_intensity,3), as_hex = TRUE), + viewpoint.rel = light_relative) } } } diff --git a/R/render_raymesh.R b/R/render_raymesh.R new file mode 100644 index 00000000..01888f01 --- /dev/null +++ b/R/render_raymesh.R @@ -0,0 +1,356 @@ +#'@title Render Raymesh +#' +#'@description Adds 3D raymesh model to the current scene, using latitude/longitude or coordinates in the reference +#'system defined by the extent object. If no altitude is provided, the raymesh will be elevated a constant offset +#'above the heightmap. If the raymesh goes off the edge, the raymesh will be filtered out. +#' +#'If no latitudes or longitudes are passed in, the raymesh will be plotted in the coordinate system set by the user-specified +#'`extent` argument as-is. Use this alongside `save_multipolygonz_to_obj()` to plot 3D polygons imported from geospatial sources +#'in the proper location (but for ease of use, use `render_multipolygonz()` to plot this data directly). +#' +#'@param raymesh `raymesh` object (see the rayvertex package for a description) +#'@param extent Either an object representing the spatial extent of the scene +#' (either from the `raster`, `terra`, `sf`, or `sp` packages), +#' a length-4 numeric vector specifying `c("xmin", "xmax","ymin","ymax")`, or the spatial object (from +#' the previously aforementioned packages) which will be automatically converted to an extent object. +#'@param long Vector of longitudes (or other coordinate in the same coordinate reference system as extent). +#'@param lat Vector of latitudes (or other coordinate in the same coordinate reference system as extent). +#'@param altitude Default `NULL`. Elevation of each point, in units of the elevation matrix (scaled by `zscale`). +#'If left `NULL`, this will be just the elevation value at ths surface, offset by `offset`. If a single value, +#'the OBJ will be rendered at that altitude. +#'@param xyz Default `NULL`, ignored. A 3 column numeric matrix, with each row specifying the x/y/z +#'coordinates of the OBJ model(s). Overrides lat/long/altitude and ignores extent to plot the OBJ in raw rgl coordinates. +#'@param load_normals Default `TRUE`. Whether to load normals for the 3D model. +#'@param change_material Default `TRUE`. Whether to change the raymesh material (to customize the color). +#'@param angle Default `c(0,0,0)`. Angle of rotation around the x, y, and z axes. If this is a matrix or list, +#'each row (or list entry) specifies the rotation of the nth model specified (number of rows/length of list must +#'equal the length of `lat`/`long`). +#'@param scale Default `c(1,1,1)`. Amount to scale the 3D model in the x, y, and z axes. If this is a matrix or list, +#'each row (or list entry) specifies the scale of the nth model specified (number of rows/length of list must +#'equal the length of `lat`/`long`). +#'@param obj_zscale Default `FALSE`. Whether to scale the size of the OBJ by zscale to have it match +#'the size of the map. If zscale is very big, this will make the model very small. +#'@param swap_yz Default `NULL`, defaults to `FALSE` unless plotting raw coordinates (no lat or long passed). +#' Whether to swap and Y and Z axes. (Y axis is vertical in +#'rayshader coordinates, but data is often provided with Z being vertical). +#'@param zscale Default `1`. The ratio between the x and y spacing (which are assumed to be equal) and the z axis in the original heightmap. +#'@param heightmap Default `NULL`. Automatically extracted from the rgl window--only use if auto-extraction +#'of matrix extent isn't working. A two-dimensional matrix, where each entry in the matrix is the elevation at that point. +#' All points are assumed to be evenly spaced. +#'@param baseshape Default `rectangle`. Shape of the base. Options are `c("rectangle","circle","hex")`. +#'@param flat_shading Default `FALSE`. If `TRUE`, this will use rgl's flat shading. +#'@param lit Default `TRUE`. Whether to light the polygons. +#'@param light_altitude Default `c(45, 60)`. Degree(s) from the horizon from which to light the polygons. +#'@param light_direction Default `c(45, 60)`. Degree(s) from north from which to light the polygons. +#'@param light_intensity Default `0.3`. Intensity of the specular highlight on the polygons. +#'@param light_relative Default `FALSE`. Whether the light direction should be taken relative to the camera, +#'or absolute. +#'@param color Default `black`. Color of the 3D model, if `load_material = FALSE`. +#'@param offset Default `5`. Offset of the track from the surface, if `altitude = NULL`. +#'@param clear_previous Default `FALSE`. If `TRUE`, it will clear all existing points. +#'@param rgl_tag Default `""`. Tag to add to the rgl scene id, will be prefixed by `"objraymsh"` +#'@param ... Additional arguments to pass to `rgl::triangles3d()`. +#'@export +#'@examples +#'if(run_documentation()) { +#'} +render_raymesh = function(raymesh, extent = NULL, lat = NULL, long = NULL, altitude=NULL, + xyz = NULL, + zscale=1, heightmap = NULL, load_normals = TRUE, change_material = TRUE, + color = "grey50", offset = 0, obj_zscale = FALSE, swap_yz = NULL, + angle=c(0,0,0), scale = c(1,1,1), clear_previous = FALSE, + baseshape = "rectangle", flat_shading = FALSE, lit = FALSE, + light_altitude = c(45,30), light_direction = c(315,135), + light_intensity = 1, light_relative = FALSE, + rgl_tag = "", + ...) { + if(rgl::cur3d() == 0) { + stop("No rgl window currently open.") + } + if(is.null(lat) || is.null(long)) { + single_obj = TRUE + } else { + single_obj = FALSE + } + heightmap = generate_base_shape(heightmap, baseshape) + if(is.null(xyz)) { + raw_coords = FALSE + if(!single_obj) { + if(is.null(swap_yz)) { + swap_yz = FALSE + } + xyz = transform_into_heightmap_coords(extent, heightmap, lat, long, + altitude, offset, zscale) + } else { + if(is.null(swap_yz)) { + swap_yz = TRUE + } + xyz = transform_into_heightmap_coords(extent, heightmap, lat, long, + altitude, offset, zscale, use_altitude = FALSE) + } + if(swap_yz) { + xyz = xyz[,c(1,3,2), drop = FALSE] + } + } else { + raw_coords = TRUE + } + + if(clear_previous) { + rgl::pop3d(tag = sprintf("obj_raymesh%s", rgl_tag)) + if(missing(raymesh)) { + return(invisible()) + } + } + if(is.numeric(color) && length(color) == 3) { + color = convert_color(color, as_hex = TRUE) + } + if(length(color) == 1 && nrow(xyz) > 0) { + color = rep(color,nrow(xyz)) + } else { + if(length(color) != nrow(xyz) && nrow(xyz) > 0) { + stop("If passing individual colors for each object, the number of colors must match the number of objects") + } + } + obj = raymesh + if(inherits(angle, "matrix")) { + stopifnot(is.numeric(angle)) + stopifnot(ncol(angle) == 3) + } else if (inherits(angle,"list")) { + angle = do.call(rbind,angle) + stopifnot(is.numeric(angle)) + stopifnot(ncol(angle) == 3) + } else { + stopifnot(length(angle) == 3) + if(nrow(xyz) > 0) { + angle = matrix(angle, ncol=3, nrow=nrow(xyz),byrow=TRUE) + } else { + angle = matrix(angle, ncol=3, nrow=1,byrow=TRUE) + } + } + if(inherits(scale, "matrix")) { + stopifnot(is.numeric(scale)) + stopifnot(ncol(scale) == 3) + } else if (inherits(scale,"list")) { + scale = do.call(rbind,scale) + stopifnot(is.numeric(scale)) + stopifnot(ncol(scale) == 3) + } else { + stopifnot(length(scale) == 3) + if(nrow(xyz) > 0) { + scale = matrix(scale, ncol=3, nrow=nrow(xyz),byrow=T) + } else { + scale = matrix(scale, ncol=3, nrow=1,byrow=T) + } + } + if(!is.null(lat) && !is.null(long) && any(is.na(xyz[,2]))) { + scale = scale[!is.na(xyz[,2]),] + angle = angle[!is.na(xyz[,2]),] + color = color[!is.na(xyz[,2])] + xyz = xyz[!is.na(xyz[,2]),] + if(nrow(xyz) == 0) { + stop("All models outside extent--check lat/long values and extent object.") + } + } + scenelist = list() + for(k in seq_len(nrow(xyz))) { + tempobj = obj + if(any(angle != 0)) { + tempobj = rayvertex::rotate_mesh(tempobj,as.numeric(angle[k,])) + } + if(any(scale[k,] != 1)) { + tempobj = rayvertex::scale_mesh(tempobj,as.numeric(scale[k,])) + } + scenelist[[k]] = rayvertex::translate_mesh(tempobj, as.numeric(xyz[k,])) + } + if(!raw_coords) { + stopifnot(!is.null(heightmap)) + nrow_map = nrow(heightmap) + ncol_map = ncol(heightmap) + + extent = get_extent(extent) + minpoint_x = (extent["xmax"] + extent["xmin"]) / 2 - zscale / 2 + minpoint_y = (extent["ymax"] + extent["ymin"]) / 2 + zscale / 2 + scale_x = (nrow_map-1) / (extent["xmax"] - extent["xmin"]) + scale_z = (ncol_map-1) / (extent["ymax"] - extent["ymin"]) + scale_y = 1/zscale + if(single_obj) { + obj_zscale = FALSE + idvals = rgl::ids3d(tags=TRUE) + if(any(c("surface","surface_tris") %in% idvals$tag)) { + id = idvals$id[idvals$tag %in% c("surface","surface_tris")] + id = id[1] + yvals = rgl::rgl.attrib(id,"vertices")[,2] + base_offset = (max(yvals,na.rm=TRUE) - min(yvals, na.rm=TRUE))/2 + } else { + base_offset = 0 + } + + if(swap_yz) { + scenelist[[1]] = rayvertex::translate_mesh(scenelist[[1]], + c(-minpoint_x,-minpoint_y, 0)) |> + rayvertex::swap_yz() |> + rayvertex::scale_mesh(c(scale_x,scale_y,scale_z)) + } else { + scenelist[[1]] = rayvertex::translate_mesh(scenelist[[1]], + c(-minpoint_x,0, -minpoint_y)) |> + rayvertex::scale_mesh(c(scale_x,scale_y,scale_z)) + } + } + } + if(nrow(xyz) == 0) { + scenelist[[1]] = obj + } + obj = rayvertex::scene_from_list(scenelist) + if(obj_zscale) { + obj = rayvertex::scale_mesh(obj, c(1,1,1)/zscale) + } + if(length(obj$materials[[1]]) == 0) { + obj = rayvertex::set_material(obj, rayvertex::material_list(diffuse = color)) + } else { + if(change_material) { + obj = rayvertex::change_material(obj, diffuse = color) + } + } + obj = rayvertex:::merge_scene(obj, flatten_materials = TRUE) + obj = rayvertex:::remove_duplicate_materials(obj) + + number_shapes = length(obj$shapes) + number_materials = length(obj$materials) + + inds_by_material = vector(mode="list", length=number_materials) + tex_by_material = vector(mode="list", length=number_materials) + norm_by_material = vector(mode="list", length=number_materials) + + for(i in seq_len(number_shapes)) { + for(j in seq_len(number_materials)) { + select_material = obj$shapes[[i]]$material_ids == (j - 1) + inds_by_material[[j]] = rbind(inds_by_material[[j]], obj$shapes[[i]]$indices[select_material,]) + tex_by_material[[j]] = rbind(tex_by_material[[j]], obj$shapes[[i]]$tex_indices[select_material,]) + norm_by_material[[j]] = rbind(norm_by_material[[j]], obj$shapes[[i]]$norm_indices[select_material,]) + } + } + + for(j in seq_len(number_materials)) { + new_tex = matrix(0,nrow=nrow(obj$vertices), ncol=2) + new_norm = matrix(0,nrow=nrow(obj$vertices), ncol=3) + + ind_temp = c(t(inds_by_material[[j]]+1)) + tex_vec = c(t(tex_by_material[[j]]+1)) + norm_vec = c(t(norm_by_material[[j]]+1)) + + for(k in seq_len(length(ind_temp))) { + if(tex_vec[k] != 0) { + new_tex[ind_temp[k],] = obj$texcoords[tex_vec[k],] + } + if(norm_vec[k] != 0) { + new_norm[ind_temp[k],] = obj$normals[norm_vec[k],] + } + } + texture = obj$materials[[j]]$diffuse_texname + diffuse_col = "white" + specular_col = "black" + ambient_col = "black" + shininess = obj$materials[[j]]$shininess + + has_texture = TRUE + if(nchar(texture) == 0) { + texture = NULL + has_texture = FALSE + diffuse_col = convert_color(darken_color(obj$materials[[j]]$diffuse, + obj$materials[[j]]$diffuse_intensity),as_hex=TRUE) + specular_col = convert_color(darken_color(obj$materials[[j]]$specular, + obj$materials[[j]]$specular_intensity),as_hex=TRUE) + ambient_col = convert_color(darken_color(obj$materials[[j]]$ambient, + obj$materials[[j]]$ambient_intensity),as_hex=TRUE) + } + mat_has_norm = all(new_norm != 0) && load_normals + + if(!flat_shading) { + verts = obj$vertices + ind_temp = ind_temp + new_tex = new_tex + new_norm = new_norm + } else { + verts = obj$vertices[ind_temp,] + if(has_texture) { + new_tex = new_tex[ind_temp,] + } + new_norm = NULL + ind_temp = NULL + } + if(has_texture) { + if(mat_has_norm) { + id = rgl::triangles3d(x=verts, + texcoords = new_tex, + indices = ind_temp, + textype = "rgba", + specular="white", + color = "white", + shininess = shininess, + normals = new_norm, + texture = texture, + tag = sprintf("obj_raymesh%s",rgl_tag), + back = "filled", + lit = lit, + ...) + + } else { + id = rgl::triangles3d(x=verts, + texcoords = new_tex, + textype = "rgba", + specular="black", + color = "white", + shininess = shininess, + indices = ind_temp, + texture = texture, + tag = sprintf("obj_raymesh%s",rgl_tag), + back = "filled", + lit = lit, + ...) + } + } else { + if(mat_has_norm) { + id = rgl::triangles3d(x=verts, + indices = ind_temp, + specular = specular_col, + color = diffuse_col, + ambient = ambient_col, + shininess = shininess, + normals = new_norm, + tag = sprintf("obj_raymesh%s",rgl_tag), + back = "filled", + lit = lit, + ...) + } else { + id = rgl::triangles3d(x=verts, + specular= specular_col, + color = diffuse_col, + ambient = ambient_col, + shininess = shininess, + indices = ind_temp, + tag = sprintf("obj_raymesh%s",rgl_tag), + back = "filled", + lit = lit, + ...) + } + } + assign(as.character(id), mat_has_norm, envir = ray_has_norm_envir) + assign(as.character(id), has_texture, envir = ray_has_tex_envir) + } + if(lit) { + existing_lights = rgl::ids3d(type = "lights") + for(i in seq_len(nrow(existing_lights))) { + rgl::pop3d(type="lights") + } + if(length(light_altitude) < length(light_direction)) { + stop("light_altitude and light_direction must be same length") + } + for(i in seq_len(length(light_direction))) { + rgl::light3d(theta = -light_direction[i]+180, phi = light_altitude[i], + specular = convert_color(rep(light_intensity,3), as_hex = TRUE), + ambient = convert_color(rep(light_intensity,3), as_hex = TRUE), + diffuse = convert_color(rep(light_intensity,3), as_hex = TRUE), + viewpoint.rel = light_relative) + } + } +} diff --git a/R/render_snapshot.R b/R/render_snapshot.R index bf29d0d3..2e465190 100644 --- a/R/render_snapshot.R +++ b/R/render_snapshot.R @@ -25,8 +25,6 @@ #'@param instant_capture Default `TRUE` if interactive, `FALSE` otherwise. If `FALSE`, a slight delay is added #'before taking the snapshot. This can help stop prevent rendering issues when running scripts. #'@param bring_to_front Default `FALSE`. Whether to bring the window to the front when taking the snapshot. -#'@param keep_user_par Default `TRUE`. Whether to keep the user's `par()` settings. Set to `FALSE` if you -#'want to set up a multi-pane plot (e.g. set `par(mfrow)`). #'@param webshot Default `FALSE`. Set to `TRUE` to have rgl use the `webshot2` package to take images, #'which can be used when `rgl.useNULL = TRUE`. #'@param width Default `NULL`. Optional argument to pass to `rgl::snapshot3d()` to specify the @@ -102,8 +100,8 @@ #'if(run_documentation()) { #'#Now with shadow mapped shadows, calculated in rayvertex #'render_snapshot(rayvertex_lighting = TRUE, -#' rayvertex_lights = rayvertex::directional_light(intensity = 2.5, -#' direction = c(-1, 0.8, -1)), +#' rayvertex_lights = rayvertex::directional_light(intensity = 1.2, +#' direction = c(-1, 1, -1)), #' rayvertex_shadow_map = TRUE, software_render = TRUE) #'} render_snapshot = function(filename, clear=FALSE, @@ -114,7 +112,7 @@ render_snapshot = function(filename, clear=FALSE, image_overlay = NULL, vignette = FALSE, vignette_color = "black", vignette_radius = 1.3, instant_capture = interactive(), bring_to_front = FALSE, - keep_user_par = FALSE, webshot = FALSE, + webshot = FALSE, width = NULL, height = NULL, software_render = FALSE, camera_location = NULL, camera_lookat = c(0,0,0), background = NULL, @@ -209,7 +207,7 @@ render_snapshot = function(filename, clear=FALSE, title_size = title_size, title_font = title_font) } if(missing(filename)) { - rayimage::plot_image(tempmap, keep_user_par = keep_user_par, new_page = new_page) + rayimage::plot_image(tempmap, new_page = new_page) } else { save_png(tempmap, filename) } diff --git a/R/render_snapshot_software.R b/R/render_snapshot_software.R index 7c48aa00..c631d619 100644 --- a/R/render_snapshot_software.R +++ b/R/render_snapshot_software.R @@ -1,6 +1,5 @@ -#'@title Render High Quality +#'@title Render Software Snapshot #' -#'@param cache_filename Name of temporary filename to store OBJ file, if the user does not want to rewrite the file each time. #'@param ground_size Default `10000`. The width of the plane representing the ground. #'@param camera_location Default `NULL`. Custom position of the camera. The `FOV`, `width`, and `height` arguments will still #'be derived from the rgl window. diff --git a/R/render_tree.R b/R/render_tree.R index 256841df..bf6b4e6a 100644 --- a/R/render_tree.R +++ b/R/render_tree.R @@ -51,6 +51,7 @@ #'@param heightmap Default `NULL`. Automatically extracted from the rgl window--only use if auto-extraction #'of matrix extent isn't working. A two-dimensional matrix, where each entry in the matrix is the elevation at that point. #' All points are assumed to be evenly spaced. +#'@param lit Default `TRUE`. Whether to apply lighting to the tree. #'@param clear_previous Default `FALSE`. If `TRUE`, it will clear all existing trees. #'@param ... Additional arguments to pass to `rgl::triangles3d()`. #'@export @@ -70,7 +71,7 @@ #'circle_coords_long = moss_landing_coord[2] + 0.3 * cos(t) #' #'render_tree(extent = attr(montereybay,"extent"), heightmap = montereybay, -#' tree_zscale = FALSE, tree_height = 30, +#' tree_zscale = FALSE, tree_height = 30, lit = TRUE, #' lat = unlist(circle_coords_lat), long = unlist(circle_coords_long), zscale=50) #'render_snapshot() #'} @@ -182,6 +183,7 @@ render_tree = function(lat = NULL, min_height = 0, max_height = Inf, zscale=1, + lit = TRUE, heightmap = NULL, baseshape = "rectangle", angle=c(0,0,0), @@ -465,12 +467,14 @@ render_tree = function(lat = NULL, lat = lat, long = long, extent = extent, zscale = zscale, offset = trunk_height*height_zscale, heightmap = heightmap, angle = angle, scale = tree_scale, - baseshape = baseshape, + baseshape = baseshape, + lit = lit, clear_previous = FALSE, rgl_tag = "tree", ...) render_obj(custom_obj_trunk, color = trunk_color, lat = lat, long = long, extent = extent, zscale = zscale, offset = 0, baseshape = baseshape, + lit = lit, heightmap = heightmap, angle = angle, scale = trunk_scale, rgl_tag = "tree", ...) } else if(custom_tree) { @@ -488,6 +492,7 @@ render_tree = function(lat = NULL, baseshape = baseshape, clear_previous = FALSE, rgl_tag = "tree", + lit = lit, ...) } else if(type == "basic") { # If a basic type is specified, render the basic tree's crown and trunk @@ -496,11 +501,13 @@ render_tree = function(lat = NULL, offset = (trunk_height + crown_height/3)*height_zscale, heightmap = heightmap, angle = angle, scale = tree_scale, baseshape = baseshape, + lit = lit, clear_previous = FALSE, rgl_tag = "tree", ...) render_obj(tree_trunk_obj(), color = trunk_color, lat = lat, long = long, extent = extent, zscale = zscale, offset = 0, baseshape = baseshape, + lit = lit, heightmap = heightmap, angle = angle, scale = trunk_scale, rgl_tag = "tree", ...) } else if (type == "cone") { @@ -509,12 +516,14 @@ render_tree = function(lat = NULL, lat = lat, long = long, extent = extent, zscale = zscale, offset = trunk_height*height_zscale, baseshape = baseshape, + lit = lit, heightmap = heightmap, angle = angle, scale = tree_scale, clear_previous = FALSE, rgl_tag = "tree", ...) render_obj(tree_trunk_obj(), color = trunk_color, lat = lat, long = long, extent = extent, zscale = zscale, offset = 0, baseshape = baseshape, + lit = lit, heightmap = heightmap, angle = angle, scale = trunk_scale, rgl_tag = "tree", ...) } else { diff --git a/R/transform_into_heightmap_coords.R b/R/transform_into_heightmap_coords.R index 93ebbdce..909a6e7f 100644 --- a/R/transform_into_heightmap_coords.R +++ b/R/transform_into_heightmap_coords.R @@ -27,14 +27,16 @@ transform_into_heightmap_coords = function(extent, heightmap, lat = NULL, long = ncol_map = ncol(heightmap) nrow_map = nrow(heightmap) } - distances_x = (long-e["xmin"])/(e["xmax"] - e["xmin"]) * nrow_map - distances_y = ncol_map - (lat-e["ymin"])/(e["ymax"] - e["ymin"]) * ncol_map + nrow_map = nrow_map - 1 + ncol_map = ncol_map - 1 + distances_x = (long-e["xmin"])/(e["xmax"] - e["xmin"]) * nrow_map + 1 + distances_y = 1 + ncol_map - (lat-e["ymin"])/(e["ymax"] - e["ymin"]) * ncol_map if(filter_bounds) { - filter_out = distances_y > ncol(heightmap) | - distances_x > nrow(heightmap) | - distances_x < 1 | - distances_y < 1 + filter_out = distances_y > ncol_map | + distances_x > nrow_map | + distances_x < 0 | + distances_y < 0 } else { filter_out = rep(FALSE, length(lat)) } @@ -45,7 +47,6 @@ transform_into_heightmap_coords = function(extent, heightmap, lat = NULL, long = } distances_x_index = distances_x distances_y_index = distances_y - distances_x_index[floor(distances_x_index) >= nrow(heightmap)] = nrow(heightmap) distances_y_index[floor(distances_y_index) >= ncol(heightmap)] = ncol(heightmap) distances_x_index[floor(distances_x_index) < 1] = 1 @@ -65,8 +66,8 @@ transform_into_heightmap_coords = function(extent, heightmap, lat = NULL, long = } altitude[filter_out] = NA if(use_altitude) { - return(matrix(c(distances_x-nrow_map/2, altitude/zscale + offset, distances_y-ncol_map/2),ncol=3,nrow=length(altitude))) + return(matrix(c(distances_x-nrow_map/2-1, altitude/zscale + offset, distances_y-ncol_map/2-1),ncol=3,nrow=length(altitude))) } else { - return(matrix(c(distances_x-nrow_map/2, offset, distances_y-ncol_map/2),ncol=3,nrow=length(altitude))) + return(matrix(c(distances_x-nrow_map/2-1, offset, distances_y-ncol_map/2-1),ncol=3,nrow=length(altitude))) } } \ No newline at end of file diff --git a/R/transform_sf_to_raycoords.R b/R/transform_sf_to_raycoords.R new file mode 100644 index 00000000..e971dc89 --- /dev/null +++ b/R/transform_sf_to_raycoords.R @@ -0,0 +1,144 @@ +#'@title Transform Polygon into Raycoords +#' +#'@keywords internal +transform_polygon_into_raycoords = function(polygon, heightmap = NULL, e = NULL, + top = NULL, bottom = NULL) { + if(inherits(polygon,"SpatialPolygonsDataFrame") || inherits(polygon,"SpatialPolygons")) { + polygon = sf::st_as_sf(polygon) + } + if(is.null(heightmap)) { + vertex_info = get_ids_with_labels(typeval = c("surface", "surface_tris")) + nrow_map = max(rgl::rgl.attrib(vertex_info$id[1], "vertices")[,1]) - + min(rgl::rgl.attrib(vertex_info$id[1], "vertices")[,1]) + ncol_map = max(rgl::rgl.attrib(vertex_info$id[1], "vertices")[,3]) - + min(rgl::rgl.attrib(vertex_info$id[1], "vertices")[,3]) + } else { + ncol_map = ncol(heightmap) + nrow_map = nrow(heightmap) + } + ncol_map = ncol_map - 1 + nrow_map = nrow_map - 1 + + if(inherits(polygon,"sf")) { + if(length(find.package("sf",quiet=TRUE)) == 0) { + stop("sf package required when handling sf objects") + } + #Remove z dimension from multipolygon z geometry + if(ncol(as.matrix(sf::st_geometry(polygon)[[1]])) == 3) { + polygon = sf::st_as_sf(sf::as_Spatial(sf::st_zm(polygon))) + } else { + polygon = sf::st_as_sf(sf::as_Spatial(polygon)) + } + new_polygon = sf::st_coordinates(polygon) + } else { + xylist = grDevices::xy.coords(polygon) + new_polygon = stats::setNames(matrix(c(xylist$x,xylist$y, rep(1,length(xylist$y)*2)),ncol=4), + c("X", "Y", "L1", "L2")) + } + + new_extent = c(nrow_map/2,-nrow_map/2, + ncol_map/2,-ncol_map/2) + new_sf_list = list() + for(i in seq_len(nrow(polygon))) { + new_sf_list[[i]] = transform_polygon_custom_crs(polygon[i,], + e, new_extent) + + new_sf_list[[i]]$top = top[i] + new_sf_list[[i]]$bottom = bottom[i] + } + return(do.call("rbind", new_sf_list)) +} + +#'@title Transform Points into Raycoords +#' +#'@keywords internal +transform_points_into_raycoords = function(points, heightmap = NULL, e = NULL, + top = NULL, bottom = NULL) { + if(is.null(heightmap)) { + vertex_info = get_ids_with_labels(typeval = c("surface", "surface_tris")) + nrow_map = max(rgl::rgl.attrib(vertex_info$id[1], "vertices")[,1]) - + min(rgl::rgl.attrib(vertex_info$id[1], "vertices")[,1]) + ncol_map = max(rgl::rgl.attrib(vertex_info$id[1], "vertices")[,3]) - + min(rgl::rgl.attrib(vertex_info$id[1], "vertices")[,3]) + } else { + ncol_map = ncol(heightmap) + nrow_map = nrow(heightmap) + } + ncol_map = ncol_map - 1 + nrow_map = nrow_map - 1 + if(inherits(points,"sf")) { + new_points = sf::st_coordinates(points) + } else { + xylist = grDevices::xy.coords(points) + new_points = stats::setNames(matrix(c(xylist$x,xylist$y),ncol=2), + c("X", "Y")) + } + new_extent = c(nrow_map/2, -nrow_map/2, + ncol_map/2, -ncol_map/2) + return(transform_points_custom_crs(new_points, + e, new_extent)) +} + + +#'@title Transform Polygon into Raycoords +#' +#'@keywords internal +transform_polygon_custom_crs = function(sf_object, orig_extent, new_extent) { + # Extract coordinates + coords = as.data.frame(sf::st_coordinates(sf_object)) + coords = coords[,c("X", "Y", "L1", "L2")] + # Compute scale factors + + scale_x = (new_extent[2] - new_extent[1]) / (orig_extent[2] - orig_extent[1]) + scale_y = (new_extent[4] - new_extent[3]) / (orig_extent[4] - orig_extent[3]) + + # Apply transformation + coords[,1] = (coords[,1] - orig_extent[1]) * scale_x + new_extent[1] + coords[,2] = (coords[,2] - orig_extent[3]) * scale_y + new_extent[3] + + polygons = split(coords, coords$L2) + sf_objects = list() + for(i in seq_along(polygons)) { + poly_holes = split(polygons[[i]],polygons[[i]]$L1) + poly_holes |> + lapply(\(x) as.matrix(x[,c("X","Y")])) |> + sf::st_polygon() |> + sf::st_sfc() |> + sf::st_sf() -> + sf_objects[[i]] + + colnames(sf_objects[[i]]) = "geometry" + sf::st_geometry(sf_objects[[i]]) = "geometry" + } + + return(do.call("rbind",sf_objects)) +} + +#'@title Transform Polygon into Raycoords +#' +#'@keywords internal +transform_points_custom_crs = function(sf_object, orig_extent, new_extent) { + # Extract coordinates + # coords = as.data.frame(sf::st_coordinates(sf_object)) + coords = as.data.frame(sf_object) + + coords = coords[,c("X", "Y")] + # Compute scale factors + scale_x <- (new_extent[2] - new_extent[1]) / (orig_extent[2] - orig_extent[1]) + scale_y <- (new_extent[4] - new_extent[3]) / (orig_extent[4] - orig_extent[3]) + + # Apply transformation + coords[,1] <- (coords[,1] - orig_extent[1]) * scale_x + new_extent[1] + coords[,2] <- (coords[,2] - orig_extent[3]) * scale_y + new_extent[3] + + coords |> + as.matrix() |> + sf::st_multipoint() |> + sf::st_sfc() |> + sf::st_sf() -> + sf_object + colnames(sf_object) = "geometry" + sf::st_geometry(sf_object) = "geometry" + + return(sf_object) +} diff --git a/man/add_overlay.Rd b/man/add_overlay.Rd index 9ef108fe..3535bbb5 100644 --- a/man/add_overlay.Rd +++ b/man/add_overlay.Rd @@ -5,8 +5,8 @@ \title{Add Overlay} \usage{ add_overlay( - hillshade, - overlay, + hillshade = NULL, + overlay = NULL, alphalayer = 1, alphacolor = NULL, alphamethod = "max", diff --git a/man/darken_color.Rd b/man/darken_color.Rd index 2768d11e..dd7b7d3c 100644 --- a/man/darken_color.Rd +++ b/man/darken_color.Rd @@ -4,7 +4,7 @@ \alias{darken_color} \title{Darken Color} \usage{ -darken_color(col, darken = 0.5) +darken_color(col, darken = 0.3) } \arguments{ \item{col}{RGB colors} diff --git a/man/generate_compass_overlay.Rd b/man/generate_compass_overlay.Rd index 207b7535..103dc026 100644 --- a/man/generate_compass_overlay.Rd +++ b/man/generate_compass_overlay.Rd @@ -13,6 +13,7 @@ generate_compass_overlay( heightmap = NULL, width = NA, height = NA, + resolution_multiply = 1, color1 = "white", color2 = "black", text_color = "black", @@ -45,6 +46,11 @@ RGB image array automatically.} \item{height}{Default `NA`. Width of the resulting image array. Default the same dimensions as height map.} +\item{resolution_multiply}{Default `1`. If passing in `heightmap` instead of width/height, amount to +increase the resolution of the overlay, which should make lines/polygons finer. +Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +in the final map.} + \item{color1}{Default `white`. Primary color of the compass.} \item{color2}{Default `black`. Secondary color of the symcompass.} diff --git a/man/generate_contour_overlay.Rd b/man/generate_contour_overlay.Rd index fd821bc6..132cd6ba 100644 --- a/man/generate_contour_overlay.Rd +++ b/man/generate_contour_overlay.Rd @@ -11,6 +11,7 @@ generate_contour_overlay( zscale = 1, width = NA, height = NA, + resolution_multiply = 1, color = "black", linewidth = 1 ) @@ -30,6 +31,11 @@ of 1 meter and the grid values are separated by 10 meters, `zscale` would be 10. \item{height}{Default `NA`. Width of the resulting overlay. Default the same dimensions as heightmap.} +\item{resolution_multiply}{Default `1`. If passing in `heightmap` instead of width/height, amount to +increase the resolution of the overlay, which should make lines/polygons finer. +Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +in the final map.} + \item{color}{Default `black`. Color.} \item{linewidth}{Default `1`. Line width.} diff --git a/man/generate_label_overlay.Rd b/man/generate_label_overlay.Rd index 620ba25d..83409850 100644 --- a/man/generate_label_overlay.Rd +++ b/man/generate_label_overlay.Rd @@ -12,6 +12,7 @@ generate_label_overlay( heightmap = NULL, width = NA, height = NA, + resolution_multiply = 1, text_size = 1, color = "black", font = 1, @@ -47,6 +48,11 @@ overlay automatically.} \item{height}{Default `NA`. Width of the resulting overlay. Default the same dimensions as height map.} +\item{resolution_multiply}{Default `1`. If passing in `heightmap` instead of width/height, amount to +increase the resolution of the overlay, which should make lines/polygons/text finer. +Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +in the final map.} + \item{text_size}{Default `1`. Text size.} \item{color}{Default `black`. Color of the labels.} diff --git a/man/generate_line_overlay.Rd b/man/generate_line_overlay.Rd index 912448f1..05e349a1 100644 --- a/man/generate_line_overlay.Rd +++ b/man/generate_line_overlay.Rd @@ -10,6 +10,7 @@ generate_line_overlay( heightmap = NULL, width = NA, height = NA, + resolution_multiply = 1, color = "black", linewidth = 1, lty = 1, @@ -32,6 +33,11 @@ overlay automatically.} \item{height}{Default `NA`. Width of the resulting overlay. Default the same dimensions as height map.} +\item{resolution_multiply}{Default `1`. If passing in `heightmap` instead of width/height, amount to +increase the resolution of the overlay, which should make lines/polygons/text finer. +Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +in the final map.} + \item{color}{Default `black`. Color of the lines.} \item{linewidth}{Default `1`. Line width.} diff --git a/man/generate_point_overlay.Rd b/man/generate_point_overlay.Rd index 3027738a..62f71dc7 100644 --- a/man/generate_point_overlay.Rd +++ b/man/generate_point_overlay.Rd @@ -10,6 +10,7 @@ generate_point_overlay( heightmap = NULL, width = NA, height = NA, + resolution_multiply = 1, pch = 20, color = "black", size = 1, @@ -32,6 +33,11 @@ overlay automatically.} \item{height}{Default `NA`. Width of the resulting overlay. Default the same dimensions as height map.} +\item{resolution_multiply}{Default `1`. If passing in `heightmap` instead of width/height, amount to +increase the resolution of the overlay, which should make lines/polygons/points finer. +Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +in the final map.} + \item{pch}{Default `20`, solid. Point symbol. `0` = square, `1` = circle, `2` = triangle point up, `3` = plus, `4` = cross, `5` = diamond, `6` = triangle point down, `7` = square cross, `8` = star, diff --git a/man/generate_polygon_overlay.Rd b/man/generate_polygon_overlay.Rd index 3466ec51..03146114 100644 --- a/man/generate_polygon_overlay.Rd +++ b/man/generate_polygon_overlay.Rd @@ -10,6 +10,7 @@ generate_polygon_overlay( heightmap = NULL, width = NA, height = NA, + resolution_multiply = 1, offset = c(0, 0), data_column_fill = NULL, linecolor = "black", @@ -32,6 +33,11 @@ overlay automatically.} \item{height}{Default `NA`. Width of the resulting overlay. Default the same dimensions as height map.} +\item{resolution_multiply}{Default `1`. If passing in `heightmap` instead of width/height, amount to +increase the resolution of the overlay, which should make lines/polygons/text finer. +Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +in the final map.} + \item{offset}{Default `c(0,0)`. Horizontal and vertical offset to apply to the polygon, in units of `geometry`.} \item{data_column_fill}{Default `NULL`. The column to map the polygon fill color to.} diff --git a/man/generate_scalebar_overlay.Rd b/man/generate_scalebar_overlay.Rd index a5817a10..7afa416b 100644 --- a/man/generate_scalebar_overlay.Rd +++ b/man/generate_scalebar_overlay.Rd @@ -22,6 +22,7 @@ generate_scalebar_overlay( heightmap = NULL, width = NA, height = NA, + resolution_multiply = 1, color1 = "white", color2 = "black", text_color = "black", @@ -81,6 +82,11 @@ RGB image array automatically.} \item{height}{Default `NA`. Width of the resulting image array. Default the same dimensions as height map.} +\item{resolution_multiply}{Default `1`. If passing in `heightmap` instead of width/height, amount to +increase the resolution of the overlay, which should make lines/polygons/text finer. +Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +in the final map.} + \item{color1}{Default `black`. Primary color of the scale bar.} \item{color2}{Default `white`. Secondary color of the scale bar.} diff --git a/man/generate_waterline_overlay.Rd b/man/generate_waterline_overlay.Rd index 4a57eb12..e9ab0829 100644 --- a/man/generate_waterline_overlay.Rd +++ b/man/generate_waterline_overlay.Rd @@ -20,6 +20,9 @@ generate_waterline_overlay( evenly_spaced = FALSE, zscale = 1, cutoff = 0.999, + width = NA, + height = NA, + resolution_multiply = 1, min_area = length(heightmap)/400, max_height = NULL, return_distance_matrix = FALSE @@ -60,6 +63,15 @@ of 1 meter and the grid values are separated by 10 meters, `zscale` would be 10. \item{cutoff}{Default `0.999`. Arguments passed to `detect_water()`. Ignored if `boolean = TRUE`.The lower limit of the z-component of the unit normal vector to be classified as water.} +\item{width}{Default `NA`. Width of the resulting image array. Default the same dimensions as height map.} + +\item{height}{Default `NA`. Width of the resulting image array. Default the same dimensions as height map.} + +\item{resolution_multiply}{Default `1`. If passing in `heightmap` instead of width/height, amount to +increase the resolution of the overlay, which should make lines/polygons/text finer. +Should be combined with `add_overlay(rescale_original = TRUE)` to ensure those added details are captured +in the final map.} + \item{min_area}{Default `length(heightmap)/400`. Arguments passed to `detect_water()`. Ignored if `boolean = TRUE`. Minimum area (in units of the height matrix x and y spacing) to be considered a body of water.} \item{max_height}{Default `NULL`. Arguments passed to `detect_water()`. Ignored if `boolean = TRUE`. If passed, this number will specify the maximum height a point can be considered to be water. diff --git a/man/get_polygon_data_value.Rd b/man/get_polygon_data_value.Rd new file mode 100644 index 00000000..96e5a5a8 --- /dev/null +++ b/man/get_polygon_data_value.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/get_polygon_data_value.R +\name{get_polygon_data_value} +\alias{get_polygon_data_value} +\title{Get Data Value from spatial object} +\usage{ +get_polygon_data_value( + polygon, + data_column_name = NULL, + default_value = 0, + scale_data = 1 +) +} +\arguments{ +\item{polygon}{This is an sf object} +} +\description{ +Get Data Value from spatial object +} +\keyword{internal} diff --git a/man/plot_3d.Rd b/man/plot_3d.Rd index 861fae6e..9b596de6 100644 --- a/man/plot_3d.Rd +++ b/man/plot_3d.Rd @@ -47,8 +47,7 @@ plot_3d( verbose = FALSE, plot_new = TRUE, close_previous = TRUE, - clear_previous = TRUE, - ... + clear_previous = TRUE ) } \arguments{ @@ -74,7 +73,7 @@ of 1 meter and the grid values are separated by 10 meters, `zscale` would be 10. \item{shadowdepth}{Default `auto`, which sets it to `soliddepth - soliddepth/10`. Depth of the shadow layer.} \item{shadowcolor}{Default `auto`. Color of the shadow, automatically computed as `shadow_darkness` -the luminance of the `background` color in the CIELab colorspace if not specified.} +the luminance of the `background` color in the CIELuv colorspace if not specified.} \item{shadow_darkness}{Default `0.5`. Darkness of the shadow, if `shadowcolor = "auto"`.} @@ -160,8 +159,6 @@ the data will be plotted in the same window.} old windows will be kept open.} \item{clear_previous}{Default `TRUE`. Clears the previously open `rgl` window if `plot_new = FALSE`.} - -\item{...}{Additional arguments to pass to the `rgl::par3d` function.} } \description{ Displays the shaded map in 3D with the `rgl` package. diff --git a/man/plot_gg.Rd b/man/plot_gg.Rd index c3c8650a..92ae865a 100644 --- a/man/plot_gg.Rd +++ b/man/plot_gg.Rd @@ -190,7 +190,6 @@ render_snapshot() } #Contours and other lines will automatically be ignored. Here is the volcano dataset: - ggvolcano = volcano \%>\% reshape2::melt() \%>\% ggplot() + @@ -199,7 +198,8 @@ ggvolcano = volcano \%>\% scale_x_continuous("X",expand = c(0,0)) + scale_y_continuous("Y",expand = c(0,0)) + scale_fill_gradientn("Z",colours = terrain.colors(10)) + - coord_fixed() + coord_fixed() + + theme(legend.position = "none") ggvolcano if(run_documentation()) { diff --git a/man/plot_map.Rd b/man/plot_map.Rd index 3d76a86e..7b7055da 100644 --- a/man/plot_map.Rd +++ b/man/plot_map.Rd @@ -17,7 +17,6 @@ plot_map( title_bar_color = NULL, title_bar_alpha = 0.5, title_position = "northwest", - keep_user_par = FALSE, ... ) } @@ -49,9 +48,6 @@ image_annotate) corner to offset the title.} \item{title_position}{Default `northwest`. Position of the title.} -\item{keep_user_par}{Default `TRUE`. Whether to keep the user's `par()` settings. Set to `FALSE` if you -want to set up a multi-pane plot (e.g. set `par(mfrow)`).} - \item{...}{Additional arguments to pass to the `raster::plotRGB` function that displays the map.} } \description{ diff --git a/man/render_beveled_polygons.Rd b/man/render_beveled_polygons.Rd new file mode 100644 index 00000000..66f87442 --- /dev/null +++ b/man/render_beveled_polygons.Rd @@ -0,0 +1,228 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/render_beveled_polygons.R +\name{render_beveled_polygons} +\alias{render_beveled_polygons} +\title{Render Beveled Polygons} +\usage{ +render_beveled_polygons( + polygon, + extent, + material = "grey", + bevel_material = NA, + angle = 45, + bevel_width = 5, + width_raw_units = FALSE, + bevel = NA, + zscale = 1, + bevel_height = 1, + base_height = 0, + raw_heights = FALSE, + raw_offsets = FALSE, + heights_relative_to_centroid = TRUE, + set_max_height = FALSE, + max_height = 10, + scale_all_max = TRUE, + data_column_top = NULL, + data_column_bottom = NULL, + heightmap = NULL, + scale_data = 1, + holes = 0, + alpha = 1, + lit = TRUE, + flat_shading = FALSE, + light_altitude = c(45, 30), + light_direction = c(315, 225), + light_intensity = 1, + light_relative = FALSE, + clear_previous = FALSE, + ... +) +} +\arguments{ +\item{polygon}{`sf` object, "SpatialPolygon" `sp` object, or xy coordinates +of polygon represented in a way that can be processed by `xy.coords()`. If +xy-coordinate based polygons are open, they will be closed by adding an +edge from the last point to the first.} + +\item{extent}{Either an object representing the spatial extent of the 3D scene +(either from the `raster`, `terra`, `sf`, or `sp` packages), +a length-4 numeric vector specifying `c("xmin", "xmax", "ymin", "ymax")`, or the spatial object (from +the previously aforementioned packages) which will be automatically converted to an extent object.} + +\item{material}{Default `"grey80"`. If a color string, this will specify the color of the sides/base of the polygon. +Alternatively (for more customization), this can be a r`ayvertex::material_list()` object to specify +the full color/appearance/material options for the resulting `ray_mesh` mesh.} + +\item{bevel_material}{Default `NA`, defaults to the material specified in `material`. If a color string, this will specify the color of the polygon bevel. +Alternatively (for more customization), this can be a `rayvertex::material_list()` object to specify +the full color/appearance/material options for the resulting `ray_mesh` mesh.} + +\item{angle}{Default `45`. Angle of the bevel.} + +\item{bevel_width}{Default `5`. Width of the bevel.} + +\item{width_raw_units}{Default `FALSE`. Whether the bevel width should be measured in raw display units, +or the actual units of the map.} + +\item{bevel}{Default `NULL`. A list with `x`/`y` components that specify a bevel profile. See `raybevel::generate_bevel()`} + +\item{zscale}{Default `1`. The ratio between the x and y spacing (which are assumed to be equal) and the z axis in the original heightmap.} + +\item{bevel_height}{Default `1`. Height from the base of the polygon to the start of the beveled top.} + +\item{base_height}{Default `0`. Height of the base of the polygon.} + +\item{raw_heights}{Default `FALSE`. A logical flag indicating whether the `bevel_heights` are already +in raw format and do not need to be multiplied by the maximum time of the skeleton. +See the documentation for `raybevel::generate_beveled_polygon()` for more info.} + +\item{raw_offsets}{Default `FALSE`. A logical flag indicating whether the `bevel_offsets` are already +in raw format and do not need to be multiplied by the maximum time of the skeleton. +See the documentation for `raybevel::generate_beveled_polygon()` for more info.} + +\item{heights_relative_to_centroid}{Default `FALSE`. Whether the heights should be measured in absolute +terms, or relative to the centroid of the polygon.} + +\item{set_max_height}{Default `FALSE`. A logical flag that controls whether to set the max height of the roof based on the `max_height` argument.} + +\item{max_height}{Default `1`. The maximum height of the polygon.} + +\item{scale_all_max}{Default `FALSE`. If passing in a list of multiple skeletons with polygons, whether to scale each polygon to the overall +max height, or whether to scale each max height to the maximum internal distance in the polygon.} + +\item{data_column_top}{Default `NULL`. A string indicating the column in the `sf` object to use +to specify the top of the beveled polygon.} + +\item{data_column_bottom}{Default `NULL`. A string indicating the column in the `sf` object to use +to specify the bottom of the beveled polygon.} + +\item{heightmap}{Default `NULL`. Automatically extracted from the rgl window--only use if auto-extraction +of matrix extent isn't working. A two-dimensional matrix, where each entry in the matrix is the elevation at that point. + All points are assumed to be evenly spaced.} + +\item{scale_data}{Default `1`. If specifying `data_column_top` or `data_column_bottom`, how +much to scale that value when rendering.} + +\item{holes}{Default `0`. If passing in a polygon directly, this specifies which index represents +the holes in the polygon. See the `earcut` function in the `decido` package for more information.} + +\item{alpha}{Default `1`. Transparency of the polygons.} + +\item{lit}{Default `TRUE`. Whether to light the polygons.} + +\item{flat_shading}{Default `FALSE`. Set to `TRUE` to have nicer shading on the 3D polygons. This comes +with the slight penalty of increasing the memory use of the scene due to vertex duplication. This +will not affect software or high quality renders.} + +\item{light_altitude}{Default `c(45, 30)`. Degree(s) from the horizon from which to light the polygons.} + +\item{light_direction}{Default `c(315, 225)`. Degree(s) from north from which to light the polygons.} + +\item{light_intensity}{Default `1`. Intensity of the specular highlight on the polygons.} + +\item{light_relative}{Default `FALSE`. Whether the light direction should be taken relative to the camera, +or absolute.} + +\item{clear_previous}{Default `FALSE`. If `TRUE`, it will clear all existing polygons.} + +\item{...}{Additional arguments to pass to `rgl::triangles3d()`.} +} +\description{ +Adds beveled polygon to the scene using the `raybevel` package. See +the `raybevel::generate_beveled_polygon()` function for more information. +} +\examples{ + +# This function can also create fake "terrain" from polygons by visualizing the distance +# to the nearest edge. +if(run_documentation()) { +#Render the county borders as polygons in Monterey Bay as terrain +montereybay \%>\% + sphere_shade(texture = "desert") \%>\% + add_shadow(ray_shade(montereybay,zscale = 50)) \%>\% + plot_3d(montereybay, water = TRUE, windowsize = 800, watercolor = "dodgerblue", + background = "pink") + +#We will apply a negative buffer to create space between adjacent polygons. You may +#have to call `sf::sf_use_s2(FALSE)` before running this code to get it to run. +sf::sf_use_s2(FALSE) +mont_county_buff = sf::st_simplify(sf::st_buffer(monterey_counties_sf,-0.003), dTolerance=0.001) + +render_beveled_polygons(mont_county_buff, flat_shading = TRUE, angle = 45 , + heightmap = montereybay, bevel_width=2000, + material = "red", + extent = attr(montereybay,"extent"), + bevel_height = 5000, base_height=0, + zscale=200) +render_camera(theta = 0, phi = 90, zoom = 0.65, fov = 0) +render_snapshot() +render_camera(theta=194, phi= 35, zoom = 0.5, fov= 80) +render_snapshot() +} + +# Changing the color of the beveled top: +if(run_documentation()) { +render_beveled_polygons(mont_county_buff, flat_shading = TRUE, angle = 45 , + heightmap = montereybay, bevel_width=2000, + material = "tan", bevel_material = "darkgreen", + extent = attr(montereybay,"extent"), clear_previous=TRUE, + bevel_height = 5000, base_height=0, + zscale=200) +} +# We can create a nice curved surface by passing in a bevel generated with the +# `raybevel::generate_bevel()` function. +if(run_documentation()) { +render_beveled_polygons(mont_county_buff, flat_shading = TRUE, heightmap = montereybay, + bevel = raybevel::generate_bevel("exp",bevel_end = 0.4), + #max_height = 10, scale_all_max = TRUE, set_max_height = TRUE, + material = rayvertex::material_list(diffuse="red", + ambient = "darkred", + diffuse_intensity = 0.2, + ambient_intensity = 0.1), + light_intensity = 1, light_relative = FALSE, + extent = attr(montereybay,"extent"), bevel_height = 5000, + base_height=0, clear_previous = TRUE, + zscale=200) +render_snapshot() +} + +# While the bevels all start at the same point in the above example, +# they rise to different levels due to being scaled by the maximum internal distance +# in the polygon. Setting `scale_all_max = TRUE` ensures the bevels are all scaled to the +# same maximum height (in this case, 3000m above the 5000m bevel start height). +if(run_documentation()) { +render_beveled_polygons(mont_county_buff, flat_shading = TRUE, heightmap = montereybay, + bevel = raybevel::generate_bevel("exp",bevel_end = 0.4), + max_height = 3000, scale_all_max = TRUE, set_max_height = TRUE, + material = rayvertex::material_list(diffuse="red", + ambient = "darkred", + diffuse_intensity = 0.2, + ambient_intensity = 0.1), + light_intensity = 1, light_relative = FALSE, + extent = attr(montereybay,"extent"), bevel_height = 5000, + base_height=0, clear_previous = TRUE, + zscale=200) +render_snapshot() +} + +# Rendering the polygons with `render_highquality()` +if(run_documentation()) { + render_highquality() +} + +# We can scale the size of the polygon to a column in the `sf` object as well: +# raybevel::generate_bevel() function. We can scale this data down using the `scale_data` +# argument. Note that this is applied as well as the `zscale` argument, and that you +# must think carefully about your scales and values if trying to represent a meaningful +# data visualization with this object. +if(run_documentation()) { +render_beveled_polygons(mont_county_buff, flat_shading = TRUE, angle = 45, bevel_width=1000, + data_column_top = "ALAND", scale_data = 1e-5, heightmap = montereybay, + #max_height = 1000, scale_all_max = TRUE, set_max_height = TRUE, + material = rayvertex::material_list(diffuse="red"), + light_intensity = 1, light_relative = FALSE, + extent = attr(montereybay,"extent"), clear_previous = TRUE, + zscale=200) +render_snapshot() +} +} diff --git a/man/render_buildings.Rd b/man/render_buildings.Rd new file mode 100644 index 00000000..9a9347a1 --- /dev/null +++ b/man/render_buildings.Rd @@ -0,0 +1,184 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/render_buildings.R +\name{render_buildings} +\alias{render_buildings} +\title{Render Buildings} +\usage{ +render_buildings( + polygon, + extent, + material = "grey", + roof_material = NA, + angle = 45, + zscale = 1, + scale_data = 1, + relative_heights = TRUE, + heights_relative_to_centroid = FALSE, + roof_height = 1, + base_height = 0, + data_column_top = NULL, + data_column_bottom = NULL, + heightmap = NULL, + holes = 0, + alpha = 1, + lit = TRUE, + flat_shading = FALSE, + light_altitude = c(45, 30), + light_direction = c(315, 225), + light_intensity = 1, + light_relative = FALSE, + clear_previous = FALSE, + ... +) +} +\arguments{ +\item{polygon}{`sf` object, "SpatialPolygon" `sp` object, or xy coordinates +of polygon represented in a way that can be processed by `xy.coords()`. If +xy-coordinate based polygons are open, they will be closed by adding an +edge from the last point to the first.} + +\item{extent}{Either an object representing the spatial extent of the 3D scene +(either from the `raster`, `terra`, `sf`, or `sp` packages), +a length-4 numeric vector specifying `c("xmin", "xmax", "ymin", "ymax")`, or the spatial object (from +the previously aforementioned packages) which will be automatically converted to an extent object.} + +\item{material}{Default `"grey80"`. If a color string, this will specify the color of the sides/base of the building +Alternatively (for more customization), this can be a r`ayvertex::material_list()` object to specify +the full color/appearance/material options for the resulting `ray_mesh` mesh.} + +\item{roof_material}{Default `NA`, defaults to the material specified in `material`. If a color string, this will specify the color of the roof of the building. +Alternatively (for more customization), this can be a `rayvertex::material_list()` object to specify +the full color/appearance/material options for the resulting `ray_mesh` mesh.} + +\item{angle}{Default `45`. Angle of the roof.} + +\item{zscale}{Default `1`. The ratio between the x and y spacing (which are assumed to be equal) and the z axis in the original heightmap.} + +\item{scale_data}{Default `1`. How much to scale the `top`/`bottom` value when rendering. Use +`zscale` to adjust the data to account for `x`/`y` grid spacing, and this argument to scale the data +for visualization.} + +\item{relative_heights}{Default `TRUE`. Whether the heights specified in `roof_height` and `base_height` should +be measured relative to the underlying heightmap.} + +\item{heights_relative_to_centroid}{Default `FALSE`. Whether the heights should be measured in absolute +terms, or relative to the centroid of the polygon.} + +\item{roof_height}{Default `1`. Height from the base of the building to the start of the roof.} + +\item{base_height}{Default `0`. Height of the base of the roof.} + +\item{data_column_top}{Default `NULL`. A string indicating the column in the `sf` object to use +to specify the top of the extruded polygon.} + +\item{data_column_bottom}{Default `NULL`. A string indicating the column in the `sf` object to use +to specify the bottom of the extruded polygon.} + +\item{heightmap}{Default `NULL`. Automatically extracted from the rgl window--only use if auto-extraction +of matrix extent isn't working. A two-dimensional matrix, where each entry in the matrix is the elevation at that point. + All points are assumed to be evenly spaced.} + +\item{holes}{Default `0`. If passing in a polygon directly, this specifies which index represents +the holes in the polygon. See the `earcut` function in the `decido` package for more information.} + +\item{alpha}{Default `1`. Transparency of the polygons.} + +\item{lit}{Default `TRUE`. Whether to light the polygons.} + +\item{flat_shading}{Default `FALSE`. Set to `TRUE` to have nicer shading on the 3D polygons. This comes +with the slight penalty of increasing the memory use of the scene due to vertex duplication. This +will not affect software or high quality renders.} + +\item{light_altitude}{Default `c(45, 30)`. Degree(s) from the horizon from which to light the polygons.} + +\item{light_direction}{Default `c(315, 225)`. Degree(s) from north from which to light the polygons.} + +\item{light_intensity}{Default `1`. Intensity of the specular highlight on the polygons.} + +\item{light_relative}{Default `FALSE`. Whether the light direction should be taken relative to the camera, +or absolute.} + +\item{clear_previous}{Default `FALSE`. If `TRUE`, it will clear all existing polygons.} + +\item{...}{Additional arguments to pass to `rgl::triangles3d()`.} +} +\description{ +Adds 3D polygons with roofs to the current scene, +using latitude/longitude or coordinates in the reference system defined by the extent object. +} +\examples{ +if(run_documentation()) { +# Load and visualize building footprints from Open Street Map +library(osmdata) +library(sf) +library(raster) + +osm_bbox = c(-121.9472, 36.6019, -121.9179, 36.6385) + +#Get buildings from OpenStreetMap +opq(osm_bbox) |> + add_osm_feature("building") |> + osmdata_sf() -> +osm_data + +#Get roads from OpenStreetMap +opq(osm_bbox) |> + add_osm_feature("highway") |> + osmdata_sf() -> +osm_road + +#Get extent +building_polys = osm_data$osm_polygons +osm_dem = elevatr::get_elev_raster(building_polys, z = 11, clip = "bbox") +e = extent(building_polys) + +# Crop DEM, but note that the cropped DEM will have an extent slightly different than what's +# specified in `e`. Save that new extent to `new_e`. +osm_dem |> + crop(e) |> + extent() -> +new_e + +osm_dem |> + crop(e) |> + raster_to_matrix() -> +osm_mat + +#Visualize areas less than one meter as water (approximate tidal range) +osm_mat[osm_mat <= 1] = -2 + +osm_mat \%>\% + rayimage::render_resized(mag=4) |> + sphere_shade(texture = "desert") |> + add_overlay(generate_polygon_overlay(building_polys, extent = new_e, + heightmap = osm_mat, + linewidth = 6, + resolution_multiply = 50), rescale_original = TRUE) |> + add_overlay(generate_line_overlay(osm_road$osm_lines, extent = new_e, + heightmap = osm_mat, + linewidth = 6, + resolution_multiply = 50), rescale_original = TRUE) |> + plot_3d(osm_mat, water = TRUE, windowsize = 800, watercolor = "dodgerblue", + zscale = 10, + background = "pink") + +#Render buildings +render_buildings(building_polys, flat_shading = TRUE, + angle = 30 , heightmap = osm_mat, + material = "white", roof_material = "white", + extent = new_e, roof_height = 3, base_height = 0, + zscale=10) +render_camera(theta=220, phi=22, zoom=0.45, fov=0) +render_snapshot() +} + +if(run_documentation()) { +#Zoom in to show roof details and render with render_highquality() +render_camera(fov=110) +render_highquality(camera_location = c(18.22, 0.57, -50.83), + camera_lookat = c(20.88, -2.83, -38.87), + focal_distance = 13, + lightdirection = 45) + +} +} diff --git a/man/render_contours.Rd b/man/render_contours.Rd index 63fb8f16..2b47cf7d 100644 --- a/man/render_contours.Rd +++ b/man/render_contours.Rd @@ -81,7 +81,7 @@ render_snapshot() if(run_documentation()) { #Render using `render_highquality()` for a neon light effect -render_highquality(light = FALSE, smooth_line = TRUE, +render_highquality(light = FALSE, line_radius = 0.1, sample_method="sobol_blue", path_material = rayrender::light, ground_size = 0, path_material_args = list(importance_sample = FALSE, diff --git a/man/render_obj.Rd b/man/render_obj.Rd index 5e463b48..c46b01bd 100644 --- a/man/render_obj.Rd +++ b/man/render_obj.Rd @@ -23,6 +23,11 @@ render_obj( scale = c(1, 1, 1), clear_previous = FALSE, baseshape = "rectangle", + lit = FALSE, + light_altitude = c(45, 30), + light_direction = c(315, 135), + light_intensity = 0.3, + light_relative = FALSE, rgl_tag = "", ... ) @@ -58,7 +63,7 @@ All points are assumed to be evenly spaced.} \item{color}{Default `black`. Color of the 3D model, if `load_material = FALSE`.} -\item{offset}{Default `5`. Offset of the track from the surface, if `altitude = NULL`.} +\item{offset}{Default `5`. Offset of the model from the surface, if `altitude = NULL`.} \item{obj_zscale}{Default `FALSE`. Whether to scale the size of the OBJ by zscale to have it match the size of the map. If zscale is very big, this will make the model very small.} @@ -79,6 +84,17 @@ equal the length of `lat`/`long`).} \item{baseshape}{Default `rectangle`. Shape of the base. Options are `c("rectangle","circle","hex")`.} +\item{lit}{Default `TRUE`. Whether to light the polygons.} + +\item{light_altitude}{Default `c(45, 60)`. Degree(s) from the horizon from which to light the polygons.} + +\item{light_direction}{Default `c(45, 60)`. Degree(s) from north from which to light the polygons.} + +\item{light_intensity}{Default `0.3`. Intensity of the specular highlight on the polygons.} + +\item{light_relative}{Default `FALSE`. Whether the light direction should be taken relative to the camera, +or absolute.} + \item{rgl_tag}{Default `""`. Tag to add to the rgl scene id, will be prefixed by `"obj"`} \item{...}{Additional arguments to pass to `rgl::triangles3d()`.} diff --git a/man/render_path.Rd b/man/render_path.Rd index 1cc398dd..622645ab 100644 --- a/man/render_path.Rd +++ b/man/render_path.Rd @@ -157,7 +157,7 @@ render_path(clear_previous=TRUE) t = seq(0,2*pi,length.out=1000) circle_coords_lat = moss_landing_coord[1] + 0.5 * t/8 * sin(t*6) circle_coords_long = moss_landing_coord[2] + 0.5 * t/8 * cos(t*6) -render_path(extent = attr(montereybay,"extent"), heightmap = montereybay, +render_path(extent = attr(montereybay,"extent"), heightmap = montereybay, lat = unlist(circle_coords_lat), long = unlist(circle_coords_long), zscale=50, color="red", antialias=TRUE,offset=100, linewidth=5) render_camera(theta = 160, phi=33, zoom=0.4, fov=55) @@ -165,8 +165,10 @@ render_snapshot() } if(run_documentation()) { -#And all of these work with `render_highquality()` +#And all of these work with `render_highquality()`. Here, I set `use_extruded_paths = TRUE` +#to get thick continuous paths. render_highquality(clamp_value=10, line_radius=3, min_variance = 0, + use_extruded_paths = TRUE, sample_method = "sobol_blue", samples = 128) } if(run_documentation()) { @@ -174,7 +176,7 @@ if(run_documentation()) { #`point_material_args` arguments in `render_highquality()` render_highquality(clamp_value=10, line_radius=3, min_variance = 0, sample_method = "sobol_blue", samples = 128, - path_material = rayrender::glossy, + path_material = rayrender::glossy, use_extruded_paths = TRUE, path_material_args = list(gloss = 0.5, reflectance = 0.2)) } diff --git a/man/render_polygons.Rd b/man/render_polygons.Rd index 5b9b0ca1..f076b595 100644 --- a/man/render_polygons.Rd +++ b/man/render_polygons.Rd @@ -21,6 +21,7 @@ render_polygons( light_altitude = c(45, 30), light_direction = c(315, 135), light_intensity = 0.3, + light_relative = FALSE, clear_previous = FALSE ) } @@ -72,6 +73,9 @@ the holes in the polygon. See the `earcut` function in the `decido` package for \item{light_intensity}{Default `0.3`. Intensity of the specular highlight on the polygons.} +\item{light_relative}{Default `FALSE`. Whether the light direction should be taken relative to the camera, +or absolute.} + \item{clear_previous}{Default `FALSE`. If `TRUE`, it will clear all existing polygons.} } \description{ @@ -94,7 +98,7 @@ mont_county_buff = sf::st_simplify(sf::st_buffer(monterey_counties_sf,-0.003), d render_polygons(mont_county_buff, extent = attr(montereybay,"extent"), top = 10, - parallel = TRUE) + parallel = FALSE) render_snapshot() } if(run_documentation()) { @@ -103,7 +107,7 @@ if(run_documentation()) { render_camera(theta=-60, phi=20, zoom = 0.85, fov=0) render_polygons(mont_county_buff, extent = attr(montereybay,"extent"), bottom = 190, top=200, - parallel=TRUE,clear_previous=TRUE) + parallel=FALSE,clear_previous=TRUE) render_snapshot() } if(run_documentation()) { @@ -113,7 +117,7 @@ render_camera(theta=-60, phi=60, zoom = 0.85, fov=30) render_polygons(mont_county_buff, extent = attr(montereybay, "extent"), data_column_top = "ALAND", scale_data = 300/(2.6E9), color = "chartreuse4", - parallel = TRUE, clear_previous = TRUE) + clear_previous = TRUE) render_snapshot() } if(run_documentation()) { diff --git a/man/render_raymesh.Rd b/man/render_raymesh.Rd new file mode 100644 index 00000000..7bb379d5 --- /dev/null +++ b/man/render_raymesh.Rd @@ -0,0 +1,117 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/render_raymesh.R +\name{render_raymesh} +\alias{render_raymesh} +\title{Render Raymesh} +\usage{ +render_raymesh( + raymesh, + extent = NULL, + lat = NULL, + long = NULL, + altitude = NULL, + xyz = NULL, + zscale = 1, + heightmap = NULL, + load_normals = TRUE, + change_material = TRUE, + color = "grey50", + offset = 0, + obj_zscale = FALSE, + swap_yz = NULL, + angle = c(0, 0, 0), + scale = c(1, 1, 1), + clear_previous = FALSE, + baseshape = "rectangle", + flat_shading = FALSE, + lit = FALSE, + light_altitude = c(45, 30), + light_direction = c(315, 135), + light_intensity = 1, + light_relative = FALSE, + rgl_tag = "", + ... +) +} +\arguments{ +\item{raymesh}{`raymesh` object (see the rayvertex package for a description)} + +\item{extent}{Either an object representing the spatial extent of the scene +(either from the `raster`, `terra`, `sf`, or `sp` packages), +a length-4 numeric vector specifying `c("xmin", "xmax","ymin","ymax")`, or the spatial object (from +the previously aforementioned packages) which will be automatically converted to an extent object.} + +\item{lat}{Vector of latitudes (or other coordinate in the same coordinate reference system as extent).} + +\item{long}{Vector of longitudes (or other coordinate in the same coordinate reference system as extent).} + +\item{altitude}{Default `NULL`. Elevation of each point, in units of the elevation matrix (scaled by `zscale`). +If left `NULL`, this will be just the elevation value at ths surface, offset by `offset`. If a single value, +the OBJ will be rendered at that altitude.} + +\item{xyz}{Default `NULL`, ignored. A 3 column numeric matrix, with each row specifying the x/y/z +coordinates of the OBJ model(s). Overrides lat/long/altitude and ignores extent to plot the OBJ in raw rgl coordinates.} + +\item{zscale}{Default `1`. The ratio between the x and y spacing (which are assumed to be equal) and the z axis in the original heightmap.} + +\item{heightmap}{Default `NULL`. Automatically extracted from the rgl window--only use if auto-extraction +of matrix extent isn't working. A two-dimensional matrix, where each entry in the matrix is the elevation at that point. +All points are assumed to be evenly spaced.} + +\item{load_normals}{Default `TRUE`. Whether to load normals for the 3D model.} + +\item{change_material}{Default `TRUE`. Whether to change the raymesh material (to customize the color).} + +\item{color}{Default `black`. Color of the 3D model, if `load_material = FALSE`.} + +\item{offset}{Default `5`. Offset of the track from the surface, if `altitude = NULL`.} + +\item{obj_zscale}{Default `FALSE`. Whether to scale the size of the OBJ by zscale to have it match +the size of the map. If zscale is very big, this will make the model very small.} + +\item{swap_yz}{Default `NULL`, defaults to `FALSE` unless plotting raw coordinates (no lat or long passed). +Whether to swap and Y and Z axes. (Y axis is vertical in +rayshader coordinates, but data is often provided with Z being vertical).} + +\item{angle}{Default `c(0,0,0)`. Angle of rotation around the x, y, and z axes. If this is a matrix or list, +each row (or list entry) specifies the rotation of the nth model specified (number of rows/length of list must +equal the length of `lat`/`long`).} + +\item{scale}{Default `c(1,1,1)`. Amount to scale the 3D model in the x, y, and z axes. If this is a matrix or list, +each row (or list entry) specifies the scale of the nth model specified (number of rows/length of list must +equal the length of `lat`/`long`).} + +\item{clear_previous}{Default `FALSE`. If `TRUE`, it will clear all existing points.} + +\item{baseshape}{Default `rectangle`. Shape of the base. Options are `c("rectangle","circle","hex")`.} + +\item{flat_shading}{Default `FALSE`. If `TRUE`, this will use rgl's flat shading.} + +\item{lit}{Default `TRUE`. Whether to light the polygons.} + +\item{light_altitude}{Default `c(45, 60)`. Degree(s) from the horizon from which to light the polygons.} + +\item{light_direction}{Default `c(45, 60)`. Degree(s) from north from which to light the polygons.} + +\item{light_intensity}{Default `0.3`. Intensity of the specular highlight on the polygons.} + +\item{light_relative}{Default `FALSE`. Whether the light direction should be taken relative to the camera, +or absolute.} + +\item{rgl_tag}{Default `""`. Tag to add to the rgl scene id, will be prefixed by `"objraymsh"`} + +\item{...}{Additional arguments to pass to `rgl::triangles3d()`.} +} +\description{ +Adds 3D raymesh model to the current scene, using latitude/longitude or coordinates in the reference +system defined by the extent object. If no altitude is provided, the raymesh will be elevated a constant offset +above the heightmap. If the raymesh goes off the edge, the raymesh will be filtered out. + +If no latitudes or longitudes are passed in, the raymesh will be plotted in the coordinate system set by the user-specified +`extent` argument as-is. Use this alongside `save_multipolygonz_to_obj()` to plot 3D polygons imported from geospatial sources +in the proper location (but for ease of use, use `render_multipolygonz()` to plot this data directly). +} +\examples{ +if(run_documentation()) { +} +} diff --git a/man/render_snapshot.Rd b/man/render_snapshot.Rd index ce445f6f..6bcd4a6e 100644 --- a/man/render_snapshot.Rd +++ b/man/render_snapshot.Rd @@ -21,7 +21,6 @@ render_snapshot( vignette_radius = 1.3, instant_capture = interactive(), bring_to_front = FALSE, - keep_user_par = FALSE, webshot = FALSE, width = NULL, height = NULL, @@ -87,9 +86,6 @@ before taking the snapshot. This can help stop prevent rendering issues when run \item{bring_to_front}{Default `FALSE`. Whether to bring the window to the front when taking the snapshot.} -\item{keep_user_par}{Default `TRUE`. Whether to keep the user's `par()` settings. Set to `FALSE` if you -want to set up a multi-pane plot (e.g. set `par(mfrow)`).} - \item{webshot}{Default `FALSE`. Set to `TRUE` to have rgl use the `webshot2` package to take images, which can be used when `rgl.useNULL = TRUE`.} @@ -192,8 +188,8 @@ render_snapshot(software_render = TRUE) if(run_documentation()) { #Now with shadow mapped shadows, calculated in rayvertex render_snapshot(rayvertex_lighting = TRUE, - rayvertex_lights = rayvertex::directional_light(intensity = 2.5, - direction = c(-1, 0.8, -1)), + rayvertex_lights = rayvertex::directional_light(intensity = 1.2, + direction = c(-1, 1, -1)), rayvertex_shadow_map = TRUE, software_render = TRUE) } } diff --git a/man/render_snapshot_software.Rd b/man/render_snapshot_software.Rd index a2c7d189..635e0424 100644 --- a/man/render_snapshot_software.Rd +++ b/man/render_snapshot_software.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/render_snapshot_software.R \name{render_snapshot_software} \alias{render_snapshot_software} -\title{Render High Quality} +\title{Render Software Snapshot} \usage{ render_snapshot_software( filename, @@ -37,11 +37,9 @@ be derived from the rgl window.} \item{...}{Additional parameters to pass to `rayvertex::rasterize_scene()`} -\item{cache_filename}{Name of temporary filename to store OBJ file, if the user does not want to rewrite the file each time.} - \item{ground_size}{Default `10000`. The width of the plane representing the ground.} } \description{ -Render High Quality +Render Software Snapshot } \keyword{internal} diff --git a/man/render_tree.Rd b/man/render_tree.Rd index 113d0732..bb33e9d3 100644 --- a/man/render_tree.Rd +++ b/man/render_tree.Rd @@ -24,6 +24,7 @@ render_tree( min_height = 0, max_height = Inf, zscale = 1, + lit = TRUE, heightmap = NULL, baseshape = "rectangle", angle = c(0, 0, 0), @@ -88,6 +89,8 @@ above that height.} \item{zscale}{Default `1`. The ratio between the x and y spacing (which are assumed to be equal) and the z axis in the original heightmap.} +\item{lit}{Default `TRUE`. Whether to apply lighting to the tree.} + \item{heightmap}{Default `NULL`. Automatically extracted from the rgl window--only use if auto-extraction of matrix extent isn't working. A two-dimensional matrix, where each entry in the matrix is the elevation at that point. All points are assumed to be evenly spaced.} @@ -126,7 +129,7 @@ circle_coords_lat = moss_landing_coord[1] + 0.3 * sin(t) circle_coords_long = moss_landing_coord[2] + 0.3 * cos(t) render_tree(extent = attr(montereybay,"extent"), heightmap = montereybay, - tree_zscale = FALSE, tree_height = 30, + tree_zscale = FALSE, tree_height = 30, lit = TRUE, lat = unlist(circle_coords_lat), long = unlist(circle_coords_long), zscale=50) render_snapshot() } diff --git a/man/transform_points_custom_crs.Rd b/man/transform_points_custom_crs.Rd new file mode 100644 index 00000000..97a0c13c --- /dev/null +++ b/man/transform_points_custom_crs.Rd @@ -0,0 +1,12 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/transform_sf_to_raycoords.R +\name{transform_points_custom_crs} +\alias{transform_points_custom_crs} +\title{Transform Polygon into Raycoords} +\usage{ +transform_points_custom_crs(sf_object, orig_extent, new_extent) +} +\description{ +Transform Polygon into Raycoords +} +\keyword{internal} diff --git a/man/transform_points_into_raycoords.Rd b/man/transform_points_into_raycoords.Rd new file mode 100644 index 00000000..bc929a9f --- /dev/null +++ b/man/transform_points_into_raycoords.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/transform_sf_to_raycoords.R +\name{transform_points_into_raycoords} +\alias{transform_points_into_raycoords} +\title{Transform Points into Raycoords} +\usage{ +transform_points_into_raycoords( + points, + heightmap = NULL, + e = NULL, + top = NULL, + bottom = NULL +) +} +\description{ +Transform Points into Raycoords +} +\keyword{internal} diff --git a/man/transform_polygon_custom_crs.Rd b/man/transform_polygon_custom_crs.Rd new file mode 100644 index 00000000..767c7cb1 --- /dev/null +++ b/man/transform_polygon_custom_crs.Rd @@ -0,0 +1,12 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/transform_sf_to_raycoords.R +\name{transform_polygon_custom_crs} +\alias{transform_polygon_custom_crs} +\title{Transform Polygon into Raycoords} +\usage{ +transform_polygon_custom_crs(sf_object, orig_extent, new_extent) +} +\description{ +Transform Polygon into Raycoords +} +\keyword{internal} diff --git a/man/transform_polygon_into_raycoords.Rd b/man/transform_polygon_into_raycoords.Rd new file mode 100644 index 00000000..a2ad7b7c --- /dev/null +++ b/man/transform_polygon_into_raycoords.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/transform_sf_to_raycoords.R +\name{transform_polygon_into_raycoords} +\alias{transform_polygon_into_raycoords} +\title{Transform Polygon into Raycoords} +\usage{ +transform_polygon_into_raycoords( + polygon, + heightmap = NULL, + e = NULL, + top = NULL, + bottom = NULL +) +} +\description{ +Transform Polygon into Raycoords +} +\keyword{internal} diff --git a/src/make_base_cpp.cpp b/src/make_base_cpp.cpp index 98d3f8cc..3211a2c9 100644 --- a/src/make_base_cpp.cpp +++ b/src/make_base_cpp.cpp @@ -366,7 +366,14 @@ List make_waterlines_cpp(NumericMatrix& heightmap, drawing = false; if((heightmap(j,i) > waterdepth || i == cols-1 ) && !na_matrix(j,i)) { if(i != cols-1) { - endcoord = -(double)i - (waterdepth - heightmap(j,i-1))/(heightmap(j,i)-heightmap(j,i-1)); + double diff = heightmap(j,i)-heightmap(j,i-1); + double adjustment_factor; + if(diff == 0) { + adjustment_factor = 0; + } else { + adjustment_factor = (waterdepth - heightmap(j,i-1))/diff; + } + endcoord = -(double)i - adjustment_factor; } else { endcoord = -cols; } @@ -381,10 +388,19 @@ List make_waterlines_cpp(NumericMatrix& heightmap, } } if(!drawing && (j == 0 || j == rows - 1)) { - if((heightmap(j,i) < waterdepth) || (na_matrix(j,i-offsetside) && heightmap(j,i+offsetside2) < waterdepth)) { + if(((heightmap(j,i) < waterdepth) || + (na_matrix(j,i-offsetside) && heightmap(j,i+offsetside2) < waterdepth)) && + ((j == 0 && !na_matrix(1, i)) || (j == rows - 1 && !na_matrix(rows - 2, i)))) { if(!na_matrix(j,i-offsetside)) { if(i != 0) { - startcoord = ((double)i-1) + (waterdepth - heightmap(j,i-1))/(heightmap(j,i)-heightmap(j,i-1)); + double diff = heightmap(j,i)-heightmap(j,i-1); + double adjustment_factor; + if(diff == 0) { + adjustment_factor = 0; + } else { + adjustment_factor = (waterdepth - heightmap(j,i-1))/diff; + } + startcoord = ((double)i-1) + adjustment_factor; } else { startcoord = 0; } @@ -476,7 +492,14 @@ List make_waterlines_cpp(NumericMatrix& heightmap, drawing = false; if((heightmap(i,j) > waterdepth || i == rows-1) && !na_matrix(i,j)) { if(i != rows-1) { - endcoord = (double)i + (waterdepth - heightmap(i-1,j))/(heightmap(i,j)-heightmap(i-1,j)); + double diff = heightmap(i,j)-heightmap(i-1,j); + double adjustment_factor; + if(diff == 0) { + adjustment_factor = 0; + } else { + adjustment_factor = (waterdepth - heightmap(i-1,j))/diff; + } + endcoord = (double)i + adjustment_factor; } else { endcoord = rows; } @@ -491,10 +514,19 @@ List make_waterlines_cpp(NumericMatrix& heightmap, } } if(!drawing && (j == 0 || j == cols - 1)) { - if((heightmap(i,j) < waterdepth || (na_matrix(i-offsetside,j) && heightmap(i+offsetside2,j) < waterdepth))) { + if((heightmap(i,j) < waterdepth || + (na_matrix(i - offsetside,j) && heightmap(i + offsetside2,j) < waterdepth)) && + ((j == 0 && !na_matrix(i, 1)) || (j == cols - 1 && !na_matrix(i, cols - 2)))) { if(!na_matrix(i-offsetside,j)) { if(i != 0) { - startcoord = ((double)i-1) + (waterdepth - heightmap(i-1,j))/(heightmap(i,j)-heightmap(i-1,j)); + double diff = heightmap(i,j)-heightmap(i-1,j); + double adjustment_factor; + if(diff == 0) { + adjustment_factor = 0; + } else { + adjustment_factor = (waterdepth - heightmap(i-1,j))/diff; + } + startcoord = ((double)i-1) + adjustment_factor; } else { startcoord = 0; } @@ -539,8 +571,8 @@ List make_waterlines_cpp(NumericMatrix& heightmap, //The matrix is not NA in the next entry AND //the current entry is not NA AND //the left OR right OR left front OR right front is NA - if((heightmap(i,j) < waterdepth || (heightmap(i,j) >= waterdepth && heightmap(i+offsetside2,j) < waterdepth)) && - !na_matrix(i+offsetside2,j) && + if((heightmap(i,j) < waterdepth || (heightmap(i,j) >= waterdepth && heightmap(i+offsetside2,j) < waterdepth) ) && //Check depths + !na_matrix(i+offsetside2,j) && //Not NA in the next entry (!na_matrix(i,j) && (na_matrix(i,j-offset) || na_matrix(i,j+offset2) || na_matrix(i+offsetside2,j-offset) || na_matrix(i+offsetside2,j+offset2)))) { if(i != 0) { adjust = (waterdepth - heightmap(i-1,j))/(heightmap(i,j)-heightmap(i-1,j));