Autor / Author: Sebastián Duarte Villanueva (@sebasdv)
Co-author: Claude.ai
Live demo: https://sebasdv.github.io/MathSurface/
Generador interactivo de superficies matemáticas 3D para impresión 3D. Visualiza y exporta funciones z = f(x, y) como archivos STL listos para imprimir, usando Three.js y Math.js.
- Abre la demo en tu navegador.
- Escribe tu ecuación en el campo "Ecuación z = f(x, y)". Ejemplo:
-0.25*x^3 - 0.25*y^3 + 0.5*x^2 + 0.5*y^2 - 0.25*x^2*y^2 - Haz clic en "Generar" o selecciona un ejemplo predefinido.
- Con "+ Agregar superficie" puedes graficar hasta cuatro ecuaciones a la vez. Donde dos se cruzan se dibuja automáticamente la curva de intersección.
- Arrastra para rotar, scroll o pellizco para hacer zoom. El botón ⏸ del encabezado pausa la rotación automática.
- Exporta como STL para impresión 3D (todas las superficies visibles, cada una como un cuerpo), o como PNG si solo quieres la imagen.
Cada modo muestra solo los controles que realmente lee, para que no haya sliders que no hagan nada:
| Control | Superficies | Revolución |
|---|---|---|
| Resolución | sí | no — usa pasos axiales/angulares |
| Escala Z | sí | no se aplica a un radio |
| Grosor, cara inferior, intersecciones | sí | no |
| Intervalo, Riemann, panel de volumen | no | sí |
A, f, φ, a1…a5, opacidad, mm/unidad |
sí | sí |
Los coeficientes a1…a5 son parámetros reales en ambos modos, así que se quedan visibles; lo que cambia son sus etiquetas — el sufijo · x³ / · y³ describe el polinomio por defecto de superficies, no los coeficientes en sí. En revolución aparecen como a1…a5 a secas, porque las ecuaciones ahí solo aceptan x. El slider de rango pasa a llamarse "rango de grilla" en revolución, que es lo único que hace ahí.
El interruptor Revolución ofrece los tres métodos del cálculo integral:
| Eje | Entradas | Método | Volumen |
|---|---|---|---|
| X | radio exterior f(x) |
disco | π ∫ f² dx |
| X | radios exterior e interior | arandela | π ∫ |f²−g²| dx |
| Y | curva superior e inferior | cáscara | 2π ∫ x(f−g) dx |
En modo cáscara el radio es x, así que el intervalo debe partir en 0 o más; la app lo rechaza si no. Dejar la segunda entrada vacía significa "la región entre la curva y el eje".
Las dos curvas se toman como máximo y mínimo punto a punto, no según en qué casilla las escribiste. Eso importa: si se cruzan — "la región limitada por y=x e y=2−x" es una arandela de libro — confiar en las etiquetas deja el radio interior por fuera del exterior y la malla se abre.
El panel de volumen muestra dos números que se calculan por caminos independientes:
π ∫ f² dx(oπ ∫ (f²−g²) dx) por regla de Simpson sobre el intervalo — el valor exacto de la curva.- El volumen de la malla generada, por el teorema de la divergencia.
Que converjan valida la malla; la diferencia que queda es el error de facetado y se encoge al subir los pasos angulares. El slider n de Riemann dibuja los n discos o arandelas que la integral está sumando, para ver la convergencia.
Verificado contra veinte sólidos con volumen de forma cerrada (esfera, cono, paraboloide, cuerno 1/x, sin(x), x², tres arandelas, y diez casos de cáscara incluyendo cambio de signo y curvas que se cruzan): la integral acierta al 0.0000% y la malla queda dentro del 0.21% con 64 pasos angulares, siempre cerrada y con normales hacia afuera.
Cuando la región se cierra a nada en un punto interior, el sólido se toca a sí mismo en un círculo. Eso es geometría fiel, no un error, pero deja una arista no-manifold que algunos slicers marcan; la app avisa cuántos puntos así encontró.
En modo disco/arandela el sólido se exporta acostado sobre el eje X. En modo cáscara sale de pie, con base circular plana — mejor para imprimir directamente.
El selector Cara inferior decide cómo se cierra el modelo por abajo:
- Base plana (por defecto): un solo plano bajo todo el modelo, en
min(z) − grosor. Apoya en la cama de impresión, y no puede auto-intersectarse nunca. - Cáscara desplazada: la superficie trasladada recto hacia abajo, de grosor constante. Gasta mucho menos material, pero no tiene base plana y se atraviesa a sí misma donde la caída entre muestras vecinas supera el grosor. La app detecta esa condición y avisa con los números concretos.
Con varias superficies el plano base es común a todas, para que la exportación apoye como una sola pieza en vez de dejar un cuerpo flotando; las alturas relativas se conservan, que es de lo que dependen las curvas de intersección.
La base se cierra con un abanico desde el centro al perímetro, no con una retícula completa: a resolución 100 son 400 triángulos en vez de 20.000 coplanares, y cada uno de ellos terminaría en el STL exportado (21.200 facetas contra 40.800).
Verificado: el volumen de la malla coincide con la integral doble ∫∫(z − z_base) dA al 0.0000% en cuatro superficies distintas, los 4·n triángulos de la base tienen normal exactamente hacia abajo, y no queda ningún vértice bajo el plano.
Los parámetros A, f, phi y a1…a5 son compartidos entre todas las superficies: mover un slider mueve toda la familia, que es justamente lo útil al compararlas. Cada superficie tiene solo su propia ecuación, color y visibilidad.
La curva de intersección es el conjunto de nivel cero del campo diferencia d = z₁ − z₂, calculado con marching squares sobre la misma malla con la que se teselan las superficies (sin evaluaciones extra ni librerías de geometría).
sin cos tan exp log sqrt abs y operadores + - * / ^
- Variables:
x,y - Parámetros ajustables con sliders:
A,f,phi,a1…a5
Interactive 3D math surface generator for 3D printing. Visualize and export z = f(x, y) equations as print-ready STL files, powered by Three.js and Math.js.
- Open the live demo in your browser.
- Enter your equation in the "Equation z = f(x, y)" field. Example:
-0.25*x^3 - 0.25*y^3 + 0.5*x^2 + 0.5*y^2 - 0.25*x^2*y^2 - Click "Generate" or pick a preset example.
- "+ Add surface" plots up to four equations at once. Where two of them cross, the intersection curve is drawn automatically.
- Drag to rotate, scroll or pinch to zoom. The ⏸ button in the header pauses the auto-rotation.
- Export as STL for 3D printing (every visible surface, each as its own body), or as PNG if you just want the image.
Each mode shows only the controls it actually reads, so no slider sits there doing nothing:
| Control | Surfaces | Revolution |
|---|---|---|
| Resolution | yes | no — uses axial/angular steps |
| Z scale | yes | not applied to a radius |
| Thickness, underside, intersections | yes | no |
| Interval, Riemann, volume panel | no | yes |
A, f, φ, a1…a5, opacity, mm/unit |
yes | yes |
The a1…a5 coefficients are real parameters in both modes, so they stay visible; what changes is their labels — the · x³ / · y³ suffix describes the default surface polynomial, not the coefficients themselves. In revolution they read as plain a1…a5, because equations there take only x. The range slider becomes "grid range" in revolution, which is all it does there.
The Revolution switch covers all three integral-calculus methods:
| Axis | Inputs | Method | Volume |
|---|---|---|---|
| X | outer radius f(x) |
disk | π ∫ f² dx |
| X | outer and inner radii | washer | π ∫ |f²−g²| dx |
| Y | upper and lower curves | shell | 2π ∫ x(f−g) dx |
In shell mode the radius is x, so the interval must start at 0 or above; the app refuses it otherwise. Leaving the second input empty means "the region between the curve and the axis".
The two curves are taken as the pointwise max and min, not according to which box you typed them in. That matters: if they cross — "the region bounded by y=x and y=2−x" is a textbook washer — trusting the labels puts the inner radius outside the outer one and the mesh comes apart.
The volume panel shows two numbers reached by independent routes:
π ∫ f² dx(orπ ∫ (f²−g²) dx) by Simpson's rule over the interval — the exact value for the curve.- The volume of the generated mesh, by the divergence theorem.
Their agreement validates the mesh; the remaining gap is faceting error and shrinks as the angular steps rise. The Riemann n slider draws the n disks or washers the integral is summing, so the convergence is visible.
Verified against twenty solids with closed-form volumes (sphere, cone, paraboloid, 1/x horn, sin(x), x², three washers, and ten shell cases including sign changes and crossing curves): the integral is exact to 0.0000% and the mesh lands within 0.21% at 64 angular steps, always closed and outward-facing.
Where the region closes to nothing at an interior point, the solid genuinely touches itself along a circle. That is faithful geometry rather than an error, but it leaves a non-manifold edge some slicers flag, so the app reports how many such points it found.
In disk/washer mode the solid exports lying along the X axis. In shell mode it comes out standing up, with a flat circular base — better for printing as-is.
The Underside selector decides how the model closes at the bottom:
- Flat base (default): one plane under the whole model, at
min(z) − thickness. It sits on the print bed, and it can never self-intersect. - Offset shell: the surface translated straight down, constant thickness. Far less material, but no flat base, and it passes through itself wherever the drop between neighbouring samples exceeds the thickness. The app detects that and warns with the actual numbers.
With several surfaces the base plane is shared, so an export sits on the bed as one piece rather than leaving a body floating; relative heights survive, which is what the intersection curves depend on.
The base closes with a fan from the centre to the perimeter rather than a full lattice: at resolution 100 that is 400 triangles instead of 20,000 coplanar ones, and every one of them would land in the exported STL (21,200 facets against 40,800).
Verified: the mesh volume matches the double integral ∫∫(z − z_base) dA to 0.0000% across four different surfaces, all 4·n base triangles have a normal pointing exactly straight down, and no vertex is left below the plane.
The A, f, phi and a1…a5 parameters are shared across all surfaces: moving one slider moves the whole family, which is the point when you are comparing them. Each surface only owns its equation, colour and visibility.
The intersection curve is the zero level set of the difference field d = z₁ − z₂, computed with marching squares over the same lattice the surfaces are tessellated on — no extra evaluations and no geometry library.
sin cos tan exp log sqrt abs and operators + - * / ^
- Variables:
x,y - Slider-controlled parameters:
A,f,phi,a1…a5
- Three.js r128
- Math.js v11
- Vanilla JS, no build tools
MIT © Sebastián Duarte Villanueva