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:
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user