|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| make_vertex_key <- function(x, y) {
|
|
|
|
|
| x_rounded <- round(x, 1)
|
| y_rounded <- round(y, 1)
|
|
|
|
|
| sprintf("%.1f,%.1f", x_rounded + 0.0, y_rounded + 0.0)
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| generate_hex_edge_map <- function(rings, seed, diameter, tabsize = 6, jitter = 5,
|
| do_warp = FALSE, do_trunc = FALSE,
|
| do_circular_border = FALSE,
|
| min_tab_size = NULL, max_tab_size = NULL) {
|
| num_pieces <- 3 * rings * (rings - 1) + 1
|
|
|
|
|
|
|
| piece_radius <- diameter / (4 * rings - 2)
|
| tab_params <- list(tabsize = tabsize, jitter = jitter)
|
|
|
|
|
| piece_vertices_original <- list()
|
| base_offset <- 0
|
|
|
| for (piece_id in 1:num_pieces) {
|
| axial_coords <- map_piece_id_to_axial(piece_id, rings)
|
| hex_size <- piece_radius
|
|
|
| cart_coords <- axial_to_cartesian(
|
| q = axial_coords$q,
|
| r = axial_coords$r,
|
| hex_size = hex_size
|
| )
|
| center_x <- cart_coords$x
|
| center_y <- cart_coords$y
|
|
|
| vertices <- list()
|
| for (i in 0:5) {
|
| vertex_angle <- i * pi / 3 + base_offset
|
| vx <- center_x + piece_radius * cos(vertex_angle)
|
| vy <- center_y + piece_radius * sin(vertex_angle)
|
| vertices[[i + 1]] <- c(vx, vy)
|
| }
|
| piece_vertices_original[[piece_id]] <- vertices
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| piece_vertices <- piece_vertices_original
|
|
|
|
|
|
|
| boundary_edge_keys <- c()
|
|
|
|
|
| vertex_sharing <- list()
|
|
|
| for (piece_id in 1:num_pieces) {
|
| for (i in 1:6) {
|
| v <- piece_vertices_original[[piece_id]][[i]]
|
| v_key <- make_vertex_key(v[1], v[2])
|
|
|
| if (is.null(vertex_sharing[[v_key]])) {
|
| vertex_sharing[[v_key]] <- list(pieces = c(), coords = v)
|
| }
|
| vertex_sharing[[v_key]]$pieces <- c(vertex_sharing[[v_key]]$pieces, piece_id)
|
| }
|
| }
|
|
|
|
|
| boundary_vertex_keys <- c()
|
| max_boundary_dist <- 0
|
|
|
| for (v_key in names(vertex_sharing)) {
|
| if (length(unique(vertex_sharing[[v_key]]$pieces)) < 3) {
|
| boundary_vertex_keys <- c(boundary_vertex_keys, v_key)
|
| v <- vertex_sharing[[v_key]]$coords
|
| dist <- sqrt(v[1]^2 + v[2]^2)
|
| max_boundary_dist <- max(max_boundary_dist, dist)
|
| }
|
| }
|
|
|
|
|
|
|
| for (piece_id in 1:num_pieces) {
|
| for (side in 0:5) {
|
| v1 <- piece_vertices_original[[piece_id]][[side + 1]]
|
| v2 <- piece_vertices_original[[piece_id]][[(side + 1) %% 6 + 1]]
|
| v1_key <- make_vertex_key(v1[1], v1[2])
|
| v2_key <- make_vertex_key(v2[1], v2[2])
|
|
|
|
|
| if (v1_key %in% boundary_vertex_keys && v2_key %in% boundary_vertex_keys) {
|
|
|
| v1_pieces <- unique(vertex_sharing[[v1_key]]$pieces)
|
| v2_pieces <- unique(vertex_sharing[[v2_key]]$pieces)
|
| shared_pieces <- intersect(v1_pieces, v2_pieces)
|
|
|
|
|
| if (length(shared_pieces) == 1) {
|
| boundary_edge_keys <- c(boundary_edge_keys, sprintf("%d-%d", piece_id, side))
|
| }
|
| }
|
| }
|
| }
|
|
|
|
|
| circle_radius <- NULL
|
|
|
|
|
| if (do_warp || do_trunc) {
|
|
|
|
|
|
|
| if (do_warp) {
|
| all_transformed <- list()
|
|
|
| for (v_key in names(vertex_sharing)) {
|
| v <- vertex_sharing[[v_key]]$coords
|
|
|
| transformed <- apply_hex_warp(v[1], v[2])
|
| all_transformed[[v_key]] <- c(transformed$x, transformed$y)
|
| }
|
|
|
|
|
| for (piece_id in 1:num_pieces) {
|
| for (i in 1:6) {
|
| v <- piece_vertices_original[[piece_id]][[i]]
|
| v_key <- make_vertex_key(v[1], v[2])
|
|
|
| if (!is.null(all_transformed[[v_key]])) {
|
| piece_vertices[[piece_id]][[i]] <- all_transformed[[v_key]]
|
| }
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| if (do_trunc) {
|
|
|
|
|
|
|
|
|
| circle_radius <- diameter / 2
|
| log_info("Truncation enabled - projecting boundary to radius {round(circle_radius, 2)}")
|
|
|
|
|
| for (piece_id in 1:num_pieces) {
|
| for (i in 1:6) {
|
| orig_v <- piece_vertices_original[[piece_id]][[i]]
|
| orig_key <- make_vertex_key(orig_v[1], orig_v[2])
|
|
|
| if (orig_key %in% boundary_vertex_keys) {
|
|
|
| current_v <- piece_vertices[[piece_id]][[i]]
|
| current_dist <- sqrt(current_v[1]^2 + current_v[2]^2)
|
|
|
| if (current_dist > 0) {
|
|
|
| scale <- circle_radius / current_dist
|
| piece_vertices[[piece_id]][[i]] <- c(
|
| current_v[1] * scale,
|
| current_v[2] * scale
|
| )
|
| }
|
| }
|
| }
|
| }
|
| } else {
|
|
|
|
|
|
|
|
|
|
|
| }
|
| }
|
|
|
|
|
|
|
| if (do_trunc && !do_warp) {
|
|
|
| for (v_key in boundary_vertex_keys) {
|
| v <- vertex_sharing[[v_key]]$coords
|
| transformed <- apply_hex_trunc(v[1], v[2], max_boundary_dist)
|
|
|
|
|
| for (piece_id in 1:num_pieces) {
|
| for (i in 1:6) {
|
| orig_v <- piece_vertices_original[[piece_id]][[i]]
|
| orig_key <- make_vertex_key(orig_v[1], orig_v[2])
|
|
|
| if (orig_key == v_key) {
|
| piece_vertices[[piece_id]][[i]] <- c(transformed$x, transformed$y)
|
| }
|
| }
|
| }
|
| }
|
|
|
| log_info("Hexagonal truncation enabled - boundary at radius {round(max_boundary_dist, 2)}")
|
| }
|
|
|
|
|
| if (!do_warp && !do_trunc) {
|
| log_info("No transformation (zigzag boundary)")
|
| }
|
| }
|
|
|
|
|
| edge_map <- list()
|
| edge_counter <- 0
|
|
|
|
|
| piece_edge_map <- list()
|
|
|
| for (piece_id in 1:num_pieces) {
|
| vertices <- piece_vertices[[piece_id]]
|
|
|
| for (side in 0:5) {
|
|
|
| v1 <- vertices[[side + 1]]
|
| v2 <- vertices[[(side + 1) %% 6 + 1]]
|
|
|
|
|
|
|
|
|
| edge_key_check <- sprintf("%d-%d", piece_id, side)
|
| is_boundary_edge <- edge_key_check %in% boundary_edge_keys
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| neighbor_id <- NA
|
| neighbor_side <- NA
|
|
|
| if (!is_boundary_edge) {
|
| for (test_id in 1:num_pieces) {
|
| if (test_id == piece_id) next
|
|
|
| test_vertices <- piece_vertices[[test_id]]
|
|
|
|
|
| for (test_side in 0:5) {
|
| test_v1 <- test_vertices[[test_side + 1]]
|
| test_v2 <- test_vertices[[(test_side + 1) %% 6 + 1]]
|
|
|
|
|
| tol <- 0.01
|
| if ((all(abs(v1 - test_v2) < tol) && all(abs(v2 - test_v1) < tol)) ||
|
| (all(abs(v1 - test_v1) < tol) && all(abs(v2 - test_v2) < tol))) {
|
| neighbor_id <- test_id
|
| neighbor_side <- test_side
|
| break
|
| }
|
| }
|
| if (!is.na(neighbor_id)) break
|
| }
|
| }
|
|
|
| if (is_boundary_edge || is.na(neighbor_id)) {
|
|
|
| edge_key <- sprintf("%d-%d", piece_id, side)
|
|
|
| if (do_circular_border && do_warp && !is.null(circle_radius)) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| piece_edge_map[[edge_key]] <- list(
|
| type = "border",
|
| forward = sprintf("A %.2f %.2f 0 0 1 %.2f %.2f",
|
| circle_radius, circle_radius, v2[1], v2[2]),
|
| reverse = sprintf("A %.2f %.2f 0 0 0 %.2f %.2f",
|
| circle_radius, circle_radius, v1[1], v1[2]),
|
| start = v1,
|
| end = v2,
|
| is_forward = TRUE,
|
| warped = TRUE,
|
| circular_border = TRUE
|
| )
|
| } else {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| piece_edge_map[[edge_key]] <- list(
|
| type = "border",
|
| forward = sprintf("L %.2f %.2f", v2[1], v2[2]),
|
| reverse = sprintf("L %.2f %.2f", v1[1], v1[2]),
|
| start = v1,
|
| end = v2,
|
| is_forward = TRUE,
|
| warped = do_warp
|
| )
|
| }
|
| } else {
|
|
|
| pieces <- sort(c(piece_id, neighbor_id))
|
| unique_edge_key <- sprintf("E%d-%d", pieces[1], pieces[2])
|
|
|
| if (is.null(edge_map[[unique_edge_key]])) {
|
|
|
| edge_counter <- edge_counter + 1
|
|
|
|
|
| edge_seed <- seed + pieces[1] * 1000 + pieces[2]
|
|
|
|
|
| bezier <- generate_hex_bezier_edge(
|
| v1 = v1,
|
| v2 = v2,
|
| seed = edge_seed,
|
| edge_id = edge_counter,
|
| tab_params = tab_params,
|
| min_tab_size = min_tab_size,
|
| max_tab_size = max_tab_size
|
| )
|
|
|
|
|
| edge_map[[unique_edge_key]] <- list(
|
| id = edge_counter,
|
| piece1 = piece_id,
|
| piece2 = neighbor_id,
|
| forward = bezier$forward,
|
| reverse = bezier$reverse,
|
| start = v1,
|
| end = v2
|
| )
|
|
|
|
|
| piece_key <- sprintf("%d-%d", piece_id, side)
|
| piece_edge_map[[piece_key]] <- list(
|
| type = "internal",
|
| edge_key = unique_edge_key,
|
| is_forward = TRUE,
|
| forward = bezier$forward,
|
| reverse = bezier$reverse,
|
| start = v1,
|
| end = v2
|
| )
|
| } else {
|
|
|
| edge <- edge_map[[unique_edge_key]]
|
| piece_key <- sprintf("%d-%d", piece_id, side)
|
| piece_edge_map[[piece_key]] <- list(
|
| type = "internal",
|
| edge_key = unique_edge_key,
|
| is_forward = FALSE,
|
| forward = edge$reverse,
|
| reverse = edge$forward,
|
| start = v1,
|
| end = v2
|
| )
|
| }
|
| }
|
| }
|
| }
|
|
|
| return(list(
|
| edge_map = edge_map,
|
| piece_edge_map = piece_edge_map,
|
| num_edges = edge_counter
|
| ))
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| generate_hex_pieces_with_edge_map <- function(rings, seed, diameter = 240,
|
| tabsize = 6, jitter = 5,
|
| separated = TRUE,
|
| base_spacing = NULL,
|
| separation_factor = 1.0,
|
| do_warp = FALSE,
|
| do_trunc = FALSE,
|
| do_circular_border = FALSE,
|
| min_tab_size = NULL,
|
| max_tab_size = NULL) {
|
|
|
| edge_data <- generate_hex_edge_map(rings, seed, diameter, tabsize, jitter,
|
| do_warp, do_trunc, do_circular_border,
|
| min_tab_size, max_tab_size)
|
|
|
|
|
| if (separated && is.null(base_spacing)) {
|
| base_spacing <- diameter / (rings * 2)
|
| }
|
|
|
|
|
| num_pieces <- 3 * rings * (rings - 1) + 1
|
| pieces <- list()
|
|
|
| for (piece_id in 1:num_pieces) {
|
|
|
| ring_info <- map_piece_id_to_ring(piece_id, rings)
|
|
|
|
|
|
|
|
|
|
|
|
|
| piece_radius <- diameter / (4 * rings - 2)
|
| compact_pos <- calculate_hex_piece_position(
|
| piece_id = piece_id,
|
| rings = rings,
|
| piece_radius = piece_radius,
|
| separation_factor = 1.0
|
| )
|
|
|
| if (separated) {
|
|
|
|
|
|
|
|
|
| separated_pos <- calculate_hex_piece_position(
|
| piece_id = piece_id,
|
| rings = rings,
|
| piece_radius = piece_radius,
|
| separation_factor = separation_factor
|
| )
|
|
|
| offset <- list(
|
| x = separated_pos$x - compact_pos$x,
|
| y = separated_pos$y - compact_pos$y
|
| )
|
|
|
| position <- offset
|
| absolute_center <- separated_pos
|
| } else {
|
|
|
| position <- list(x = 0, y = 0)
|
| absolute_center <- compact_pos
|
| }
|
|
|
|
|
| path_parts <- c()
|
|
|
|
|
| first_edge_key <- sprintf("%d-0", piece_id)
|
| first_edge <- edge_data$piece_edge_map[[first_edge_key]]
|
| path_parts <- c(sprintf("M %.2f %.2f",
|
| position$x + first_edge$start[1],
|
| position$y + first_edge$start[2]))
|
|
|
|
|
|
|
| offset_path_coords <- function(path_segment, offset_x, offset_y) {
|
|
|
|
|
| tokens <- unlist(strsplit(path_segment, "(?=[CLMA])", perl = TRUE))
|
| tokens <- tokens[nchar(trimws(tokens)) > 0]
|
|
|
| result_parts <- c()
|
|
|
| for (token in tokens) {
|
| token <- trimws(token)
|
| if (nchar(token) == 0) next
|
|
|
| cmd <- substr(token, 1, 1)
|
| rest <- substr(token, 2, nchar(token))
|
|
|
| if (cmd == "A") {
|
|
|
|
|
| numbers <- as.numeric(unlist(strsplit(rest, "\\s+")))
|
| numbers <- numbers[!is.na(numbers)]
|
|
|
| if (length(numbers) >= 7) {
|
|
|
| rx <- numbers[1]
|
| ry <- numbers[2]
|
| rotation <- numbers[3]
|
| large_arc <- numbers[4]
|
| sweep <- numbers[5]
|
| x <- numbers[6] + offset_x
|
| y <- numbers[7] + offset_y
|
|
|
| result_parts <- c(result_parts,
|
| sprintf("A %.2f %.2f %.0f %d %d %.2f %.2f",
|
| rx, ry, rotation, large_arc, sweep, x, y))
|
| } else {
|
|
|
| result_parts <- c(result_parts, token)
|
| }
|
| } else if (cmd == "L") {
|
|
|
| numbers <- as.numeric(unlist(strsplit(rest, "\\s+")))
|
| numbers <- numbers[!is.na(numbers)]
|
|
|
| if (length(numbers) >= 2) {
|
| x <- numbers[1] + offset_x
|
| y <- numbers[2] + offset_y
|
| result_parts <- c(result_parts, sprintf("L %.2f %.2f", x, y))
|
| } else {
|
| result_parts <- c(result_parts, token)
|
| }
|
| } else if (cmd == "C") {
|
|
|
| numbers <- as.numeric(unlist(strsplit(rest, "\\s+")))
|
| numbers <- numbers[!is.na(numbers)]
|
|
|
| if (length(numbers) >= 6) {
|
| x1 <- numbers[1] + offset_x
|
| y1 <- numbers[2] + offset_y
|
| x2 <- numbers[3] + offset_x
|
| y2 <- numbers[4] + offset_y
|
| x3 <- numbers[5] + offset_x
|
| y3 <- numbers[6] + offset_y
|
| result_parts <- c(result_parts,
|
| sprintf("C %.2f %.2f %.2f %.2f %.2f %.2f", x1, y1, x2, y2, x3, y3))
|
| } else {
|
| result_parts <- c(result_parts, token)
|
| }
|
| } else if (cmd == "M") {
|
|
|
| numbers <- as.numeric(unlist(strsplit(rest, "\\s+")))
|
| numbers <- numbers[!is.na(numbers)]
|
|
|
| if (length(numbers) >= 2) {
|
| x <- numbers[1] + offset_x
|
| y <- numbers[2] + offset_y
|
| result_parts <- c(result_parts, sprintf("M %.2f %.2f", x, y))
|
| } else {
|
| result_parts <- c(result_parts, token)
|
| }
|
| } else {
|
|
|
| result_parts <- c(result_parts, token)
|
| }
|
| }
|
|
|
| return(paste(result_parts, collapse = " "))
|
| }
|
|
|
|
|
| for (side in 0:5) {
|
| edge_key <- sprintf("%d-%d", piece_id, side)
|
| edge <- edge_data$piece_edge_map[[edge_key]]
|
|
|
|
|
| offset_edge <- offset_path_coords(edge$forward, position$x, position$y)
|
| path_parts <- c(path_parts, offset_edge)
|
| }
|
|
|
|
|
| path_parts <- c(path_parts, "Z")
|
| path <- paste(path_parts, collapse = " ")
|
|
|
|
|
| piece_type <- if (ring_info$ring == 0) {
|
| "center"
|
| } else if (ring_info$ring == rings - 1) {
|
| "edge"
|
| } else {
|
| "inner"
|
| }
|
|
|
| pieces[[piece_id]] <- list(
|
| id = piece_id,
|
| ring = ring_info$ring,
|
| position_in_ring = ring_info$position,
|
| center_x = absolute_center$x,
|
| center_y = absolute_center$y,
|
| path = path,
|
| type = piece_type
|
| )
|
| }
|
|
|
| return(pieces)
|
| }
|
|
|