diff --git a/README.md b/README.md index bb42cbf..eb67fc1 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,12 @@ Published in [ACM Transactions on Graphics (TOG)](https://dl.acm.org/doi/abs/10. - 🔥 A modern NVIDIA GPU (Turing or newer). - 🐳 A Docker environment (see [below](#-getting-started)). +## 📝 Change History + +- (2024.12.18) Added a [frictional contact example](./examples/friction.ipynb): armadillo sliding on the slope [[Video]](https://drive.google.com/file/d/12WGdfDTFIwCT0UFGEZzfmQreM6WSSHet/view?usp=sharing). +- (2024.12.18) Added a [hindsight](./articles/hindsight.md) noting that the tilt angle was not $30^\circ$, but rather $26.57^\circ$. +- (2024.12.16) Removed thrust dependencies to fix runtime errors for the driver version `560.94` [[Issue Link]](https://github.com/st-tech/ppf-contact-solver/issues/1). + ## 🐍 How To Use Our frontend is accessible through a browser using our built-in JupyterLab interface (see this video [[Video]](https://drive.google.com/file/d/1n068Ai_hlfgapf2xkAutOHo3PkLpJXA4/view?usp=sharing)). All is set up when you open it for the first time. diff --git a/articles/eigensys.md b/articles/eigensys.md index cc6ecd1..373e0e4 100644 --- a/articles/eigensys.md +++ b/articles/eigensys.md @@ -15,7 +15,7 @@ Since the materials below are lengthy, we provide a summary of the final results - If the energy is expressed in terms of invariants, eigen-filtered force jacobian can be obtained in closed form by using only $\frac{\partial \Psi}{\partial I_k}$ and $\frac{\partial^2 \Psi}{\partial I_k^2}$, where $I_k$ are the Cauchy-Green invariants or those from Smith et al. [[1]](#1). -- Our eigen system is idential to the one by Smith et al. [[1]](#1). +- Our analysis can be used to re-derive exactly the same system from Smith et al. [[1]](#1). ## 📖 What's the Story? @@ -473,9 +473,9 @@ Similar to our 3D case, two vectors $[a_1, a_2]$, $[b_1, b_2]$ and their corresp Everything else is exactly the same as the 3D case presented above. We provide another Python code [[Code]](../eigsys/eigsys_2.py) to numerically verify this analysis. -## 🎓 Equivalence with Smith et al. [[1]](#1) +## 🎓 Re-Deriving Smith et al. [[1]](#1) -Our technique yields the same eigen system revealed by Smith et al. [[1]](#1). +Our technique can be used to arrive at the same eigen system revealed by Smith et al. [[1]](#1). We can confirm this by simply swapping the Cauchy-Green invariants with those of Smith et al. [[1]](#1) and substitute them into our eigenvalue expressions. This is simple enough to do manually, but I’ve written a SymPy code to help facilitate the task: @@ -497,6 +497,7 @@ display(ratsimp((E.diff(b) - E.diff(c)) / (b - c))) Now look at Equations (7.16 to 7.21) from [[B]](#B). The output is identical. +This can't be a coincidence. [B] Theodore Kim and David Eberle. 2022. Dynamic deformables: implementation and production practicalities (now with code!). In ACM SIGGRAPH 2022 Courses (SIGGRAPH '22). https://doi.org/10.1145/3532720.3535628 @@ -563,7 +564,7 @@ display(simplify(H3x3-A)) This prints zero! 😲 But if you tweak the way $A$ is computed a little bit, it gives non-zero expressions 🔢, so this must be correct ✅. This also confirms that their encoded matrix $A$ corresponds to $\frac{\partial^2 \Psi}{\partial \sigma^2}$. -Now we have proven that our eigen system is exactly the same as Smith et al. [[1]](#1). +Now we have proven that our eigen analysis can re-derive the same system as Smith et al. [[1]](#1). ## 🍱 Takeaway C/C++ ⚙️ and Rust 🦀 Codes diff --git a/articles/hindsight.md b/articles/hindsight.md index 121854f..0132a7f 100644 --- a/articles/hindsight.md +++ b/articles/hindsight.md @@ -10,7 +10,7 @@ In the paper we presented the following barrier as a quadratic energy counterpar \end{equation} ```
-equation +equation
However, soon after the publication, we realized that this was not the best counterpart since its curvature is a constant $\kappa / \hat{g}$. @@ -31,9 +31,11 @@ We show an example and discuss why. When the above new quadratic barrier is used, visual artifacts may emerge **when contacts are lightly touched** as shown in Figure A. +
snag artifacts **Figure A:** Domino scene. Noticeable snags occur when one domino pushes the next ones. +
### 🔄 The Sources of Artifacts @@ -50,6 +52,25 @@ This can be seen in Figure B. As a result, when $g \approx \hat{g}$ (that is, contacts are lightly touched), the conditioning of the system unnecessarily stiffens, leading to possible artifacts. +
graph -**Figure B:** Visualizing the transition of the magnitude of both our cubic barrier and a quadratic counterpart. \ No newline at end of file +**Figure B:** Visualizing the transition of the magnitude of both our cubic barrier and a quadratic counterpart. +
+ +### Tilted Slope (Section 5.8) + +In Section 5.8, we stated that the slope was tilted at an angle of $30^\circ$. + +
equation
+ +However, this was incorrect. +The actual tilt was set up such that gravity is split into two orthogonal components, with their ratio being 2:1, as shown in Figure C. + +
tilt divide + +Figure C: Actual tilt configuration used in the paper example. +
+ +That is, the actual tilt angle is approximately $26.57^\circ$. +We chose this setup because, in this way, the sliding object eventually stops its motion when $\mu > 0.5$ and keeps sliding if $\mu < 0.5$. diff --git a/asset/image/hindsight/tilt-eratta-text.png b/asset/image/hindsight/tilt-eratta-text.png new file mode 100644 index 0000000..7a7a947 Binary files /dev/null and b/asset/image/hindsight/tilt-eratta-text.png differ diff --git a/asset/image/hindsight/tilt-friction.png b/asset/image/hindsight/tilt-friction.png new file mode 100644 index 0000000..0e9992b Binary files /dev/null and b/asset/image/hindsight/tilt-friction.png differ diff --git a/examples/friction.ipynb b/examples/friction.ipynb new file mode 100644 index 0000000..5957287 --- /dev/null +++ b/examples/friction.ipynb @@ -0,0 +1,112 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "1845cfba-4cda-4917-905c-2991ada078b1", + "metadata": {}, + "outputs": [], + "source": [ + "from frontend import App\n", + "import numpy as np\n", + "\n", + "app = App(\"friction\", renew=True)\n", + "\n", + "V, F, T = app.mesh.preset(\"armadillo\").decimate(15000).tetrahedralize().normalize()\n", + "app.asset.add.tet(\"armadillo\", V, F, T)\n", + "\n", + "V, F = app.mesh.rectangle(res_x=33, width=15.0, height=3)\n", + "app.asset.add.tri(\"slope\", V, F)\n", + "\n", + "scene = app.scene.create(\"friction\")\n", + "armadillo = scene.add(\"armadillo\")\n", + "armadillo.rotate(180,\"y\").rotate(-90,\"x\").rotate(-30,\"z\").at(-5,3,0.1)\n", + "\n", + "deg = 180*np.arctan(0.5)/np.pi\n", + "scene.add(\"slope\").rotate(-90,\"x\").rotate(-deg,\"z\").pin()\n", + "\n", + "fixed = scene.build().report()\n", + "fixed.preview();" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17242000-e3a2-456a-a718-02c1b90e6bd8", + "metadata": {}, + "outputs": [], + "source": [ + "param = app.session.param()\n", + "param.set(\"volume-poiss-rat\",0.49)\n", + "param.set(\"dt\",0.01).set(\"min-newton-steps\",32);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8910b401-1153-4744-9413-1987bddc05c0", + "metadata": {}, + "outputs": [], + "source": [ + "param.set(\"friction\",0.49).set(\"frames\",650);\n", + "session_049 = app.session.create(\"armadillo-friction-0.49\").init(fixed)\n", + "session_049.start(param).preview();\n", + "session_049.stream();" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bb40f3a8-8539-4b64-9b1d-aa13c3461461", + "metadata": {}, + "outputs": [], + "source": [ + "session_049.animate();" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cbb5ebeb-2bb1-43a3-ab52-863ec39c4a05", + "metadata": {}, + "outputs": [], + "source": [ + "param.set(\"friction\",0.51).set(\"frames\",800);\n", + "session_051 = app.session.create(\"armadillo-friction-0.51\").init(fixed)\n", + "session_051.start(param).preview();\n", + "session_051.stream();" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d70cac9b-43b9-4355-9426-6953dea667dd", + "metadata": {}, + "outputs": [], + "source": [ + "session_051.animate();" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/frontend/_app_.py b/examples/frontend/_app_.py index 2923811..889174e 100644 --- a/examples/frontend/_app_.py +++ b/examples/frontend/_app_.py @@ -17,7 +17,9 @@ class App: def __init__(self, name: str, renew: bool = False, cache_dir: str = ""): self.extra = Extra() self._name = name - self._root = os.path.expanduser(os.path.join("~", ".local", "ppf-cts", name)) + self._root = os.path.expanduser( + os.path.join("~", ".local", "share", "ppf-cts", name) + ) self._path = os.path.join(self._root, "app.pickle") proj_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) if cache_dir: diff --git a/examples/frontend/_mesh_.py b/examples/frontend/_mesh_.py index 2437325..d92ac75 100644 --- a/examples/frontend/_mesh_.py +++ b/examples/frontend/_mesh_.py @@ -25,22 +25,27 @@ def box(self, width: float = 1, height: float = 1, depth: float = 1) -> "TriMesh o3d.geometry.TriangleMesh.create_box(width, height, depth) ) - def square(self, res: int = 32, size: float = 2) -> "TriMesh": - dx = size / (res - 1) - x = -size / 2 + dx * np.arange(res) - y = x + def rectangle( + self, res_x: int = 32, width: float = 2, height: float = 1 + ) -> "TriMesh": + ratio = height / width + res_y = int(res_x * ratio) + size_x, size_y = width, width * (res_y / res_x) + dx = min(size_x / (res_x - 1), size_y / (res_y - 1)) + x = -size_x / 2 + dx * np.arange(res_x) + y = -size_y / 2 + dx * np.arange(res_y) X, Y = np.meshgrid(x, y, indexing="ij") X_flat, Y_flat = X.flatten(), Y.flatten() Z_flat = np.full_like(X_flat, 0) vert = np.vstack((X_flat, Y_flat, Z_flat)).T - n_faces = 2 * (res - 1) ** 2 + n_faces = 2 * (res_x - 1) * (res_y - 1) tri = np.zeros((n_faces, 3), dtype=np.int32) tri_idx = 0 - for j in range(res - 1): - for i in range(res - 1): - v0 = j * res + i + for j in range(res_y - 1): + for i in range(res_x - 1): + v0 = i * res_y + j v1 = v0 + 1 - v2 = v0 + res + v2 = v0 + res_y v3 = v2 + 1 if (i % 2) == (j % 2): tri[tri_idx] = [v0, v1, v3] @@ -51,6 +56,9 @@ def square(self, res: int = 32, size: float = 2) -> "TriMesh": tri_idx += 2 return TriMesh.create(vert, tri, self._cache_dir) + def square(self, res: int = 32, size: float = 2) -> "TriMesh": + return self.rectangle(res, size, size) + def circle(self, n: int = 32, r: float = 1, ntri: int = 1024) -> "TriMesh": pts = [] for i in range(n): diff --git a/src/scene.rs b/src/scene.rs index aa5630f..979e7f7 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -284,6 +284,8 @@ impl Scene { let nx = read_f32(count, "nx"); let ny = read_f32(count, "ny"); let nz = read_f32(count, "nz"); + let mut normal = Vector3::new(nx, ny, nz); + normal.normalize_mut(); let position = read_mat_from_file::(&format!("{}/bin/wall-pos-{}.bin", args.path, i)) .expect("Failed to read pos_path"); @@ -292,7 +294,7 @@ impl Scene { assert_eq!(position.ncols(), n_keyframe as usize); assert_eq!(wall_timing.len(), n_keyframe); wall.push(InvisibleWall { - normal: Vector3::new(nx, ny, nz), + normal, position, timing: wall_timing, });