Fix: PNG_SHAPE distribución adaptativa corregida completamente

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 <noreply@anthropic.com>
This commit is contained in:
2025-10-04 17:07:06 +02:00
parent fbd09b3201
commit d030d4270e
2 changed files with 177 additions and 51 deletions

View File

@@ -4,6 +4,7 @@
#include <cmath>
#include <algorithm>
#include <iostream>
#include <map>
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<Point2D> filled_points_original = filled_points_;
std::vector<Point2D> edge_points_original = edge_points_;
// === ADAPTACIÓN AUTOMÁTICA MULTINIVEL ===
// Conjunto de puntos ACTIVO (será modificado por filtros)
std::vector<Point2D> 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<int>(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<int>(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<int>(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<Point2D>* points_to_sample = (!edge_points_.empty()) ? &edge_points_ : &filled_points_;
while (sampling_step < 10 && num_points < static_cast<int>(total_3d_points) / 2) {
sampling_step++;
// Aplicar sampling (tomar cada N puntos)
std::vector<Point2D> 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<int>(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<int>(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<int>(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<int>(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<int>(total_3d_points) * 0.4f && num_points < 100) {
// Determinar desde qué conjunto extraer vértices (el que esté activo actualmente)
const std::vector<Point2D>& source_for_vertices = (mode_name.find("BORDES") != std::string::npos)
? edge_points_original
: filled_points_original;
std::vector<Point2D> 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::Point2D> PNGShape::extractAlternateRows(const std::vector<Point2D>& source, int row_skip) {
std::vector<Point2D> result;
if (row_skip <= 1 || source.empty()) {
return source; // Sin filtrado, devolver copia del original
}
// Organizar puntos por fila (Y)
std::map<int, std::vector<Point2D>> rows;
for (const auto& p : source) {
int row = static_cast<int>(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::Point2D> PNGShape::extractCornerVertices(const std::vector<Point2D>& source) {
std::vector<Point2D> 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<int, std::pair<float, float>> row_extremes; // Y -> (min_x, max_x)
for (const auto& p : source) {
int row = static_cast<int>(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<float>(row_y)}); // Extremo izquierdo
if (extremes.second != extremes.first) { // Solo añadir derecho si es diferente
result.push_back({extremes.second, static_cast<float>(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<Point2D>& points = (!edge_points_.empty()) ? edge_points_ : filled_points_;
// Usar SIEMPRE el vector optimizado (resultado final de generatePoints)
const std::vector<Point2D>& points = optimized_points_;
if (points.empty()) {
x = y = z = 0.0f;

View File

@@ -17,8 +17,9 @@ private:
struct Point2D {
float x, y;
};
std::vector<Point2D> edge_points_; // Contorno (solo bordes)
std::vector<Point2D> filled_points_; // Relleno completo (para Enfoque B)
std::vector<Point2D> edge_points_; // Contorno (solo bordes) - ORIGINAL sin optimizar
std::vector<Point2D> filled_points_; // Relleno completo - ORIGINAL sin optimizar
std::vector<Point2D> 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<Point2D> extractAlternateRows(const std::vector<Point2D>& source, int row_skip); // Extraer filas alternas
std::vector<Point2D> extractCornerVertices(const std::vector<Point2D>& source); // Extraer vértices/esquinas
public:
// Constructor: recibe path relativo al PNG
PNGShape(const char* png_path = "data/shapes/jailgames.png");