From d030d4270e23a155bd94e63cefd448a673a7890e Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sat, 4 Oct 2025 17:07:06 +0200 Subject: [PATCH] =?UTF-8?q?Fix:=20PNG=5FSHAPE=20distribuci=C3=B3n=20adapta?= =?UTF-8?q?tiva=20corregida=20completamente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PROBLEMAS RESUELTOS: 1. getPoint3D() ignoraba optimización → usaba edge_points_ siempre 2. extractAlternateRows() era destructiva → filtraba sobre filtrado 3. Con 10,000 pelotas mostraba bordes → ahora muestra RELLENO 4. Con 100 pelotas solo primera fila → ahora muestra todo el texto CAMBIOS IMPLEMENTADOS: - Añadido optimized_points_ (vector resultado final) - extractAlternateRows() ahora es función pura (devuelve nuevo vector) - extractCornerVertices() ahora es función pura - Cada nivel recalcula desde original (no desde filtrado previo) - getPoint3D() usa optimized_points_ exclusivamente FLUJO CORRECTO: - 10,000 pelotas: RELLENO completo (capas reducidas) - 500 pelotas: RELLENO + filas alternas (texto completo visible) - 100 pelotas: BORDES completos (todo el texto visible) - 10 pelotas: VÉRTICES (esqueleto visible) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- source/shapes/png_shape.cpp | 219 ++++++++++++++++++++++++++++-------- source/shapes/png_shape.h | 9 +- 2 files changed, 177 insertions(+), 51 deletions(-) diff --git a/source/shapes/png_shape.cpp b/source/shapes/png_shape.cpp index db029f8..2c091fb 100644 --- a/source/shapes/png_shape.cpp +++ b/source/shapes/png_shape.cpp @@ -4,6 +4,7 @@ #include #include #include +#include PNGShape::PNGShape(const char* png_path) { // Cargar PNG desde path @@ -98,67 +99,125 @@ void PNGShape::generatePoints(int num_points, float screen_width, float screen_h extrusion_depth_ = screen_height * PNG_EXTRUSION_DEPTH_FACTOR; num_layers_ = PNG_NUM_EXTRUSION_LAYERS; - // Generar puntos según el enfoque configurado - generateExtrudedPoints(); + // Generar AMBOS conjuntos de puntos (relleno Y bordes) + floodFill(); // Generar filled_points_ + detectEdges(); // Generar edge_points_ - // Calcular cuántos puntos 2D se necesitan - size_t num_2d_points = PNG_USE_EDGES_ONLY ? edge_points_.size() : filled_points_.size(); - size_t total_3d_points = num_2d_points * num_layers_; + // Guardar copias originales (las funciones de filtrado modifican los vectores) + std::vector filled_points_original = filled_points_; + std::vector edge_points_original = edge_points_; - // === ADAPTACIÓN AUTOMÁTICA MULTINIVEL === + // Conjunto de puntos ACTIVO (será modificado por filtros) + std::vector active_points_data; + std::string mode_name = ""; - // Nivel 1: Si relleno completo y pocas pelotas → switch a bordes - if (!PNG_USE_EDGES_ONLY && num_points < static_cast(total_3d_points) / 2) { - std::cout << "[PNG_SHAPE] Paso 1: Cambiando de RELLENO a BORDES (pelotas insuficientes)\n"; - detectEdges(); - num_2d_points = edge_points_.size(); - total_3d_points = num_2d_points * num_layers_; + // === SISTEMA DE DISTRIBUCIÓN ADAPTATIVA === + // Estrategia: Optimizar según número de pelotas disponibles + // Objetivo: SIEMPRE intentar usar relleno primero, solo bordes si es necesario + + size_t num_2d_points = 0; + size_t total_3d_points = 0; + + // NIVEL 1: Decidir punto de partida (relleno o bordes por configuración) + if (PNG_USE_EDGES_ONLY) { + active_points_data = edge_points_original; + mode_name = "BORDES (config)"; + } else { + active_points_data = filled_points_original; + mode_name = "RELLENO"; } - // Nivel 2: Reducir capas de extrusión si aún hay pocas pelotas - while (num_layers_ > 1 && num_points < static_cast(total_3d_points) / 2) { - num_layers_ = std::max(1, num_layers_ / 2); // Dividir capas a la mitad + num_2d_points = active_points_data.size(); + total_3d_points = num_2d_points * num_layers_; + + std::cout << "[PNG_SHAPE] Nivel 1: Modo inicial " << mode_name + << " (puntos 2D: " << num_2d_points << ", capas: " << num_layers_ + << ", total 3D: " << total_3d_points << ")\n"; + std::cout << "[PNG_SHAPE] Pelotas disponibles: " << num_points << "\n"; + + // NIVEL 2: Reducir capas de extrusión PRIMERO (antes de cambiar a bordes) + // Objetivo: Mantener relleno completo pero más fino + while (num_layers_ > 3 && num_points < static_cast(total_3d_points) * 0.8f) { + num_layers_ = std::max(3, num_layers_ / 2); total_3d_points = num_2d_points * num_layers_; - std::cout << "[PNG_SHAPE] Paso 2: Reduciendo capas a " << num_layers_ - << " (total puntos: " << total_3d_points << ")\n"; + std::cout << "[PNG_SHAPE] Nivel 2: Reduciendo capas a " << num_layers_ + << " (puntos 3D: " << total_3d_points << ")\n"; } - // Nivel 3: Sampling de píxeles (tomar cada N píxeles) si aún insuficiente - int sampling_step = 1; - std::vector* points_to_sample = (!edge_points_.empty()) ? &edge_points_ : &filled_points_; - - while (sampling_step < 10 && num_points < static_cast(total_3d_points) / 2) { - sampling_step++; - - // Aplicar sampling (tomar cada N puntos) - std::vector sampled_points; - for (size_t i = 0; i < points_to_sample->size(); i += sampling_step) { - sampled_points.push_back((*points_to_sample)[i]); - } - - if (!sampled_points.empty()) { - *points_to_sample = sampled_points; - num_2d_points = points_to_sample->size(); + // NIVEL 3: Filas alternas en RELLENO (mantiene densidad visual) + // Esto permite usar relleno incluso con pocas pelotas + int row_skip = 1; + if (!PNG_USE_EDGES_ONLY) { // Solo si empezamos con relleno + while (row_skip < 4 && num_points < static_cast(total_3d_points) * 0.8f) { + row_skip++; + // ✅ CLAVE: Recalcular desde el ORIGINAL cada vez (no desde el filtrado previo) + active_points_data = extractAlternateRows(filled_points_original, row_skip); + num_2d_points = active_points_data.size(); total_3d_points = num_2d_points * num_layers_; - std::cout << "[PNG_SHAPE] Paso 3: Aplicando sampling 1/" << sampling_step - << " (puntos: " << num_2d_points << ")\n"; + std::cout << "[PNG_SHAPE] Nivel 3: Filas alternas RELLENO (cada " << row_skip + << " filas, puntos 2D: " << num_2d_points << ", total 3D: " << total_3d_points << ")\n"; + mode_name = "RELLENO + FILAS/" + std::to_string(row_skip); } } + // NIVEL 4: Cambiar a BORDES solo si relleno optimizado no es suficiente + if (!PNG_USE_EDGES_ONLY && num_points < static_cast(total_3d_points) * 0.5f) { + active_points_data = edge_points_original; + mode_name = "BORDES (auto)"; + num_2d_points = active_points_data.size(); + total_3d_points = num_2d_points * num_layers_; + row_skip = 1; // Reset row_skip para bordes + std::cout << "[PNG_SHAPE] Nivel 4: Cambiando a BORDES (pelotas: " << num_points + << ", necesarias: " << total_3d_points << ")\n"; + } + + // NIVEL 5: Filas alternas en BORDES (si aún no es suficiente) + while (row_skip < 8 && num_points < static_cast(total_3d_points) * 0.7f) { + row_skip++; + // ✅ CLAVE: Recalcular desde edge_points_original cada vez + active_points_data = extractAlternateRows(edge_points_original, row_skip); + num_2d_points = active_points_data.size(); + total_3d_points = num_2d_points * num_layers_; + std::cout << "[PNG_SHAPE] Nivel 5: Filas alternas BORDES (cada " << row_skip + << " filas, puntos 2D: " << num_2d_points << ", total 3D: " << total_3d_points << ")\n"; + if (mode_name.find("FILAS") == std::string::npos) { + mode_name += " + FILAS/" + std::to_string(row_skip); + } + } + + // NIVEL 6: Reducir más las capas si aún sobran puntos + while (num_layers_ > 1 && num_points < static_cast(total_3d_points) * 0.6f) { + num_layers_ = std::max(1, num_layers_ / 2); + total_3d_points = num_2d_points * num_layers_; + std::cout << "[PNG_SHAPE] Nivel 6: Reduciendo más capas a " << num_layers_ + << " (puntos 3D: " << total_3d_points << ")\n"; + } + + // NIVEL 7: Vértices/esquinas (último recurso, muy pocas pelotas) + if (num_points < static_cast(total_3d_points) * 0.4f && num_points < 100) { + // Determinar desde qué conjunto extraer vértices (el que esté activo actualmente) + const std::vector& source_for_vertices = (mode_name.find("BORDES") != std::string::npos) + ? edge_points_original + : filled_points_original; + + std::vector vertices = extractCornerVertices(source_for_vertices); + if (!vertices.empty() && vertices.size() < active_points_data.size()) { + active_points_data = vertices; + num_2d_points = active_points_data.size(); + total_3d_points = num_2d_points * num_layers_; + mode_name = "VÉRTICES"; + std::cout << "[PNG_SHAPE] Nivel 7: Solo vértices (puntos 2D: " << num_2d_points << ")\n"; + } + } + + // ✅ CLAVE: Guardar el conjunto de puntos optimizado final en optimized_points_ (usado por getPoint3D) + optimized_points_ = active_points_data; + // Debug: mostrar configuración final std::cout << "[PNG_SHAPE] === CONFIGURACIÓN FINAL ===\n"; - std::string mode_str = (!edge_points_.empty()) ? "BORDES" : (PNG_USE_EDGES_ONLY ? "BORDES" : "RELLENO"); - std::cout << "[PNG_SHAPE] Modo: " << mode_str; - if (sampling_step > 1) { - std::cout << " (optimizado)"; - } - std::cout << "\n"; - std::cout << "[PNG_SHAPE] Píxeles 2D: " << num_2d_points; - if (sampling_step > 1) { - std::cout << " (sampling 1/" << sampling_step << ")"; - } - std::cout << "\n"; - std::cout << "[PNG_SHAPE] Capas: " << num_layers_ << "\n"; + std::cout << "[PNG_SHAPE] Modo: " << mode_name << "\n"; + std::cout << "[PNG_SHAPE] Píxeles 2D: " << num_2d_points << "\n"; + std::cout << "[PNG_SHAPE] Capas extrusión: " << num_layers_ << "\n"; std::cout << "[PNG_SHAPE] Total puntos 3D: " << total_3d_points << "\n"; std::cout << "[PNG_SHAPE] Pelotas disponibles: " << num_points << "\n"; std::cout << "[PNG_SHAPE] Ratio: " << (float)num_points / (float)total_3d_points << " pelotas/punto\n"; @@ -172,6 +231,68 @@ void PNGShape::generatePoints(int num_points, float screen_width, float screen_h center_offset_y_ = image_height_ * 0.5f; } +// Extraer filas alternas de puntos (FUNCIÓN PURA: no modifica parámetros) +// Recibe vector original y devuelve nuevo vector filtrado +std::vector PNGShape::extractAlternateRows(const std::vector& source, int row_skip) { + std::vector result; + + if (row_skip <= 1 || source.empty()) { + return source; // Sin filtrado, devolver copia del original + } + + // Organizar puntos por fila (Y) + std::map> rows; + for (const auto& p : source) { + int row = static_cast(p.y); + rows[row].push_back(p); + } + + // Tomar solo cada N filas + int row_counter = 0; + for (const auto& [row_y, row_points] : rows) { + if (row_counter % row_skip == 0) { + result.insert(result.end(), row_points.begin(), row_points.end()); + } + row_counter++; + } + + return result; +} + +// Extraer vértices y esquinas (FUNCIÓN PURA: devuelve nuevo vector) +std::vector PNGShape::extractCornerVertices(const std::vector& source) { + std::vector result; + + if (source.empty()) { + return result; + } + + // Estrategia simple: tomar bordes extremos de cada fila + // Esto da el "esqueleto" mínimo de las letras + + std::map> row_extremes; // Y -> (min_x, max_x) + + for (const auto& p : source) { + int row = static_cast(p.y); + if (row_extremes.find(row) == row_extremes.end()) { + row_extremes[row] = {p.x, p.x}; + } else { + row_extremes[row].first = std::min(row_extremes[row].first, p.x); + row_extremes[row].second = std::max(row_extremes[row].second, p.x); + } + } + + // Generar puntos en extremos de cada fila + for (const auto& [row_y, extremes] : row_extremes) { + result.push_back({extremes.first, static_cast(row_y)}); // Extremo izquierdo + if (extremes.second != extremes.first) { // Solo añadir derecho si es diferente + result.push_back({extremes.second, static_cast(row_y)}); // Extremo derecho + } + } + + return result; +} + void PNGShape::update(float delta_time, float screen_width, float screen_height) { if (!is_flipping_) { // Estado IDLE: texto de frente @@ -213,8 +334,8 @@ void PNGShape::update(float delta_time, float screen_width, float screen_height) } void PNGShape::getPoint3D(int index, float& x, float& y, float& z) const { - // Seleccionar puntos según configuración (priorizar edges si fue auto-ajustado) - const std::vector& points = (!edge_points_.empty()) ? edge_points_ : filled_points_; + // Usar SIEMPRE el vector optimizado (resultado final de generatePoints) + const std::vector& points = optimized_points_; if (points.empty()) { x = y = z = 0.0f; diff --git a/source/shapes/png_shape.h b/source/shapes/png_shape.h index 8eca604..4a2e4bb 100644 --- a/source/shapes/png_shape.h +++ b/source/shapes/png_shape.h @@ -17,8 +17,9 @@ private: struct Point2D { float x, y; }; - std::vector edge_points_; // Contorno (solo bordes) - std::vector filled_points_; // Relleno completo (para Enfoque B) + std::vector edge_points_; // Contorno (solo bordes) - ORIGINAL sin optimizar + std::vector filled_points_; // Relleno completo - ORIGINAL sin optimizar + std::vector optimized_points_; // Puntos finales optimizados (usado por getPoint3D) // Parámetros de extrusión float extrusion_depth_ = 0.0f; // Profundidad de extrusión en Z @@ -46,6 +47,10 @@ private: void floodFill(); // Rellenar interior (Enfoque B - futuro) void generateExtrudedPoints(); // Generar puntos con extrusión 2D + // Métodos de distribución adaptativa (funciones puras, no modifican parámetros) + std::vector extractAlternateRows(const std::vector& source, int row_skip); // Extraer filas alternas + std::vector extractCornerVertices(const std::vector& source); // Extraer vértices/esquinas + public: // Constructor: recibe path relativo al PNG PNGShape(const char* png_path = "data/shapes/jailgames.png");