Skip to content

Commit a56c762

Browse files
committed
Add implicit sampling
1 parent 42c4b6e commit a56c762

File tree

3 files changed

+273
-0
lines changed

3 files changed

+273
-0
lines changed

media/src/Story.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import Blank from './stories/Blank.svelte';
33
import ArcLength from './stories/ArcLength.svelte';
44
import Linear from './stories/Linear.svelte';
5+
import Lagrange from './stories/Lagrange.svelte';
56
import PathIntegral from './stories/PathIntegral.svelte';
67
import SurfaceArea from './stories/SurfaceArea.svelte';
78
import FluxIntegral from './stories/FluxIntegral.svelte';
@@ -23,6 +24,7 @@
2324
<option value={Blank}>Select Story...</option>
2425
<option value={ArcLength}>Arc Length</option>
2526
<option value={Linear}>Linearization</option>
27+
<option value={Lagrange}>Lagrange</option>
2628
<option value={PathIntegral}>Path Integrals</option>
2729
<option value={SurfaceArea}>Surface Area</option>
2830
<option value={FluxIntegral}>Flux Integrals</option>

media/src/stories/Lagrange.svelte

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
<script>
2+
import * as THREE from 'three';
3+
4+
import { all, create, xor } from 'mathjs';
5+
import M from '../M.svelte';
6+
import {
7+
ShardsEdgesGeometry,
8+
ShardsGeometry,
9+
filterBang,
10+
gaussLegendre,
11+
sampleImplicitSurface,
12+
} from '../utils';
13+
import { mathToJSFunction } from '../objects/mathutils';
14+
import { onMount, onDestroy, untrack } from 'svelte';
15+
import { demoObjects } from '../states.svelte';
16+
17+
let { scene, render } = $props();
18+
19+
const config = {};
20+
const math = create(all, config);
21+
22+
let sampleParam = $state(0);
23+
24+
let backupObjects = $state(
25+
demoObjects.map((obj) => {
26+
obj.selected = false;
27+
return obj;
28+
}),
29+
);
30+
31+
let standardExamples = $state([
32+
{
33+
uuid: 'lag-story-example-001-',
34+
kind: 'level',
35+
title: 'Sphere',
36+
params: {
37+
a: '-2',
38+
b: '2',
39+
c: '-2',
40+
d: '2',
41+
e: '-2',
42+
f: '2',
43+
g: 'x^2 + y^2 - 1',
44+
},
45+
color: '#44CB44',
46+
},
47+
]);
48+
49+
const whiteLineMaterial = new THREE.LineBasicMaterial({
50+
color: 0xffffff,
51+
linewidth: 2,
52+
depthTest: true,
53+
});
54+
55+
let currentSurface = $state(
56+
backupObjects.find((o) => o.kind === 'level') ?? standardExamples[0],
57+
);
58+
59+
// $inspect(currentSurface);
60+
61+
onMount(() => {
62+
render();
63+
});
64+
65+
onDestroy(() => {
66+
for (let index = 0; index < boxes.children.length; index++) {
67+
const element = boxes.children[index];
68+
element.geometry?.dispose();
69+
element.material?.dispose();
70+
}
71+
scene.remove(boxes);
72+
demoObjects.length = 0;
73+
demoObjects.push(...backupObjects);
74+
render();
75+
});
76+
77+
let geo = $state();
78+
let edgesUp = $state();
79+
80+
const plusMaterial = new THREE.MeshStandardMaterial({
81+
color: 0xccddff,
82+
side: THREE.DoubleSide,
83+
roughness: 0.4,
84+
metalness: 0.8,
85+
});
86+
87+
const boxes = new THREE.Object3D();
88+
// boxes.add(new THREE.Mesh(undefined, plusMaterial)); // pos boxes
89+
// boxes.add(new THREE.LineSegments(undefined, whiteLineMaterial)); // pos edges
90+
// boxes.add(new THREE.Mesh(undefined, minusMaterial)); // neg boxes
91+
// boxes.add(new THREE.LineSegments(undefined, whiteLineMaterial)); // neg edges
92+
93+
scene.add(boxes);
94+
95+
let boxesVisible = $state(true);
96+
$effect(() => {
97+
boxes.visible = boxesVisible;
98+
});
99+
100+
let nBoxes = $state(10);
101+
let boxing = false;
102+
103+
let x0 = $derived(math.parse(currentSurface.params.a).evaluate());
104+
let x1 = $derived(math.parse(currentSurface.params.b).evaluate());
105+
let y0 = $derived(math.parse(currentSurface.params.c).evaluate());
106+
let y1 = $derived(math.parse(currentSurface.params.d).evaluate());
107+
let z0 = $derived(math.parse(currentSurface.params.e).evaluate());
108+
let z1 = $derived(math.parse(currentSurface.params.f).evaluate());
109+
let g = $derived(
110+
mathToJSFunction(currentSurface.params.g, ['x', 'y', 'z']),
111+
);
112+
113+
let pts = $derived(
114+
sampleImplicitSurface(g, [
115+
[x0, x1],
116+
[y0, y1],
117+
[z0, z1],
118+
nBoxes,
119+
nBoxes,
120+
nBoxes,
121+
]),
122+
);
123+
124+
$inspect(pts);
125+
126+
const sphereGeo = new THREE.SphereGeometry(0.01, 14, 14);
127+
128+
$effect(() => {
129+
untrack(() => {
130+
for (let index = boxes.children.length - 1; index >= 0; index--) {
131+
const element = boxes.children[index];
132+
// element.geometry?.dispose();
133+
// element.material?.dispose();
134+
boxes.remove(element);
135+
}
136+
});
137+
for (const p of pts) {
138+
const box = new THREE.Mesh(sphereGeo, plusMaterial);
139+
box.position.set(...p);
140+
boxes.add(box);
141+
}
142+
console.log('effecttt', boxes);
143+
render();
144+
});
145+
146+
$effect(() => {
147+
// console.log('demobj maintenance');
148+
if (currentSurface)
149+
untrack(() => {
150+
demoObjects[0] = currentSurface;
151+
demoObjects.length = 1;
152+
// filterBang(
153+
// (o) => o === currentField || o === currentSurface,
154+
// demoObjects,
155+
// );
156+
// if (demoObjects.findIndex((o) => o === currentField) < 0)
157+
// demoObjects.push(currentField);
158+
// if (demoObjects.findIndex((o) => o === currentSurface) < 0)
159+
// demoObjects.push(currentSurface);
160+
});
161+
});
162+
</script>
163+
164+
<div>
165+
<p>Hello</p>
166+
167+
<input
168+
type="range"
169+
name=""
170+
id=""
171+
min="1"
172+
bind:value={nBoxes}
173+
step="1"
174+
max="20"
175+
/>
176+
</div>
177+
178+
<style>
179+
/* .demos-obj-select {
180+
border-bottom-right-radius: 0;
181+
border-top-right-radius: 0;
182+
} */
183+
/* .blue-button {
184+
background-color: red;
185+
} */
186+
</style>

media/src/utils.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2876,3 +2876,88 @@ export {
28762876
scaleExp,
28772877
filterBang,
28782878
};
2879+
2880+
2881+
/**
2882+
* Sample roughly evenly spaced points on an implicit surface g(x, y, z) = 0
2883+
* within a cuboid defined by [xmin, xmax], [ymin, ymax], [zmin, zmax].
2884+
*
2885+
* @param {Function} g - scalar field function g(x, y, z)
2886+
* @param {Array} bounds - [[xmin, xmax], [ymin, ymax], [zmin, zmax]]
2887+
* @param {number} nx - grid resolution in x
2888+
* @param {number} ny - grid resolution in y
2889+
* @param {number} nz - grid resolution in z
2890+
* @returns {Array<[number, number, number]>} sampled points
2891+
*/
2892+
export function sampleImplicitSurface(g, bounds, nx = 40, ny = 40, nz = 40) {
2893+
const [[xmin, xmax], [ymin, ymax], [zmin, zmax]] = bounds;
2894+
const dx = (xmax - xmin) / (nx - 1);
2895+
const dy = (ymax - ymin) / (ny - 1);
2896+
const dz = (zmax - zmin) / (nz - 1);
2897+
2898+
// Precompute scalar field on the grid
2899+
const grid = Array.from({ length: nx }, () =>
2900+
Array.from({ length: ny }, () => new Array(nz))
2901+
);
2902+
2903+
for (let i = 0; i < nx; i++) {
2904+
const x = xmin + i * dx;
2905+
for (let j = 0; j < ny; j++) {
2906+
const y = ymin + j * dy;
2907+
for (let k = 0; k < nz; k++) {
2908+
const z = zmin + k * dz;
2909+
grid[i][j][k] = g(x, y, z);
2910+
}
2911+
}
2912+
}
2913+
2914+
const points = [];
2915+
2916+
// Check each cube in the grid for sign changes (surface crossings)
2917+
for (let i = 0; i < nx - 1; i++) {
2918+
for (let j = 0; j < ny - 1; j++) {
2919+
for (let k = 0; k < nz - 1; k++) {
2920+
// The cube's 8 corner values
2921+
const corners = [
2922+
[i, j, k],
2923+
[i + 1, j, k],
2924+
[i + 1, j + 1, k],
2925+
[i, j + 1, k],
2926+
[i, j, k + 1],
2927+
[i + 1, j, k + 1],
2928+
[i + 1, j + 1, k + 1],
2929+
[i, j + 1, k + 1]
2930+
].map(([ii, jj, kk]) => grid[ii][jj][kk]);
2931+
2932+
const minVal = Math.min(...corners);
2933+
const maxVal = Math.max(...corners);
2934+
2935+
if (minVal * maxVal > 0) continue; // No sign change → skip
2936+
2937+
// Find approximate intersection point (weighted average)
2938+
let sumWeights = 0;
2939+
let px = 0, py = 0, pz = 0;
2940+
2941+
for (let ii = 0; ii <= 1; ii++) {
2942+
for (let jj = 0; jj <= 1; jj++) {
2943+
for (let kk = 0; kk <= 1; kk++) {
2944+
const val = grid[i + ii][j + jj][k + kk];
2945+
const weight = 1 / (Math.abs(val) + 1e-6);
2946+
const x = xmin + (i + ii) * dx;
2947+
const y = ymin + (j + jj) * dy;
2948+
const z = zmin + (k + kk) * dz;
2949+
px += weight * x;
2950+
py += weight * y;
2951+
pz += weight * z;
2952+
sumWeights += weight;
2953+
}
2954+
}
2955+
}
2956+
2957+
points.push([px / sumWeights, py / sumWeights, pz / sumWeights]);
2958+
}
2959+
}
2960+
}
2961+
2962+
return points;
2963+
}

0 commit comments

Comments
 (0)