jigsawR / R /hexagonal_separation.R
pjt222's picture
Upload folder using huggingface_hub
e232e39 verified
Raw
History Blame Contribute Delete
13.8 kB
# Working Hexagonal Puzzle Piece Separation
# Simplified approach: separate placeholder pieces, not actual puzzle pieces
#' Extract hexagonal puzzle structure (stub for compatibility)
#'
#' Generates a complete hexagonal puzzle and returns its structure.
#' This is a compatibility stub for code that expects the old function.
#'
#' @param rings Number of rings
#' @param seed Random seed
#' @param diameter Puzzle diameter
#' @param tabsize Tab size percentage
#' @param jitter Jitter percentage
#' @param do_warp Apply circular warping
#' @param do_trunc Truncate edge pieces
#' @return List with puzzle structure
extract_hexagonal_puzzle_structure <- function(rings, seed, diameter = 240,
tabsize = 6, jitter = 5,
do_warp = FALSE, do_trunc = FALSE) {
# Generate complete puzzle
puzzle_result <- generate_hex_jigsaw_svg(
seed = seed,
tabsize = tabsize,
jitter = jitter,
diameter = diameter,
rings = rings,
do_warp = do_warp,
do_trunc = do_trunc
)
# Return structure compatible with old API
return(list(
type = "hexagonal",
rings = rings,
diameter = diameter,
seed = seed,
num_pieces = 3 * rings * (rings - 1) + 1,
paths = list(
horizontal = puzzle_result$horizontal,
vertical = puzzle_result$vertical,
border = puzzle_result$border
),
parameters = puzzle_result$parameters,
svg = puzzle_result$svg
))
}
#' Generate separated hexagonal puzzle with placeholder pieces or bezier curves
#'
#' Creates a layout showing where pieces would be positioned when separated.
#' Can use simple hexagon shapes as placeholders OR real puzzle pieces with bezier curves.
#'
#' @param rings Number of rings
#' @param seed Random seed
#' @param diameter Puzzle diameter
#' @param offset Separation distance between pieces
#' @param arrangement "rectangular" or "hexagonal" packing
#' @param use_bezier Use real bezier curves with tabs (default: FALSE for placeholder hexagons)
#' @param tabsize Tab size percentage (used when use_bezier = TRUE)
#' @param jitter Jitter percentage (used when use_bezier = TRUE)
#' @param do_warp Apply circular warping to border edges (creates circular puzzle boundary)
#' @param do_trunc Truncate edge pieces to regular hexagon boundary
#' @param colors Piece colors (if NULL, uses palette)
#' @param stroke_width Line width
#' @param background Background color
#' @param palette Viridis palette name (NULL = use config default, only used if colors is NULL)
#' @param fill_color Fill color for pieces ("none" for transparent, or any CSS color)
#' @param opacity Opacity of puzzle pieces (0.0 to 1.0, default 1.0 = fully opaque)
#' @return SVG content as string
#' @note DEPRECATED: Use generate_puzzle(type = 'hexagonal', offset = X) instead.
#' This function will be removed in a future version.
generate_separated_hexagonal_svg <- function(rings = 3, seed = NULL,
diameter = 240, offset = 10,
arrangement = "rectangular",
use_bezier = FALSE,
tabsize = 6, jitter = 5,
do_warp = FALSE, do_trunc = FALSE,
colors = NULL, stroke_width = 1,
background = "none",
palette = NULL,
fill_color = "none",
opacity = 1.0) {
.Deprecated("generate_puzzle",
msg = paste(
"generate_separated_hexagonal_svg() is deprecated.",
"Use generate_puzzle(type = 'hexagonal', offset = X) instead.",
"Example: generate_puzzle(type = 'hexagonal', grid = c(3), size = c(240), offset = 10)"
)
)
if (is.null(seed)) {
seed <- as.integer(runif(1) * 10000)
}
# Initialize hexagonal puzzle
init_hex_jigsaw(seed = seed, rings = rings, diameter = diameter)
# Calculate number of pieces
num_pieces <- 3 * rings * (rings - 1) + 1
log_info("Generating separated layout for {num_pieces} pieces")
# Calculate piece size (approximate)
# Correct formula: diameter / (4 * rings - 2)
piece_radius <- diameter / (4 * rings - 2)
# Calculate grid layout
if (arrangement == "rectangular") {
# Rectangular packing for efficiency
cols <- ceiling(sqrt(num_pieces * 1.3))
rows <- ceiling(num_pieces / cols)
log_info("Using rectangular grid: {cols} x {rows}")
# Calculate viewBox
total_width <- cols * (2 * piece_radius + offset) + offset
total_height <- rows * (2 * piece_radius + offset) + offset
vb_x <- 0
vb_y <- 0
} else {
# Hexagonal arrangement (topology-based, centered at origin)
# Calculate actual extent of pieces
# For n rings, outermost pieces are at grid coordinates ~(+/-n, +/-n)
base_spacing <- piece_radius * 2
separation_factor <- 1.0 + (offset / base_spacing)
# Maximum distance from center (approximation)
max_radius <- rings * base_spacing * separation_factor * 1.5
# Add margin for piece size
margin <- piece_radius * 2
total_width <- 2 * (max_radius + margin)
total_height <- 2 * (max_radius + margin)
# Center the viewBox at origin
vb_x <- -total_width / 2
vb_y <- -total_height / 2
}
# Generate colors from palette if colors not provided
if (is.null(colors)) {
colors <- get_puzzle_colors(num_pieces, palette)
}
# Start SVG
svg_lines <- c(
sprintf('<svg width="%.1fmm" height="%.1fmm" viewBox="%.2f %.2f %.2f %.2f" xmlns="http://www.w3.org/2000/svg">',
total_width, total_height, vb_x, vb_y, total_width, total_height),
sprintf(' <title>Hexagonal Puzzle - %d rings, %d pieces (separated by %dmm)</title>',
rings, num_pieces, offset)
)
# Add background based on type
# Background can be: "none", a color string, or a list with gradient colors
if (is.list(background) && !is.null(background$type) && background$type == "gradient") {
# Custom gradient with user-specified colors
center_color <- background$center
middle_color <- background$middle
edge_color <- background$edge
svg_lines <- c(svg_lines,
' <defs>',
sprintf(' <radialGradient id="bg-gradient" cx="50%%" cy="50%%" r="50%%">'),
sprintf(' <stop offset="0%%" style="stop-color:%s;stop-opacity:1" />', center_color),
sprintf(' <stop offset="50%%" style="stop-color:%s;stop-opacity:1" />', middle_color),
sprintf(' <stop offset="100%%" style="stop-color:%s;stop-opacity:1" />', edge_color),
' </radialGradient>',
' </defs>',
sprintf(' <rect x="%.2f" y="%.2f" width="%.2f" height="%.2f" fill="url(#bg-gradient)"/>',
vb_x, vb_y, total_width, total_height)
)
} else if (is.character(background) && background == "gradient") {
# Legacy: default gradient colors for backward compatibility
svg_lines <- c(svg_lines,
' <defs>',
' <radialGradient id="bg-gradient" cx="50%" cy="50%" r="50%">',
' <stop offset="0%" style="stop-color:#e3f2fd;stop-opacity:1" />',
' <stop offset="50%" style="stop-color:#bbdefb;stop-opacity:1" />',
' <stop offset="100%" style="stop-color:#90caf9;stop-opacity:1" />',
' </radialGradient>',
' </defs>',
sprintf(' <rect x="%.2f" y="%.2f" width="%.2f" height="%.2f" fill="url(#bg-gradient)"/>',
vb_x, vb_y, total_width, total_height)
)
} else if (is.character(background) && background != "none" && background != "") {
# Solid color background
svg_lines <- c(svg_lines,
sprintf(' <rect x="%.2f" y="%.2f" width="%.2f" height="%.2f" fill="%s"/>',
vb_x, vb_y, total_width, total_height, background)
)
}
# If background is "none" or "", no background rect is added
svg_lines <- c(svg_lines, ' <g id="separated-pieces">')
# Generate pieces (bezier or placeholder)
if (use_bezier) {
# Generate real puzzle pieces with complementary edges
log_info("Generating pieces with bezier curves and tabs...")
# Calculate spacing based on arrangement
if (arrangement == "rectangular") {
# For rectangular, use offset directly (pieces spaced in grid)
separated <- FALSE # Pieces positioned in grid, not separated by topology
base_spacing <- NULL
} else {
# For hexagonal, use topology-based separation
separated <- TRUE
base_spacing <- piece_radius * 2
separation_factor <- 1.0 + (offset / base_spacing)
}
# Generate all pieces with proper edge mapping
pieces <- generate_hex_pieces_with_edge_map(
rings = rings,
seed = seed,
diameter = diameter,
tabsize = tabsize,
jitter = jitter,
separated = separated,
base_spacing = base_spacing,
separation_factor = if (separated) separation_factor else 1.0,
do_warp = do_warp,
do_trunc = do_trunc
)
# Add pieces to SVG
for (i in 1:num_pieces) {
piece <- pieces[[i]]
# For rectangular arrangement, override position
if (arrangement == "rectangular") {
row <- floor((i - 1) / cols)
col <- (i - 1) %% cols
piece$center_x <- offset + col * (2 * piece_radius + offset) + piece_radius
piece$center_y <- offset + row * (2 * piece_radius + offset) + piece_radius
# Rebuild path with new position (simple translation)
# This is a simplified approach - proper implementation would regenerate edges
# For now, we'll use the hexagonal arrangement for bezier mode
}
# Select color
color_index <- ((i - 1) %% length(colors)) + 1
piece_color <- colors[color_index]
svg_lines <- c(svg_lines,
sprintf(' <path d="%s" fill="%s" stroke="%s" stroke-width="%.1f" opacity="%.2f"/>',
piece$path, fill_color, piece_color, stroke_width, opacity),
sprintf(' <text x="%.2f" y="%.2f" text-anchor="middle" dominant-baseline="central" font-size="8" fill="%s">%d</text>',
piece$center_x, piece$center_y, piece_color, i)
)
}
} else {
# Generate placeholder hexagons
for (i in 1:num_pieces) {
# Calculate position and rotation
piece_rotation <- 0 # Default rotation for rectangular arrangement
if (arrangement == "rectangular") {
row <- floor((i - 1) / cols)
col <- (i - 1) %% cols
center_x <- offset + col * (2 * piece_radius + offset) + piece_radius
center_y <- offset + row * (2 * piece_radius + offset) + piece_radius
} else {
# Hexagonal arrangement (topology-based with center at origin)
# Calculate spacing parameters
base_spacing <- piece_radius * 2
separation_factor <- 1.0 + (offset / base_spacing)
# Get topology-based position
position <- calculate_hex_piece_position(
piece_id = i,
rings = rings,
base_spacing = base_spacing,
separation_factor = separation_factor
)
center_x <- position$x
center_y <- position$y
# In a proper hexagonal grid (honeycomb), ALL hexagons have the same orientation
# They don't rotate - only their position varies
# This creates a tiled pattern where edges align naturally
piece_rotation <- 0
# Debug logging for first 3 pieces
if (i <= 3) {
rot_deg <- round(piece_rotation * 180 / pi, 2)
cx_fmt <- round(center_x, 1)
cy_fmt <- round(center_y, 1)
log_info("Piece {i}: (x={cx_fmt}, y={cy_fmt}, rot={rot_deg} deg)")
}
}
# Select color
color_index <- ((i - 1) %% length(colors)) + 1
piece_color <- colors[color_index]
# Create hexagon placeholder with rotation
hex_path <- create_hex_placeholder(center_x, center_y, piece_radius * 0.8, piece_rotation)
svg_lines <- c(svg_lines,
sprintf(' <path d="%s" fill="%s" stroke="%s" stroke-width="%.1f" opacity="%.2f"/>',
hex_path, fill_color, piece_color, stroke_width, opacity),
sprintf(' <text x="%.2f" y="%.2f" text-anchor="middle" dominant-baseline="central" font-size="8" fill="%s">%d</text>',
center_x, center_y, piece_color, i)
)
}
}
svg_lines <- c(svg_lines, ' </g>', '</svg>')
return(paste(svg_lines, collapse = "\n"))
}
#' Create hexagon placeholder path
#'
#' @param cx Center X
#' @param cy Center Y
#' @param radius Hexagon radius
#' @param rotation Rotation angle in radians (default: 0)
#' @return SVG path string
create_hex_placeholder <- function(cx, cy, radius, rotation = 0) {
# Create regular hexagon with rotation
# Add pi/6 offset for flat-top orientation (edges horizontal)
# Without offset: pointy-top (vertices at top/bottom)
# With offset: flat-top (edges at top/bottom)
base_offset <- pi / 6
vertices <- list()
for (i in 0:5) {
angle <- i * pi / 3 + rotation + base_offset
x <- cx + radius * cos(angle)
y <- cy + radius * sin(angle)
vertices[[i + 1]] <- c(x, y)
}
# Build path
path <- sprintf("M %.2f %.2f", vertices[[1]][1], vertices[[1]][2])
for (i in 2:6) {
path <- paste(path, sprintf("L %.2f %.2f", vertices[[i]][1], vertices[[i]][2]))
}
path <- paste(path, "Z")
return(path)
}