Skip to content

Commit

Permalink
Start surface area story
Browse files Browse the repository at this point in the history
  • Loading branch information
drewyoungren committed Nov 20, 2023
1 parent 03f6b8c commit e47dc12
Show file tree
Hide file tree
Showing 3 changed files with 512 additions and 0 deletions.
2 changes: 2 additions & 0 deletions media/src/Story.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import ArcLength from './stories/ArcLength.svelte';
import Linear from './stories/Linear.svelte';
import PathIntegral from './stories/PathIntegral.svelte';
import SurfaceArea from './stories/SurfaceArea.svelte';
import FluxIntegral from './stories/FluxIntegral.svelte';
export let objects;
Expand All @@ -21,6 +22,7 @@
<option value={ArcLength}>Arc Length</option>
<option value={Linear}>Linearization</option>
<option value={PathIntegral}>Path Integrals</option>
<option value={SurfaceArea}>Surface Area</option>
<option value={FluxIntegral}>Flux Integrals</option>
</select>
</div>
Expand Down
373 changes: 373 additions & 0 deletions media/src/stories/SurfaceArea.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,373 @@
<script>
import PlayButtons from '../form-components/PlayButtons.svelte';

Check failure on line 2 in media/src/stories/SurfaceArea.svelte

View workflow job for this annotation

GitHub Actions / Node run

'PlayButtons' is defined but never used
import * as THREE from 'three';
import { all, create } from 'mathjs';
import M from '../M.svelte';
import { FluxBoxEdgesGeometry, FluxBoxGeometry } from '../utils';
import { onMount, onDestroy } from 'svelte';
// import { createEventDispatcher } from 'svelte';
// const dispatch = createEventDispatcher();
import { tickTock } from '../stores';

Check failure on line 13 in media/src/stories/SurfaceArea.svelte

View workflow job for this annotation

GitHub Actions / Node run

'tickTock' is defined but never used
export let scene;
export let render;
// export let objects;
const config = {};
const math = create(all, config);
let animation = false;

Check failure on line 22 in media/src/stories/SurfaceArea.svelte

View workflow job for this annotation

GitHub Actions / Node run

'animation' is assigned a value but never used
let last = null;

Check failure on line 23 in media/src/stories/SurfaceArea.svelte

View workflow job for this annotation

GitHub Actions / Node run

'last' is assigned a value but never used
let sampleParam = 0;
const shards = true;
export let objects;
const backupObjects = [
...objects.map((obj) => {
obj.selected = false;
return obj;
}),
];
const exampleSurfaces = [
{
uuid: 'area-story-example-001-',
kind: 'surface',
title: 'Cylinder',
params: {
a: '0',
b: '2 pi',
c: '0',
d: '1',
x: 'cos(u)',
y: '2 * sin(u)',
z: 'v',
t0: '0',
t1: '1',
},
color: '#CB44CB',
},
{
uuid: 'area-story-example-002-',
kind: 'surface',
title: 'Dome',
params: {
a: '0',
b: 'pi / 2',
c: '0',
d: 'pi',
x: 'sin(u) cos(v)',
y: 'sin(u) sin(v)',
z: 'cos(u)',
t0: '0',
t1: '1',
},
color: '#CB44CB',
},
...backupObjects.filter(
(obj) =>
obj.kind === 'surface' &&
obj.uuid.slice(0, 18) !== 'flux-story-example'
),
];
const whiteLineMaterial = new THREE.LineBasicMaterial({
color: 0xffffff,
linewidth: 2,
depthTest: true,
});
let currentSurface =
objects.find((o) => o.kind === 'surface') || exampleSurfaces[0];
onMount(() => {
// currentField =
// objects.find((o) => o.kind === 'field')?.uuid ||
// 'flux-story-field-001-';
// currentSurface =
// objects.find((o) => o.kind === 'surface') || exampleSurfaces[0];
render();
});
onDestroy(() => {
for (let index = 0; index < boxes.children.length; index++) {
const element = boxes.children[index];
element.geometry?.dispose();
element.material?.dispose();
}
scene.remove(boxes);
objects = [...backupObjects];
render();
});
let r = (u, v) => [u, v, 1 / 2 - (u * u) / 4 - (v * v) / 2];
let F = (x, y) => new THREE.Vector3(0, 1 / 2, y);
let geo;
let geoDown;
let edgesUp;
let edgesDown;
// console.log(geo);
const plusMaterial = new THREE.MeshPhongMaterial({
color: '#474747',
shininess: 80,
side: THREE.FrontSide,
transparent: true,
opacity: 0.6,
});
const minusMaterial = new THREE.MeshPhongMaterial({
color: '#CC1212',
shininess: 80,
side: THREE.BackSide,
transparent: true,
opacity: 0.6,
});
const boxes = new THREE.Object3D();
boxes.add(new THREE.Mesh(undefined, plusMaterial)); // pos boxes
boxes.add(new THREE.LineSegments(undefined, whiteLineMaterial)); // pos edges
boxes.add(new THREE.Mesh(undefined, minusMaterial)); // neg boxes
boxes.add(new THREE.LineSegments(undefined, whiteLineMaterial)); // neg edges
scene.add(boxes);
let tau = 1;
let nBoxes = 3;
let u0, u1, v0, v1;
const setRuv = (obj) => {
// const obj = exampleSurfaces.find((o) => o.uuid === uuid);
if (obj) {
const { x, y, z, a, b, c, d } = obj.params;
const [X, Y, Z, A, B, C, D] = [x, y, z, a, b, c, d].map((w) =>
math.parse(w).compile()
);
r = (u, v) => [
X.evaluate({ u, v }),
Y.evaluate({ u, v }),
Z.evaluate({ u, v }),
];
u0 = A.evaluate();
u1 = B.evaluate();
v0 = (u) => C.evaluate({ u });
v1 = (u) => D.evaluate({ u });
// // Match color to that of surface
// plusMaterial.color.set(obj.color);
// const hsl = {};
// plusMaterial.color.getHSL(hsl);
// minusMaterial.color.setHSL(hsl.h + 0.6, hsl.s, hsl.l);
} else {
r = (u, v) => [u, v, (u * u) / 4 - (v * v) / 4];
u0 = -1;
u1 = 1;
v0 = () => -1;
v1 = () => 1;
}
objects = [...objects.filter((o) => o.kind !== 'surface'), obj];
};
$: setRuv(currentSurface);
const updateGeo = (N, F, r, A, B, C, D, samp) => {
geo?.dispose();
geo = new FluxBoxGeometry(
F,
r,
A,
B,
C,
D,
N,
shards,
tau,
'pos',
samp
);
boxes.children[0].geometry = geo;
edgesUp?.dispose();
edgesUp = new FluxBoxEdgesGeometry(geo, shards, tau);
boxes.children[1].geometry = edgesUp;
geoDown?.dispose();
geoDown = new FluxBoxGeometry(F, r, A, B, C, D, N, false, tau, 'neg');
boxes.children[2].geometry = geoDown;
edgesDown?.dispose();
edgesDown = new FluxBoxEdgesGeometry(geoDown, false, tau);
boxes.children[3].geometry = edgesDown;
// boxes.children[1].geometry = geo;
// boxes.children[1].geometry = geo;
render();
};
$: updateGeo(nBoxes, F, r, u0, u1, v0, v1, sampleParam);
const updateTau = (t) => {
geo.changeT(t);
edgesUp?.dispose();
edgesUp = new FluxBoxEdgesGeometry(geo, false, t);
boxes.children[1].geometry = edgesUp;
geoDown.changeT(t);
edgesDown?.dispose();
edgesDown = new FluxBoxEdgesGeometry(geoDown, false, t);
boxes.children[3].geometry = edgesDown;
render();
};
$: updateTau(tau);
</script>
<div>
<h1>Flux Integrals</h1>
<p>
Let <M>{`\\mathbf r: D\\to \\Omega \\subset \\mathbb{R}^3`}</M> be a parametric
surface oriented with normal <M>{`\\mathbf N`}</M> and <M>
{`\\mathbf F`}
</M> a vector field continuous on <M>{`\\Omega`}</M>.
</p>
<p>
Then the <b>flux</b>
<M>{'\\Phi'}</M> of the vector field through the surface is given by
<M align>
{'\\Phi &= \\iint_\\Omega \\mathbf F\\cdot\\,d\\mathbf S \\\\ &= \\iint_\\Omega \\mathbf F\\cdot\\mathbf N\\,dS \\\\ &= \\iint_D \\mathbf F(\\mathbf r(u,v)) \\cdot \\mathbf r_u \\times \\mathbf r_v\\,dS.'}
</M>
</p>
<p>
We seek to visualize that integrand. Choose an example vector field and
parametric surface.
</p>
<div class="row">
<div class="col-auto">
<M>{'\\Omega:'}</M>
<select
class="demos-obj-select m-2 bg-primary text-light"
bind:value={currentSurface}
>
{#each exampleSurfaces as obj}
<option value={obj}>{obj.title}</option>
{/each}
</select>
</div>
<div class="col-auto">
<button
class=" bg-primary text-light"
on:click={() => {
setRuv(currentSurface);
}}
>
<i class="bi bi-arrow-clockwise" />
</button>
</div>
</div>
<p>
Recall that a triple product <M
>{'\\mathbf a\\cdot \\mathbf b \\times \\mathbf c'}</M
> measures the (signed) volume of a parallelopiped with edges defined by
vectors <M>{'\\mathbf a'}</M>, <M>{'\\mathbf b'}</M>, and <M>
{'\\mathbf c'}
</M>. We thus divide our surface into <M>{'n^2'}</M> pieces. On each, we
take a parallelogram-shaped portion of the tangent plane with edges <M>
{'\\mathbf r_u \\Delta u'}
</M> and <M>{'\\mathbf r_v \\Delta v'}</M> and sample the vector field <M
>{'\\mathbf F'}</M
> as the third edge. Adjust <M>n</M> and the sample point below.
</p>
<div class="row my-3">
<div class="col-auto">
<span class=""><M>{'n = '}</M></span>
<span class=""
><input
type="range"
min="1"
max="20"
step="1"
bind:value={nBoxes}
/></span
>
</div>
<div class="col-auto">
show
<label class="switch box box-3">
<input
type="checkbox"
bind:checked={boxes.visible}
on:change={render}
/>
<span class="slider round" />
</label>
</div>
</div>
<p>
At this point, we are not looking at time-dependent vector fields. That
is, they don't move. Nonetheless, a good way to understand these
structures (and to justify the term "flux") is to imagine <M>
{'\\mathbf F'}</M
> as the velocity field of some fluid. The we can interpret <M>
{'\\Phi'}
</M> as the net volume flowing per unit time through the surface in the direction
of its orientation. By scaling the vector field in time, we can "see" this
volume flowing through the surface. <strong>Warning</strong> the
quantity <M>{'\\Phi'}</M> is computed as a static computation. This is meant
only to help see where positive/negtaive contributions come from.
</p>
<div class="row my-3">
<div class="col-auto">
<span><M>{'t'}</M></span>
<span>
<input
type="range"
min="0"
max="1"
step="0.01"
bind:value={tau}
/>
</span>
</div>
<div class="col-auto">
center
<label class="switch box box-3">
<input
type="checkbox"
checked={false}
on:change={(e) => {
if (e.target.checked) {
sampleParam = 0.5;
} else {
sampleParam = 0;
}
}}
/>
<span class="slider round" />
</label>
</div>
</div>
</div>
<style>
.demos-obj-select {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
/* .blue-button {
background-color: red;
} */
</style>
Loading

0 comments on commit e47dc12

Please sign in to comment.