Here are some words that you may want to read.
You also may not want to read them.
But by now you've already read them.
( out of )
Here are some words that you may want to read.
You also may not want to read them.
But by now you've already read them.
( out of )
I'm a math-minded engineer, motivated by weaving together concepts from different fields to produce novel, impactful solutions to real-world problems. My PhD focused on applying computer vision and machine learning to elastodynamic problems. On this site you will find information about my talents, projects & publications, education, interests, and more.
Scene physics, logic, and artwork hand-rolled by yours truly.
Photo of me rollerblading taken by Catherine Goohs.
Inkscape used for creating leaf art.
Three.js used for scene rendering.
Apache ECharts used to make plots.
MathJax used for math typesetting.
California Institute of Technology Caltech Pasadena, California Pasadena, CA Ph.D. Mechanical Engineering
University of Wisconsin–Madison
UW – Madison
Madison, Wisconsin
Madison, WI
B.S. Engineering Mechanics
B.S. Mathematics
California Institute of Technology Caltech Pasadena, California Pasadena, CA Postdoctoral Scholar Postdoc –
Meta Reality Labs Meta Redmond, Washington Redmond, WA Research Engineer Intern Research Intern –
ATA Engineering ATA San Diego, California San Diego, CA Graduate Engineer Intern Graduate Intern –
University of Wisconsin – Madison UW – Madison Madison, Wisconsin Madison, WI Undergraduate Researcher Undergrad Researcher –
CommScope Shakopee, Minnesota Shakopee, MN Undergraduate Engineer Intern Undergraduate Intern –
Coding languages Matlab, Python, Maple, Visual Basic, JavaScript, HTML, CSS
Machine learning, inference, and optimization Gaussian processes, convolutional neural networks, fitting physics-based models to observed data to perform inference
Finite element analysis (my libraries) 2D-dispersion, 3D-dispersion, tissue-dispersion
Finite element analysis (commercial software) COMSOL Multiphysics, NX Nastran, Abaqus, ANSYS
3D modeling Solidworks, Siemens NX, Autodesk AutoCAD
Git
Spanish
The falling leaves in the background are a physics-based simulation that I built as a fun way to introduce myself to JavaScript. Falling leaves are visually simple and elegant, yet represent an intricate mathematical dance happening in the background. Here, you can take a peek behind the curtain to see how I modeled this beautiful natural process.
In this simulation, each individual leaf is reacting to wind and gravity. By considering how the air flows past a leaf, we can determine how the leaf will drift and tumble.
On this page you can find more technical and mathematical explanations of each facet in the drop-down panels, like you see below.
Each leaf, idealized as a thin disk, responds to wind and gravity. The air's velocity field is a space-time varying sinusoid combined additively with randomly-spawning Gaussian-enveloped vortices (referred to as swirls).
Given the relative velocity between each leaf and the surrounding air, we can compute the forces and moments that act upon each leaf. From there, we can apply Newton's second law and integrate to propagate the state of each leaf over time.
When a leaf wanders too far out of bounds, it fades and despawns, increasing the probability that a new leaf will spawn near the top of the domain.
Except where stated otherwise, everything runs on the
$\left[\text{kg}\cdot\text{m}\cdot\text{s}\right]$ unit system.
$\uvec{i}, \uvec{j}, \uvec{k}$ are unit vectors in the $x, y, z$ directions, respectively
In website space, $\uvec{i}, \uvec{j}, \uvec{k}$ point right, up, and into the page, respectively
Bold symbols such as $\vec{v}$ are vectors in $\mathbb{R}^3$
Hatted symbols such as $\uvec{v}$ are unit vectors
$t$ is time
$\vec{x} = [x,y,z]$ denotes spatial coordinates
$\omega$ is a frequency in $[1/s]$
$\gamma$ is a wavevector (spatial version of frequency) in $[1/m]$
$\propto$ means "proportional to" (i.e. I'm omitting a scaling factor)
$\rho$ signifies density (could be an area density $\left[\text{kg}/\text{m}^2\right]$ or volumetric
density $\left[\text{kg}/\text{m}^3\right]$)
Kinematics is the study and description of motion — positions, orientations, velocities, and accelerations of objects as functions of time. To properly describe how a leaf falls through the breeze, we need to simulate both the linear kinematics (how the leaf is moving in $x, y, z$) and the rotational kinematics (how the leaf flips, spins, and rotates).
Linear kinematics are pretty simple in this simulation. Each leaf has a position, velocity, and acceleration: $\vec{x}_{\text{leaf}}$, $\vec{v}_{\text{leaf}}$, and $\vec{a}_{\text{leaf}}.$ It's easy to propagate these quantities in time because they live in $\mathbb{R}^3$ — meaning that they compose through addition. My simulation uses symplectic Euler for this.
Rotational kinematics are not as simple to model because rotations do not compose through addition (i.e. $\,\boldsymbol{\theta}_{t+\Delta t}\leftarrow \boldsymbol{\theta}_t+\boldsymbol{\omega}\Delta t\,$ doesn't work). Instead, we need to represent rotations and track orientations with quaternions, which live in $\mathbb{R}\times\mathbb{R}^3$. It may seem like a lot, but seeing the leaves rotate is so worth it!
My sim uses quaternions for rotation math.
A quaternion is an ordered pair \(q=(w,\vec{q})\in\mathbb{R}\times\mathbb{R}^3\) with scalar part \(w\) and vector part \(\vec{q}\). We write the quaternion product as \(\otimes\).
$$ (a,\vec{A}) \otimes (b,\vec{B}) = \Big(ab - \vec{A}\!\cdot\!\vec{B},\;\; a\,\vec{B} + b\,\vec{A} + \vec{A}\times\vec{B}\Big). $$A unit quaternion satisfies $q \otimes q^{*} = (\|q\|^{2},\,\vec{0}) = (1,\,\vec{0})$. Unit quaternions parametrize 3D rotations. To rotate a vector $\vec{v}$ by quaternion $\vec{q}$, we embed \(\vec{v}\in\mathbb{R}^3\) as a pure quaternion \((0,\vec{v})\) and sandwich multiply: $$\mathcal{R}_q(\vec{v}) \;=\; \big(q \otimes (0,\vec{v}) \otimes q^{-1}\big)$$ where, since the result is a quaternion in $\mathbb{R}\times\mathbb{R}^3$, we take only the vector part as the result of the rotation.
We express angular acceleration $\alpha$ and angular velocity $\omega$ in the inertial frame (as opposed to the local frame), and our quaternion \(q\) maps local→world.
We integrate angular velocity by treating it as constant over each small step. The kinematic ODE \[ \dot q=\tfrac12\,(0,\boldsymbol{\omega})\otimes q \] has the per-step solution \[ q_{n+1}=\underbrace{\exp\!\big(\tfrac12(0,\boldsymbol{\omega}_{n+1})\Delta t\big)}_{\text{unit quaternion with axis }\hat{\boldsymbol{\omega}}_{n+1}\text{, and angle }\|\boldsymbol{\omega}_{n+1}\|\Delta t}\ \otimes\ q_n. \] This update rule can be read more simply as $q\leftarrow dq\otimes q$.
Wind is just air that's moving. But in real life, air movement isn't the same everywhere, and it doesn't always move in the same way. The wind in this simulation reflects that real-life variation — it's a function of both space and time.
In this sim, the background breeze gently blows the leaves left and right. To stir things up, short-lived eddies sporadically appear to swirl the leaves around.
The background field is a space-time varying sinusoid:
$$\vec{v}_{\text{background}}(\mathbf{x},t)\propto\sin(2\pi\gamma\,y)\sin(2\pi\omega\,t)\uvec{i}.$$
Each swirl is a Gaussian vortex about the $\uvec{k}$-axis, with on-off behavior modulated by smoothed Heaviside envelopes: $$\vec{v}_{\text{swirl}}(\mathbf{x},t)\propto e^{-\tfrac12(r/R)^2}\operatorname{env}(t)\symuvec{\phi}, \quad\symuvec{\phi}=\uvec{k}\times\uvec{r}.$$ where $\operatorname{env}(t,t_{0},t_{1}) = \operatorname{H_{s,2}}(t-t_{0})\operatorname{H_{s,2}}(t_{1} - t)$ turns the vortex on and off with a smoothed Heaviside function at times $t_{0}$ and $t_{1}$ respectively. Here, position vector $\vec{r} = \vec{x}-\vec{x_{\text{swirl}}}$, where $\vec{x_{\text{swirl}}}$ is the center of the swirl. Distance from the swirl center is $r = \norm{\vec{r}}$. Radial size variable $R$ scales the spatial size of the swirl.
The total velocity field is the background field, plus any existing swirls: $$\vec{v}_{\text{air}}(\vec{x},t) = \vec{v}_{\text{background}}(\vec{x},t) + \sum_{\text{swirls}}{\vec{v}_{\text{swirl}}(\vec{x},t)}.$$
The relative air velocity dictates how each leaf will move. If a leaf is already moving the same way as the surrounding air, it'll just continue to go with the flow — no extra effect from the wind. If not, the wind will encourage the leaf to change what it's doing, causing it to drift, lift, float, and tumble.
The relative velocity between the air and each leaf is computed as:
$$\vec{v}_{\text{rel}} = \vec{v}_{\text{air}} - \vec{v}_{\text{leaf}}$$By idealizing each leaf as a thin disk, we can compute the aerodynamic forces and moments analytically. The aerodynamic force is computed as:
$$\vec{F}_{\text{aero}} = -\frac{1}{2}\,\rho_{\text{air}}\,A_{\text{leaf}}\,\norm{\vec{v}_{\text{rel}}}^2\!\left[ \vec{F}_{\text{drag}} + \vec{F}_{\text{normal}} \right].$$ $$\vec{F}_{\text{drag}} = C_D(\theta)\,\uvec{v}_{\text{rel}}$$ $$\vec{F}_{\text{normal}} = C_N(\theta)\,\operatorname{sgn}\!\big(\uvec{n}_{\text{leaf}}\!\cdot\!\uvec{v}_{\text{rel}}\big)\,\uvec{n}_{\text{leaf}}$$The coefficients of drag depend on the angle $\theta$ between the relative velocity $(\uvec{v}_{\text{rel}})$ and the leaf normal $(\uvec{n}_{\text{leaf}})$:
$$C_D(\theta)=C_{d,\parallel}+\big(C_{d,\perp}-C_{d,\parallel}\big)\cos^2\!\theta$$ $$C_N(\theta)=C_{N,\max}\sin(2\theta).$$We offset the center of pressure a small fraction of the radius along the in-plane flow direction, then compute the moment by a lever-arm $\times$ force cross-product. $$\vec{r} = -a_{\mathrm{CoP}}R\uvec{v}_{\text{rel},\parallel}$$
$$\vec{M}_{\text{aero}} = \mathbf{r}\times \mathbf{F}_{\mathrm{aero}}$$The simulation takes place in a finite 3D box of space, but runs in time for as long as the page is open.
As you scroll, the camera gravitates toward a new target position — but it doesn't go there immediately. To make camera movements smooth, we use frame-rate-independent exponentially-damped movement.
Let $\vec{x}_\star$ denote the camera's current target position (computed directly from scroll location). To move the camera to that location smoothly, we apply $$\vec{x}_{t+\Delta t} = \vec{x}_\star + (\vec{x}_{t} - \vec{x}_\star)e^{-\tau\Delta t}$$ where damping rate $\tau > 0$ controls how snappy/laggy the transition feels.
When a leaf goes too far out-of-bounds, it fades and disappears. Without creating new leaves, all the leaves would eventually fall out of view, leaving an empty scene. But if I create new leaves too quickly, we may end up with way too many leaves, and it can look a bit overwhelming. We use a self-balancing method to keep the vibe going that adjusts how quickly new leaves are created based on how many currently exist.
Initially, the scene is seeded with $N_{0}$ leaves. Over time, aerodynamics or gravity eventually cause them to leave the simulated domain and are culled.
New leaves are probabilistically spawned via a Poisson distribution. The number of leaves that will spawn during a given time-step is drawn as: $$N_{\text{new}} \sim \operatorname{Poisson}(\lambda \Delta t)$$ $$\lambda = \lambda_0 \operatorname{max}(0,N_{\text{max}} - N)/N_{\text{max}}$$ By this construction, the spawn rate $\lambda$ linearly interpolates between $0$ and the base spawn rate $\lambda_{0}$ based on the current population $N$ and the maximum population $N_{\text{max}}$. If $N \geq N_{\text{max}}$, then $\lambda = 0$ (no new leaves will spawn). If $N = 0$, then $\lambda = \lambda_{0}$ (leaves will spawn at the base spawn rate). This spawn logic is frame-rate independent.
Swirls follow the same spawning logic, but are culled differently from leaves. Each swirl has a set time duration, after which it fades out.
In nature, no two leaves look exactly alike. To reflect this in my sim, I layer different types of structured randomness. I designed a handful of different leaf geometries and color palettes. Each color palette has several colors to choose from, but are designed such that colors from the same palette look reasonable together. When a leaf is created, it randomly picks a geometry and a color palette. Then, the leaf uses the color palette to randomly color each of the regions in the geometry. This gives each leaf a unique yet coordinated look.
Of course, two leaves could randomly end up identical. In the current configuration, two leaves in 2349810509294 will be exactly the same.
The sim runs on fixed physics time-steps at 120 Hz, but allows rendering skips with a frame-time accumulator and a cap on substeps. That way, even if you open this website on your smart-fridge, you can still enjoy some true-to-physics leaves, though possibly at a lower frame rate.