three-pde
Una aplicación web interactiva que transforma imágenes bidimensionales en visualizaciones tridimensionales dinámicas mediante la resolución numérica de ecuaciones diferenciales parciales (PDEs). Este proyecto combina técnicas avanzadas de procesamiento de imágenes, métodos numéricos de diferencias finitas, y renderizado 3D en tiempo real utilizando WebGL y Three.js.



Ver el proyecto completo aquí: https://github.com/agarnung/threepde/tree/main.
Resumen Ejecutivo
three-pde es una herramienta educativa e interactiva que permite visualizar cómo evolucionan las imágenes cuando se les aplican diferentes ecuaciones diferenciales parciales. La aplicación convierte cada píxel de una imagen en un punto de altura en una malla 3D, donde la intensidad del píxel determina la altura inicial. A medida que la simulación avanza, la PDE modifica estos valores de altura según las leyes físicas y matemáticas que gobiernan cada ecuación, creando visualizaciones dinámicas y fascinantes.
Las imágenes de entrada deben estar en formato WebP, ya que el sistema lee los datos en formato RGBA (Rojo, Verde, Azul, Alfa). Es válido convertir una imagen monocromática a WebP y luego pasársela a la aplicación web. El procesamiento completo se realiza en el navegador del cliente, sin necesidad de servidor backend, aprovechando las capacidades de JavaScript moderno y WebGL para cálculos intensivos.
Arquitectura del Proyecto
Estructura de Carpetas
La estructura de carpetas del proyecto está diseñada de forma modular, optimizada y con separación clara de responsabilidades. Esto incluye la separación entre partes de web estática, archivos públicos o assets, y componentes de CI/CD:
threepde/
├── index.html # Punto de entrada único de la aplicación
├── assets/ # Archivos estáticos optimizados para producción
│ ├── js/ # Código JavaScript minificado (solo usado en producción)
│ │ └── main.min.js
│ └── images/ # Imágenes estáticas (siempre directamente disponibles desde el cliente)
├── src/ # Archivos de código fuente que se modifican durante desarrollo
│ ├── main.js # Punto de entrada principal de la aplicación
│ ├── solver.js # Implementación del solver de PDEs
│ ├── helpers/
│ │ ├── image-preprocessor.js # Normalización y redimensionado de imágenes
│ │ ├── color-maps.js # Implementación de mapas de colores y conversiones Lab↔RGB
│ │ └── image-mesh-converter.js # Conversión de imágenes a mallas 3D
│ └── ...
├── public/ # Archivos públicos servidos directamente
│ ├── css/
│ │ └── styles.css # Estilos de la aplicación
│ ├── images/ # Imágenes de ejemplo predefinidas
│ │ ├── lena_gray.webp
│ │ └── lena_rgb.webp
│ └── favicon.ico
└── .github/workflows/ # Automatización de producción
└── deploy.yml # Workflow de GitHub Actions para despliegue
Flujo de Desarrollo vs. Producción
Durante el desarrollo, se modifican únicamente los archivos dentro de src/. Las imágenes nuevas (por defecto, que el usuario finalmente podrá elegir con un selector) se agregan en assets/images/ o public/images/ y serán accesibles inmediatamente por el cliente, en el formato original, sin procesamiento de transferencia adicional.
Para previsualizar la web durante desarrollo se puede usar Live Server o Live Preview de VSCode (accesible desde http://localhost:5500), que sirve el index.html localmente y recarga automáticamente al guardar cambios. Esto elimina la necesidad de un backend (Flask, Django, etc.), ya que todo lo que servimos es contenido estático.
Uso de Three.js mediante CDN
Utilizamos Three.js mediante CDN tanto en desarrollo como en producción; lo especificamos directamente en el index.html. Esto tiene varias ventajas con respecto a usar Three.js en local o como librería npm:
- Evita duplicados en el repositorio: No necesitamos incluir archivos grandes de librerías en nuestro repositorio
- Aprovecha caché del navegador: Si el usuario ya visitó otro sitio que usa la misma versión de Three.js desde el mismo CDN, el navegador reutiliza el archivo en caché
- Versión minificada y optimizada: El CDN proporciona versiones minificadas que están optimizadas para producción
- Import maps modernos: Usamos la versión ES6 (three.module.js) con un import map, que es la versión más moderna y recomendada, en lugar de la UMD (three.min.js)
<!-- En index.html (SIEMPRE usa CDN) -->
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.module.js",
"three/examples/jsm/": "https://cdn.jsdelivr.net/npm/three@0.132.2/examples/jsm/"
}
}
</script>
Automatización de Despliegue con GitHub Actions
Usamos GitHub Actions para detectar pushes a la rama main y automáticamente minificar todos los archivos JavaScript de src/ y llevarlos a assets/js/main.min.js, es decir, para minificar y desplegar la web. Esto mantiene el código de src/ intacto y solo sube el resultado final optimizado a GitHub Pages, que es donde se sirve luego la versión optimizada de la web.
El workflow también minifica el CSS usando clean-css-cli para reducir aún más el tamaño de los archivos en producción. El proceso completo incluye:
- Checkout del código: Obtiene el código fuente del repositorio
- Instalación de dependencias: Instala herramientas de minificación (terser, clean-css-cli)
- Construcción y minificación:
- Copia archivos estáticos necesarios
- Minifica todos los archivos JavaScript de
src/en un solo bundle - Minifica el CSS
- Organiza todo en un directorio
dist/listo para producción
- Despliegue a GitHub Pages: Usa la acción
peaceiris/actions-gh-pages@v3para desplegar el contenido
name: Deploy to GitHub Pages
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: npm install -g terser clean-css-cli
- name: Build and Minify
run: |
mkdir -p dist/assets/js
mkdir -p dist/assets/css
cp index.html dist/
cp -r public/images dist/images
cp public/favicon.ico dist/
# Minify JS
npx terser "src/**/*.js" -c -m -o dist/assets/js/main.min.js
# Minify CSS
npx cleancss -o dist/assets/css/styles.min.css public/css/styles.css
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: $
publish_dir: dist
Carga Condicional de Scripts
Para decidir qué scripts JavaScript cargar desde el index.html, se crea un bloque de carga condicional basado en la URL actual, para diferenciar si cargar nuestro código fuente en desarrollo o el código minificado en producción:
<!-- Carga condicional basada en URL -->
<script type="module">
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
// Desarrollo: carga módulos desagregados
import('./src/main.js');
} else {
// Producción: carga bundle minificado
const script = document.createElement('script');
script.type = 'module';
script.src = 'assets/js/main.min.js';
document.head.appendChild(script);
}
</script>
Así, en localhost desarrollamos con archivos fuente individuales y en nuestro dominio (o, aquí, en github.io), se usa el bundle minificado automáticamente. Esto permite un desarrollo más cómodo con debugging más fácil, mientras que en producción se sirve código optimizado.
Comparación: GitHub Pages vs. PythonAnywhere/Flask
Es importante destacar la diferencia fundamental entre usar GitHub Pages y usar una solución como PythonAnywhere con Flask:
GitHub Pages:
- Sirve únicamente contenido estático (HTML, CSS, JavaScript)
- No requiere backend ni servidor de aplicaciones
- No puede ejecutar código del lado del servidor (Python, PHP, etc.)
- Ideal para aplicaciones de una sola página (SPA) que procesan todo en el cliente
- Gratuito para repositorios públicos
- Despliegue automático mediante Git
PythonAnywhere/Flask:
- Requiere un servidor de aplicaciones que ejecute código Python
- Permite procesamiento del lado del servidor
- Puede generar contenido dinámico en cada solicitud
- Requiere configuración de servidor, bases de datos, etc.
- Puede tener costos asociados según el plan
En nuestro caso, three-pde no necesita backend porque:
- Todo el procesamiento de imágenes se hace en el navegador (FileReader API, Canvas API)
- El solver de PDEs se ejecuta en JavaScript/WebGL
- No hay necesidad de almacenar datos del usuario
- No hay autenticación ni sesiones
Por lo tanto, GitHub Pages es la solución perfecta y más simple para este proyecto.
Procesamiento de Imágenes
Tipos de Imágenes en el Proyecto
Hay dos tipos de imágenes en el proyecto, cada una con su propio flujo de procesamiento:
1. Imágenes Estáticas Predefinidas
Las imágenes estáticas, predefinidas por el desarrollador en public/images/, son parte del repositorio y deben estar optimizadas. Es decir, se debería usar herramientas como ImageMagick o squoosh-cli para convertirlas a un formato optimizado (e.g. WebP) antes de subirlas, a fin de reducir el tamaño del repositorio, mejorar el tiempo de carga inicial, reducir el ancho de banda, etc.
Ejemplo de optimización:
# Usando ImageMagick
convert lena.png -resize 512x512 -quality 99 public/images/lena.webp
# Usando squoosh-cli
npx @squoosh/cli --webp '{quality:99}' lena.png -d public/images/
2. Imágenes Subidas por el Usuario
Las imágenes subidas por el usuario, cuyo origen es su sistema local, se procesan inmediatamente y por completo en el navegador. El flujo completo es el siguiente:
sequenceDiagram
Usuario->>Navegador: Selecciona imagen desde sistema de archivos
Navegador->>JavaScript: FileReader API lee el archivo
JavaScript->>Canvas: Dibuja imagen en canvas HTML5
Canvas->>JavaScript: getImageData() obtiene datos de píxeles RGBA
JavaScript->>Normalización: Convierte valores a rango [0,1]
JavaScript->>Preprocesamiento: Extrae canal de luminancia (L de Lab)
JavaScript->>Three.js: Crea heightmap a partir de datos normalizados
Three.js->>WebGL: Renderiza malla 3D en tiempo real
Este procesamiento completamente en el cliente tiene varias ventajas:
- Privacidad: Las imágenes nunca salen del navegador del usuario
- Velocidad: No hay latencia de red para el procesamiento
- Escalabilidad: No hay carga en el servidor
- Costo: No se requiere infraestructura de servidor para procesamiento
Conversión a Escala de Grises y Espacio de Color Lab
Independientemente del tipo de imagen de entrada (RGB, escala de grises, etc.), las PDEs se resuelven utilizando únicamente el canal de luminancia. Esto se debe a que las ecuaciones diferenciales parciales que implementamos operan sobre funciones escalares (un valor por punto), no sobre funciones vectoriales (múltiples valores por punto).
El proceso de conversión utiliza el espacio de color Lab (CIE Lab*), que es perceptualmente uniforme y separa la luminancia (L) de la crominancia (a, b). Esto permite:
- Extraer la luminancia (L): Se usa para resolver la PDE
- Preservar la crominancia (a, b): Se almacena para reconstruir colores después
El algoritmo de conversión RGB a Lab utiliza el punto blanco D65 como referencia estándar, que es el estándar para monitores y pantallas modernas.
Redimensionado y Submuestreo
Para optimizar el rendimiento del solver, las imágenes se redimensionan típicamente a 512×512 píxeles antes de ser procesadas por la PDE. Esto reduce significativamente el número de operaciones necesarias:
- Una imagen de 1920×1080 tiene 2,073,600 píxeles
- Una imagen de 512×512 tiene 262,144 píxeles
- Reducción de ~8x en el número de operaciones
El redimensionado se realiza usando el algoritmo de interpolación bilineal del Canvas API, que proporciona un buen balance entre calidad y rendimiento.
Nota importante sobre el modo “constant-color”: Este es el único modo en el que se puede visualizar en el canvas 2D la imagen con resolución original perfecta, puesto que la intensidad del heightmap del resto de modos es la calculada en cada paso por el solver, y típicamente submuestreamos a 512×512 la imagen para no sobrecargar al solver. Pero en el caso constant-color, el valor de intensidad de malla y canvas2d no cambia en ningún paso; solo la altura de la mesh evoluciona según la PDE.
Ecuaciones Diferenciales Parciales Implementadas
Ecuación de Calor (Heat Equation)
La ecuación de calor, también conocida como ecuación de difusión o ecuación de Fourier, es una de las PDEs más fundamentales en física y matemáticas:
\[\frac{\partial u}{\partial t} = \alpha \nabla^2 u\]Donde:
- \(u(x,y,t)\) es la función que evoluciona (en nuestro caso, la intensidad de la imagen)
- \(\alpha\) es el coeficiente de difusión
- \(\nabla^2\) es el operador laplaciano (segunda derivada espacial)
Esta ecuación modela cómo se difunde el calor (o cualquier cantidad conservada) a través de un medio. En el contexto de procesamiento de imágenes, produce un efecto de “desenfoque” progresivo, donde los bordes se suavizan y los detalles de alta frecuencia se atenúan con el tiempo.
Ecuación de Onda (Wave Equation)
La ecuación de onda describe la propagación de ondas en un medio:
\[\frac{\partial^2 u}{\partial t^2} = c^2 \nabla^2 u\]Donde:
- $c$ es la velocidad de propagación de la onda
- La segunda derivada temporal indica que es una ecuación de segundo orden
Esta ecuación produce efectos ondulatorios, donde las perturbaciones se propagan a través de la imagen creando patrones de interferencia y ondas que se reflejan en los bordes según las condiciones de contorno.
Decaimiento Exponencial (Exponential Decay)
Técnicamente, el decaimiento exponencial es una ecuación diferencial ordinaria (ODE), no una PDE, ya que no hay propagación espacial:
\[\frac{\partial u}{\partial t} = -\lambda u\]Donde \(\lambda\) es la constante de decaimiento.
Esta ecuación no requiere condición CFL (Courant-Friedrichs-Lewy) porque no hay propagación espacial, pero sí tiene una condición de estabilidad numérica que debe cumplirse para evitar inestabilidades en el solver.
La solución analítica es \(u(t) = u_0 e^{-\lambda t}\), lo que produce un desvanecimiento uniforme de la imagen con el tiempo.
Métodos Numéricos y Discretización
Esquemas de Discretización Temporal
El proyecto implementa varios esquemas numéricos para resolver las PDEs:
Forward Euler (Explícito)
El método de Euler hacia adelante es un esquema explícito de primer orden:
\[u^{n+1} = u^n + \Delta t \cdot f(u^n, t^n)\]Ventajas:
- Simple de implementar
- Computacionalmente eficiente (no requiere resolver sistemas lineales)
- Fácil de paralelizar
Desventajas:
- Condición de estabilidad restrictiva (CFL)
- Puede volverse inestable con pasos de tiempo grandes
Backward Euler (Implícito)
El método de Euler hacia atrás es un esquema implícito de primer orden:
\[u^{n+1} = u^n + \Delta t \cdot f(u^{n+1}, t^{n+1})\]Ventajas:
- Incondicionalmente estable (para ecuaciones lineales)
- Permite pasos de tiempo más grandes
Desventajas:
- Requiere resolver un sistema lineal en cada paso
- Más costoso computacionalmente
- Más complejo de implementar
Crank-Nicholson (Comentado, no implementado aún)
El método de Crank-Nicholson es un esquema implícito de segundo orden que promedia las evaluaciones en \(t^n\) y \(t^{n+1}\):
\[u^{n+1} = u^n + \frac{\Delta t}{2} \left[ f(u^n, t^n) + f(u^{n+1}, t^{n+1}) \right]\]Este método combina las ventajas de estabilidad de los métodos implícitos con mayor precisión, pero requiere resolver sistemas lineales más complejos.
Discretización Espacial: Diferencias Finitas
El laplaciano $\nabla^2 u$ se discretiza usando el esquema de diferencias finitas de cinco puntos (stencil de 5 puntos):
\[\nabla^2 u_{i,j} \approx \frac{u_{i+1,j} + u_{i-1,j} + u_{i,j+1} + u_{i,j-1} - 4u_{i,j}}{h^2}\]Donde $h$ es el espaciado de la malla (típicamente \(h = 1\) píxel).
Este esquema es de segundo orden en espacio y es el estándar para problemas de difusión en 2D.
Condiciones de Contorno
Las condiciones de contorno son cruciales para determinar cómo se comporta la solución en los bordes del dominio. El proyecto implementa varias opciones:
Dirichlet (Valor Fijo)
Especifica el valor de la función en el contorno:
\[u|_{\partial \Omega} = g\]Casos especiales:
- Zero: \(u|_{\partial \Omega} = 0\) (valores fijos en cero)
- Fixed: \(u|_{\partial \Omega} = u_0|_{\partial \Omega}\) (valores originales de la imagen inicial)
Neumann (Derivada Fija)
Especifica el valor de la derivada normal en el contorno:
\[\frac{\partial u}{\partial n}|_{\partial \Omega} = h\]Caso especial:
- Reflective: \(\frac{\partial u}{\partial n}|_{\partial \Omega} = 0\) (sin flujo a través del borde, como un espejo)
Periódica
Los bordes se conectan de forma que la imagen se “envuelve” sobre sí misma:
\[u(0, y) = u(W, y)\] \[u(x, 0) = u(x, H)\]Donde \(W\) y \(H\) son el ancho y alto de la imagen. Esto crea un dominio toroidal donde no hay bordes reales.
Robin (Mixta)
Combina condiciones Dirichlet y Neumann:
\[\alpha u + \beta \frac{\partial u}{\partial n}|_{\partial \Omega} = \gamma\]Esta es una condición más general que permite modelar fenómenos físicos más complejos, como transferencia de calor con convección.
Para más información sobre condiciones de contorno, consulta: Wikipedia.
Mapas de Colores (Colormaps)
El proyecto implementa varios mapas de colores para visualizar los valores de altura de la malla 3D. Los colormaps transforman valores escalares (altura) en colores RGB para facilitar la interpretación visual.
Modos de Coloreado
Constant (Constante)
Todos los vértices de la malla reciben el mismo color uniforme (gris medio, #888888). Este modo es útil para enfatizar la forma geométrica de la malla sin distracciones de color, pero para la imagen no tiene mucho sentido visualmente pues se ve completamente uniforme. Se mantiene principalmente para demostrar el funcionamiento de los colormaps y para casos de prueba.
Grayscale (Escala de Grises)
Mapea directamente los valores de altura a una escala de grises, donde valores bajos son negros y valores altos son blancos. Es el modo más intuitivo y corresponde directamente a la imagen procesada.
Constant Color (Color Constante / Modo Mantel)
En este modo, los colores RGB originales de la imagen se mantienen completamente intactos. La imagen se comporta como un “mantel” que se coloca sobre la malla 3D, donde la altura evoluciona según la PDE pero los colores permanecen fijos.
Características importantes:
- La imagen queda visualmente intacta en términos de color
- Solo la altura (geometría) de la malla cambia
- Es el único modo donde el canvas 2D puede mostrar la imagen a resolución original perfecta, ya que la intensidad no cambia en cada paso del solver
- Todos los vértices de la malla comparten el mismo color base (gris), pero se mapea el color original de la imagen a la posición correspondiente
Este modo realmente significa “mantener colores originales”, mientras que el modo “constant-chrominance” significa “mantener crominancia original”.
Constant Chrominance (Crominancia Constante)
Este modo combina la altura evolutiva (que se mapea al canal L de Lab) con los canales de crominancia originales (a, b) de la imagen. Esto produce una visualización donde:
- La luminancia (L) evoluciona según la PDE (determina la altura y el brillo)
- La crominancia (a, b) se mantiene constante (determina el matiz y la saturación)
Este modo proporciona una retroalimentación visual más físicamente informada de la evolución de la PDE, mientras mantiene parte de la identidad de color original de la imagen. Es especialmente útil para visualizar cómo la difusión o las ondas afectan la estructura de la imagen sin perder completamente la información de color.
Jet
Un colormap clásico que va del azul (valores bajos) al rojo (valores altos), pasando por cian, verde, amarillo y naranja. Es muy popular en visualización científica pero puede ser problemático para personas con daltonismo.
Viridis
Un colormap perceptualmente uniforme diseñado específicamente para visualización científica. Va del púrpura oscuro (bajos) al amarillo brillante (altos), y es accesible para personas con daltonismo.
Inferno
Similar a Viridis pero con una paleta más cálida, yendo del negro (bajos) al amarillo brillante (altos). También es perceptualmente uniforme y accesible.
Seismic
Un colormap divergente que va del azul oscuro (valores negativos/bajos) al rojo oscuro (valores positivos/altos), pasando por blanco en el centro. Ideal para visualizar datos que tienen un punto de referencia central.
RdYlBu (Red-Yellow-Blue)
Otro colormap divergente que va del rojo (bajos) al azul (altos), pasando por amarillo en el centro. Útil para visualizar desviaciones de un valor central.
Visualización 3D con Three.js
Creación de la Malla de Altura
La malla 3D se crea a partir del heightmap (mapa de alturas) generado a partir de los datos de la imagen. El proceso incluye:
- Generación de vértices: Cada píxel de la imagen se convierte en un vértice 3D
- Cálculo de índices: Se crean triángulos conectando vértices adyacentes
- Cálculo de normales: Se calculan normales de vértices para iluminación realista
- Asignación de colores: Se aplica el colormap seleccionado a cada vértice
- Creación de wireframe: Se genera una versión de alambre para visualización alternativa
Materiales y Iluminación
El proyecto utiliza MeshStandardMaterial de Three.js, que soporta iluminación física basada (PBR - Physically Based Rendering). Las propiedades configurables incluyen:
- Roughness (rugosidad): Controla qué tan difusa es la reflexión (0 = espejo, 1 = completamente mate)
- Metalness (metalicidad): Controla el comportamiento metálico del material (0 = dieléctrico, 1 = conductor)
Actualmente, estos valores están fijos en el código, pero hay un TODO para permitir su control en tiempo real a través de la interfaz de usuario.
La escena utiliza iluminación direccional desde dos direcciones opuestas para proporcionar una iluminación equilibrada que revele los detalles de la superficie.
Normalización de Alturas
El proyecto incluye una opción de normalización de alturas que ajusta dinámicamente el rango de alturas visible en cada paso de la simulación. Esto es útil cuando:
- Los valores de altura cambian significativamente durante la simulación
- Se quiere mantener un contraste visual constante
- Se quiere enfatizar las variaciones relativas en lugar de los valores absolutos
La normalización se puede activar/desactivar con la tecla N durante la simulación.
Interfaz de Usuario y Controles
Controles Principales
- Run/Pause: Inicia o pausa la simulación (checkbox o tecla
R) - Show Mesh: Muestra/oculta la malla 3D sólida
- Show Wireframe: Muestra/oculta el wireframe de la malla
- Velocity Slider: Controla la velocidad de visualización de la simulación (NO el paso de tiempo del solver)
- Color Map Selector: Permite cambiar el mapa de colores en tiempo real
- PDE Selector: Permite cambiar la ecuación diferencial parcial
- Boundary Condition Selector: Permite cambiar las condiciones de contorno
- Discretization Scheme Selector: Permite cambiar el método numérico
- File Upload: Permite cargar imágenes personalizadas
Atajos de Teclado
| Tecla | Acción |
|---|---|
E |
Alterna modo pseudo-pantalla completa 3D |
R |
Inicia/pausa la simulación |
S |
Guarda la imagen actual como PNG |
N |
Alterna normalización de alturas |
G |
Alterna solver GPU (cuando está disponible) |
⟳ (Reset) |
Restaura las condiciones iniciales |
Modo Pseudo-Pantalla Completa
Al presionar E, la visualización 3D se expande para ocupar toda la pantalla, ocultando los controles y el canvas 2D. Esto permite una experiencia de visualización inmersiva. Los ejes de referencia también se ocultan automáticamente en este modo.
Optimizaciones y Rendimiento
Control de Velocidad de Animación
El control de velocidad (slider) NO modifica el paso de tiempo (dt) del solver, sino que controla la frecuencia con la que se actualiza la visualización. Esto es importante porque:
- Cambiar
dtafectaría la precisión y estabilidad numérica - El control de velocidad permite ajustar la experiencia visual sin comprometer la física
El sistema implementa dos modos de velocidad:
- Velocidad baja (1-50): Ejecuta un paso de la simulación cada cierto intervalo de tiempo
- Velocidad alta (51-99): Ejecuta múltiples pasos por frame para acelerar la visualización
Solver en GPU (WebGL)
El proyecto incluye soporte experimental para ejecutar el solver en la GPU usando WebGL shaders. Esto puede proporcionar aceleración significativa para resoluciones altas, pero actualmente está limitado a:
- Ecuaciones de calor y onda
- Esquema Forward Euler
- Condiciones de contorno periódicas o reflectivas
El indicador “GPU solver ON” aparece cuando el solver está ejecutándose en la GPU.
Nota sobre Web Workers: Hay un TODO pendiente para implementar el solver en un Web Worker, lo que permitiría ejecutar el cómputo en un hilo separado sin bloquear el hilo principal de renderizado. Esto mejoraría significativamente la fluidez de la animación, especialmente para simulaciones intensivas.
Minificación y Optimización
En producción, todos los archivos JavaScript se minifican y se combinan en un solo bundle. El CSS también se minifica. Esto reduce:
- El tamaño total de los archivos
- El número de solicitudes HTTP
- El tiempo de carga inicial
Flujo de Trabajo Completo
El flujo de trabajo completo de desarrollo y producción se puede visualizar así:
graph LR
A[Editar archivos en src/] --> B[Previsualizar con Live Server]
B --> C{¿Funciona correctamente?}
C -->|Sí| D[Commit y Push a GitHub]
C -->|No| A
D --> E[GitHub Actions detecta push]
E --> F[Minifica JS y CSS]
F --> G[Despliega a GitHub Pages]
G --> H[Disponible en usuario.github.io/repo]
Uso de MathJax para Ecuaciones
Para escribir las ecuaciones matemáticas en la interfaz, usamos MathJax v3 mediante CDN. MathJax permite renderizar ecuaciones LaTeX directamente en HTML, proporcionando una visualización de alta calidad de las fórmulas matemáticas.
Alternativamente, se podría usar MathML, que es nativo de HTML5, pero es menos legible en el código fuente y no permite usar la sintaxis LaTeX, que es más familiar para la mayoría de los científicos y matemáticos.
Polyfills y Compatibilidad
Se emplea un polyfill para URL (url-polyfill) para garantizar que ciertas características de JavaScript correspondientes a ES6 estén disponibles en navegadores más antiguos que no las soportan de manera nativa. Esto mejora la compatibilidad con navegadores legacy, aunque el proyecto está optimizado para navegadores modernos con soporte WebGL2.
Stats.js para Monitoreo de Rendimiento
Utilizamos Stats.js (desde CDN) para mostrar los FPS (frames por segundo) en tiempo real. Esto es útil para:
- Detectar problemas de rendimiento
- Ajustar parámetros de la simulación
- Verificar que la aplicación mantiene una tasa de frames adecuada
El contador de FPS se muestra en la esquina inferior derecha de la pantalla.
Exportación de Imágenes
La función de exportación permite guardar el estado actual de la simulación como una imagen PNG. El proceso:
- Crea un canvas temporal con las dimensiones originales de la imagen
- Aplica el colormap actual
- Genera un blob con los datos de la imagen
- Descarga el archivo usando la API de descarga del navegador
Para el modo “constant-color”, se exporta la imagen original a resolución completa. Para otros modos, se escala la imagen procesada a la resolución original.
Referencias y Trabajos Relacionados
Este proyecto está inspirado por el trabajo de chrismars91/fdm, que implementa un solver de diferencias finitas para visualización. Sin embargo, three-pde extiende significativamente estas ideas al:
- Convertir imágenes a heightmaps con colormaps seleccionables
- Implementar múltiples PDEs, incluyendo algunas de nuestro preprint de investigación
- Proporcionar una interfaz web interactiva completa
- Soportar procesamiento completamente en el cliente
Para más información sobre PDEs aplicadas a imágenes, consulta:
Para opciones de uso de Three.js en proyectos, consulta: discourse.threejs.org.
TODOs y Mejoras Futuras
Implementaciones Pendientes
-
Control de velocidad del solver: Actualmente el slider controla la velocidad de visualización, pero sería útil poder controlar también el paso de tiempo (
dt) del solver para experimentar con diferentes condiciones de estabilidad. -
Exportar imagen mejorado: La funcionalidad de exportación está implementada, pero se podría mejorar con opciones para elegir formato (PNG, JPEG, WebP), calidad, y resolución.
-
Minificación de CSS automatizada: Ya está implementada en el workflow de GitHub Actions usando
clean-css-cli. -
Selector de resolucion de heightmap: Permitir al usuario elegir el tamaño del heightmap, ya sea especificando ancho y alto, o especificando uno y manteniendo la relación de aspecto (no forzar 512×512).
-
Esquemas numéricos adicionales: Implementar Crank-Nicholson, Forward-Backward Euler, y otros esquemas para mayor flexibilidad y precisión.
-
Solver en Web Worker: Implementar el solver en un Web Worker para evitar bloquear el hilo principal de renderizado. Esto mejoraría significativamente la fluidez, especialmente para simulaciones intensivas. La variable
needToUpdatese usaría como un mutex compartido para notificar cuando el cómputo ha terminado. -
Control de propiedades de material: Permitir control en tiempo real de
roughnessymetalnessdel material de la malla, así como cambiar entre materiales básicos y estándar, todo en una ventana específica de opciones de Three.js. -
Modo Lab original: Dedicar un párrafo completo explicando el modo original Lab y cómo funciona en contraste con los otros modos.
-
Solver GPU mejorado: Habilitar GPU ON/OFF para todas las PDEs, no solo algunas. Inspirarse en proyectos como vertexWaves para implementaciones más avanzadas.
-
Interpolación perspective-correct: Para interpolar valores de color de la imagen a la malla, usar interpolación perspective-correct para mayor precisión visual.
-
Optimización de transferencia CPU-GPU: Para el solver GPU, no debería convertirse a ImageData en cada paso, ya que esto es un cuello de botella (alta carga de transferencia CPU-GPU). En su lugar, si se está en modo GPU, se debería renderizar directamente la textura en pantalla usando un quad de pantalla completa (sceneView), por ejemplo.
-
Más PDEs: Implementar ecuaciones adicionales como la ecuación de Laplace, ecuaciones de reacción-difusión, ecuaciones de Burgers, etc.
Conclusión
three-pde es un proyecto que demuestra la potencia de combinar procesamiento de imágenes, métodos numéricos y visualización 3D en tiempo real en el navegador. Al ejecutar todo en el cliente, proporciona una experiencia interactiva inmediata sin necesidad de infraestructura de servidor, mientras mantiene la privacidad del usuario al procesar las imágenes localmente.
El proyecto sirve tanto como herramienta educativa para entender cómo funcionan las PDEs en el contexto de procesamiento de imágenes, como plataforma de experimentación para investigadores que quieren visualizar la evolución de imágenes bajo diferentes modelos físicos y matemáticos.
La arquitectura modular y el uso de tecnologías web estándar hacen que el proyecto sea fácil de extender y modificar, permitiendo a otros desarrolladores agregar nuevas PDEs, esquemas numéricos, o funcionalidades de visualización.