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}
```
-
+
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.
+
**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.
+
-**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$.
+
+
+
+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.
+
+
+
+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,
});