Skip to content

Commit 915ac08

Browse files
committed
isolines
1 parent cd8fc97 commit 915ac08

10 files changed

Lines changed: 613 additions & 396 deletions

File tree

docs/isosurface.ipynb

Lines changed: 172 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
"source": [
88
"# Isosurfaces\n",
99
"\n",
10-
"Isosurface rendering extracts and displays the zero level-set of a scalar function inside a 3D mesh. A second function can be colored on the isosurface."
10+
"Isosurface rendering extracts and displays the zero level-set of a scalar function inside a 3D mesh. A second function can be colored on the isosurface.\n",
11+
"\n",
12+
"Isolines (contour lines) draw curves of constant value on surfaces and clipping planes, using screen-space derivatives for consistent line width."
1113
]
1214
},
1315
{
@@ -91,10 +93,178 @@
9193
"scene.redraw()"
9294
]
9395
},
96+
{
97+
"cell_type": "markdown",
98+
"id": "b1",
99+
"metadata": {},
100+
"source": [
101+
"# Isolines\n",
102+
"\n",
103+
"Isolines (contour lines) show where a function takes specific values. They are drawn directly in the fragment shader using screen-space derivatives for consistent line width at any zoom level.\n",
104+
"\n",
105+
"- `IsolineRenderer` is a subclass of `CFRenderer` that adds isoline rendering.\n",
106+
"- `ClippingIsolineRenderer` does the same on a clipping plane cross-section.\n",
107+
"- `show_field=True` renders the colored field with isolines; `show_field=False` (default) renders only the lines."
108+
]
109+
},
110+
{
111+
"cell_type": "markdown",
112+
"id": "b2",
113+
"metadata": {},
114+
"source": [
115+
"### Quick Usage with `Draw`\n",
116+
"\n",
117+
"The simplest way to add isolines: pass `isolines=True` or a number of levels to `Draw`."
118+
]
119+
},
120+
{
121+
"cell_type": "code",
122+
"execution_count": null,
123+
"id": "b3",
124+
"metadata": {},
125+
"outputs": [],
126+
"source": [
127+
"from ngsolve import *\n",
128+
"from ngsolve_webgpu import *\n",
129+
"from ngsolve_webgpu.jupyter import Draw\n",
130+
"\n",
131+
"mesh = Mesh(unit_square.GenerateMesh(maxh=0.05))\n",
132+
"Draw(sin(10*x)*cos(10*y), mesh, order=4, isolines=10)"
133+
]
134+
},
135+
{
136+
"cell_type": "markdown",
137+
"id": "b4",
138+
"metadata": {},
139+
"source": [
140+
"### Programmatic Control\n",
141+
"\n",
142+
"For more control, create an `IsolineRenderer` directly. It is a subclass of `CFRenderer` that adds isoline rendering. Use `show_field=True` to also show the colored field underneath."
143+
]
144+
},
145+
{
146+
"cell_type": "code",
147+
"execution_count": null,
148+
"id": "b5",
149+
"metadata": {},
150+
"outputs": [],
151+
"source": [
152+
"from ngsolve import *\n",
153+
"from ngsolve_webgpu import *\n",
154+
"from webgpu.jupyter import Draw\n",
155+
"\n",
156+
"mesh = Mesh(unit_square.GenerateMesh(maxh=0.05))\n",
157+
"mesh_data = MeshData(mesh)\n",
158+
"cf = exp(-(10*((x-0.5)**2 + (y-0.5)**2)))\n",
159+
"function_data = FunctionData(mesh_data, cf, order=4)\n",
160+
"\n",
161+
"renderer = IsolineRenderer(function_data, n_lines=12, thickness=2.0, show_field=True)\n",
162+
"scene = Draw([renderer, Colorbar(renderer.colormap)])"
163+
]
164+
},
165+
{
166+
"cell_type": "code",
167+
"execution_count": null,
168+
"id": "b6",
169+
"metadata": {},
170+
"outputs": [],
171+
"source": [
172+
"renderer.isolines.n_lines = 25\n",
173+
"renderer.isolines.color = (1, 0, 0, 1)\n",
174+
"scene.redraw()"
175+
]
176+
},
177+
{
178+
"cell_type": "markdown",
179+
"id": "b7",
180+
"metadata": {},
181+
"source": [
182+
"### Isolines of One Function over Another\n",
183+
"\n",
184+
"`IsolineRenderer` draws only the isoline pixels and discards everything else, so it can be layered over a different visualization. This lets you show the isolines of one function on top of the color field of another."
185+
]
186+
},
187+
{
188+
"cell_type": "code",
189+
"execution_count": null,
190+
"id": "b8",
191+
"metadata": {},
192+
"outputs": [],
193+
"source": [
194+
"from ngsolve import *\n",
195+
"from ngsolve_webgpu import *\n",
196+
"from webgpu.jupyter import Draw\n",
197+
"\n",
198+
"mesh = Mesh(unit_square.GenerateMesh(maxh=0.03))\n",
199+
"mesh_data = MeshData(mesh)\n",
200+
"\n",
201+
"temperature = FunctionData(mesh_data, sin(3*x)*cos(3*y), order=4)\n",
202+
"pressure = FunctionData(mesh_data, exp(-5*((x-0.3)**2 + (y-0.7)**2)), order=4)\n",
203+
"\n",
204+
"colormap = Colormap()\n",
205+
"r_field = CFRenderer(temperature, colormap=colormap)\n",
206+
"r_iso = IsolineRenderer(pressure, n_lines=12, thickness=1)\n",
207+
"\n",
208+
"Draw([r_field, r_iso, Colorbar(colormap)])"
209+
]
210+
},
211+
{
212+
"cell_type": "markdown",
213+
"id": "b9",
214+
"metadata": {},
215+
"source": [
216+
"### Isolines on a 3D Clipping Plane\n",
217+
"\n",
218+
"Isolines also work on clipping plane cross-sections. Use `ClippingIsolineRenderer` to overlay isolines of a function on the clip plane."
219+
]
220+
},
221+
{
222+
"cell_type": "code",
223+
"execution_count": null,
224+
"id": "b10",
225+
"metadata": {},
226+
"outputs": [],
227+
"source": [
228+
"from ngsolve import *\n",
229+
"from ngsolve_webgpu import *\n",
230+
"from webgpu.clipping import Clipping\n",
231+
"from webgpu.jupyter import Draw\n",
232+
"\n",
233+
"mesh = Mesh(unit_cube.GenerateMesh(maxh=0.1))\n",
234+
"mesh_data = MeshData(mesh)\n",
235+
"cf = sin(5*x)*cos(5*y)*exp(z)\n",
236+
"function_data = FunctionData(mesh_data, cf, order=3)\n",
237+
"\n",
238+
"clipping = Clipping()\n",
239+
"clipping.mode = clipping.Mode.PLANE\n",
240+
"clipping.center = [0.5, 0.5, 0.5]\n",
241+
"\n",
242+
"colormap = Colormap()\n",
243+
"clip_iso = ClippingIsolineRenderer(function_data, clipping=clipping, colormap=colormap, n_lines=10, show_field=True)\n",
244+
"cfr = IsolineRenderer(function_data, n_lines=10, show_field=True, clipping=clipping, colormap=colormap)\n",
245+
"\n",
246+
"Draw([cfr, clip_iso, Colorbar(colormap)])"
247+
]
248+
},
249+
{
250+
"cell_type": "markdown",
251+
"id": "b11",
252+
"metadata": {},
253+
"source": [
254+
"### Properties\n",
255+
"\n",
256+
"| Property | Description |\n",
257+
"|---|---|\n",
258+
"| `renderer.isolines.n_lines` | Number of evenly-spaced levels between colormap min and max |\n",
259+
"| `renderer.isolines.thickness` | Line width in approximate pixels (default 1.5) |\n",
260+
"| `renderer.isolines.color` | RGBA tuple for the line color (default black) |\n",
261+
"| `renderer.isolines.show_field` | Whether to also render the colored field (default False) |"
262+
]
263+
},
94264
{
95265
"cell_type": "code",
96266
"execution_count": null,
97-
"id": "deb6e1a2-afd5-4b68-b9ce-20dc7b2ba4a8",
267+
"id": "b12",
98268
"metadata": {},
99269
"outputs": [],
100270
"source": []

ngsolve_webgpu/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .facet_cf import FacetFunctionData, FacetCFRenderer, FacetCFRenderer3D
55
from .pick import MeshPickResult, HighlightUniforms, GeoPickResult
66
from .clipping import ClippingCF
7+
from .isolines import IsolineSettings, IsolineRenderer, ClippingIsolineRenderer
78
from webgpu.colormap import Colorbar, Colormap
89
from webgpu.clipping import Clipping
910
from .geometry import GeometryRenderer

ngsolve_webgpu/cf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from webgpu.colormap import Colormap
1010
from webgpu.renderer import Renderer, RenderOptions, check_timestamp
1111
from webgpu.shapes import ShapeRenderer, generate_cylinder
12-
from webgpu.utils import BufferBinding, UniformBinding, buffer_from_array, write_array_to_buffer
12+
from webgpu.utils import BufferBinding, UniformBinding, buffer_from_array, write_array_to_buffer, read_shader_file
1313
from webgpu.renderer import BaseRenderer, RenderOptions, check_timestamp
1414
from webgpu.uniforms import UniformBase, ct
1515
from webgpu.utils import (

0 commit comments

Comments
 (0)