Skip to content

Commit cc41052

Browse files
committed
Added cloth simulation example
1 parent 8cc10b3 commit cc41052

File tree

12 files changed

+245
-87
lines changed

12 files changed

+245
-87
lines changed

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ <h1 class="text-center display-4">Sketching</h1>
8787
<div class="col-12 col-sm-4">
8888
<a
8989
class="btn btn-primary btn-lg btn-block"
90-
href="/sketching/"
90+
href="/mesh/"
9191
role="button"
9292
>Mesh Sketching</a
9393
>
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

realtime/cloth.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { vec3 } from "gl-matrix";
2+
3+
//https://github.com/regl-project/regl/blob/gh-pages/example/cloth.js
4+
export class Cloth {
5+
constructor(rows, cols) {
6+
this.t = 0;
7+
8+
this.rows = rows;
9+
this.cols = cols;
10+
this.positions = [];
11+
this.oldPositions = [];
12+
this.normals = [];
13+
this.triangles = [];
14+
this.edges = []
15+
this.acc = []
16+
this.width = 2.0;
17+
this.height = this.width * (this.rows - 1) / (this.cols - 1);
18+
this.pins = [(this.rows - 1) * this.cols, (this.rows - 1) * this.cols + 1, this.rows * this.cols - 2, this.rows * this.cols - 1];
19+
const restLength = this.width / (this.cols - 1) * 0.95;
20+
const diagonalRestLength = Math.sqrt(2) * restLength;
21+
22+
for (let i = 0; i < this.rows; i++) {
23+
for (let j = 0; j < this.cols; j++) {
24+
let y = i / (this.rows - 1) * this.height - this.height / 2.0;
25+
let x = j / (this.cols - 1) * this.width - this.width / 2.0;
26+
this.positions.push([x, y, 0.0]);
27+
this.oldPositions.push([x, y, 0.0]);
28+
this.normals.push([0.0, 0.0, 1.0]);
29+
this.acc.push([0.0, 0.0, 0.0]);
30+
}
31+
}
32+
33+
for (let i = 0; i < this.rows; i++) {
34+
for (let j = 0; j < this.cols; j++) {
35+
if (i > 0 && j > 0) {
36+
this.triangles.push([i * this.cols + j, i * this.cols + j - 1, (i - 1) * this.cols + j - 1])
37+
this.triangles.push([i * this.cols + j, (i - 1) * this.cols + j - 1, (i - 1) * this.cols + j])
38+
39+
this.edges.push([i * this.cols + j, i * this.cols + j - 1, restLength])
40+
this.edges.push([i * this.cols + j - 1, (i - 1) * this.cols + j - 1, restLength])
41+
this.edges.push([(i - 1) * this.cols + j - 1, i * this.cols + j, diagonalRestLength])
42+
this.edges.push([(i - 1) * this.cols + j, i * this.cols + j - 1, diagonalRestLength])
43+
this.edges.push([(i - 1) * this.cols + j - 1, (i - 1) * this.cols + j, restLength])
44+
this.edges.push([(i - 1) * this.cols + j, i * this.cols + j, restLength])
45+
}
46+
if (i > 0 && i < this.rows - 1) {
47+
this.edges.push([(i - 1) * this.cols + j, (i + 1) * this.cols + j, 2.0 * restLength]);
48+
}
49+
if (j > 0 && j < this.cols - 1) {
50+
this.edges.push([i * this.cols + j - 1, i * this.cols + j + 1, 2.0 * restLength]);
51+
}
52+
}
53+
}
54+
}
55+
56+
step() {
57+
const MASS = 1.0;
58+
const HOOKE = 1.0;
59+
const DRAG = 0.03;
60+
const DT = 0.01;
61+
this.windForce = [Math.sin(this.t / 20.0), Math.cos(this.t / 30.0), Math.sin(this.t / 10.0)];
62+
vec3.normalize(this.windForce, this.windForce);
63+
vec3.scale(this.windForce, this.windForce, 0.5);
64+
this.gravity = [0.0, -1.0, 0.0];
65+
66+
67+
for (let i = 0 ; i < this.positions.length; i++) {
68+
let newPos = [];
69+
vec3.scale(newPos, this.positions[i], 2.0 - DRAG);
70+
vec3.scaleAndAdd(newPos, newPos, this.oldPositions[i], DRAG - 1.0);
71+
vec3.scaleAndAdd(newPos, newPos, this.acc[i], 0.5 * DT * DT);
72+
this.oldPositions[i] = this.positions[i];
73+
this.positions[i] = newPos;
74+
75+
this.normals[i] = [0.0, 0.0, 0.0];
76+
}
77+
console.log(this.positions[0], this.oldPositions[0]);
78+
79+
for (let i = 0; i < this.pins.length; i++) {
80+
this.positions[this.pins[i]] = this.oldPositions[this.pins[i]];
81+
}
82+
83+
for (let i = 0; i < this.triangles.length; i++){
84+
let u = this.triangles[i][0], v = this.triangles[i][1], w = this.triangles[i][2];
85+
let euv = [], euw = [], n = [];
86+
vec3.sub(euv, this.positions[u], this.positions[v]);
87+
vec3.sub(euw, this.positions[u], this.positions[w]);
88+
vec3.cross(n, euv, euw);
89+
vec3.add(this.normals[u], this.normals[u], n);
90+
vec3.add(this.normals[v], this.normals[v], n);
91+
vec3.add(this.normals[w], this.normals[w], n);
92+
}
93+
94+
for (let i = 0; i < this.positions.length; i++) {
95+
vec3.normalize(this.normals[i], this.normals[i]);
96+
vec3.set(this.acc[i], 0.0, 0.0, 0.0);
97+
vec3.add(this.acc[i], this.acc[i], this.gravity);
98+
vec3.scaleAndAdd(this.acc[i], this.acc[i], this.normals[i], vec3.dot(this.normals[i], this.windForce));
99+
}
100+
console.log(this.acc[0]);
101+
102+
for (let i = 0; i < this.edges.length; i++) {
103+
let u = this.edges[i][0], v = this.edges[i][1], l = this.edges[i][2];
104+
let e = [], diff = [];
105+
let invM = 1.0 * this.rows * this.cols / MASS;
106+
vec3.sub(e, this.positions[u], this.positions[v]);
107+
//if (vec3.length(e) < 1e-6 * this.width / (this.cols - 1)) {
108+
// continue;
109+
//}
110+
vec3.scale(diff, e, (l - vec3.length(e)) / vec3.length(e));
111+
vec3.scaleAndAdd(this.acc[u], this.acc[u], diff, HOOKE * invM);
112+
vec3.scaleAndAdd(this.acc[v], this.acc[v], diff, -HOOKE * invM);
113+
}
114+
115+
this.t += DT;
116+
117+
let fullPositions = [];
118+
let fullNormals = [];
119+
let fullElements = [];
120+
let fullIndexInTriangle = [];
121+
122+
for (let i = 0; i < this.triangles.length; i++) {
123+
let u = this.triangles[i][0], v = this.triangles[i][1], w = this.triangles[i][2];
124+
fullElements.push(6 * i, 6 * i + 1, 6 * i + 2, 6 * i + 3, 6 * i + 4, 6 * i + 5);
125+
//fullElements.push(3 * i, 3 * i + 1, 3 * i + 2);
126+
fullPositions.push(this.positions[u], this.positions[v], this.positions[w]);
127+
let back = [], eps = 1e-1;
128+
vec3.scaleAndAdd(back, this.positions[u], this.normals[u], -eps);
129+
fullPositions.push(back);
130+
vec3.scaleAndAdd(back, this.positions[w], this.normals[w], -eps);
131+
fullPositions.push(back);
132+
vec3.scaleAndAdd(back, this.positions[v], this.normals[v], -eps);
133+
fullPositions.push(back);
134+
fullNormals.push(this.normals[u], this.normals[v], this.normals[w]);
135+
let neg = [];
136+
vec3.scale(neg, this.normals[u], -1.0);
137+
fullNormals.push(neg);
138+
vec3.scale(neg, this.normals[w], -1.0);
139+
fullNormals.push(neg);
140+
vec3.scale(neg, this.normals[v], -1.0);
141+
fullNormals.push(neg);
142+
fullIndexInTriangle.push(0, 1, 2, 0, 1, 2);
143+
//fullIndexInTriangle.push(0, 1, 2);
144+
}
145+
return {
146+
"elements": fullElements,
147+
attributes: {
148+
"position": fullPositions,
149+
"normal": fullNormals,
150+
"indexInTriangle": fullIndexInTriangle,
151+
},
152+
};
153+
}
154+
}

realtime/frag2.glsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ vec3 getMinDirection(vec3 ax, vec3 az, mat2 LMN){
113113
}
114114

115115
void main() {
116-
float eps = 2.0;
116+
float eps = 3.0;
117117
vec2 tp = gl_FragCoord.xy / resolution.xy;
118118
vec2 dx = eps * vec2(1.0, 0.0) / resolution.x;
119119
vec2 dy = eps * vec2(0.0, 1.0) / resolution.y;

realtime/frag3.glsl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ vec2 screenspace(vec3 dir) {
2828
return (mvp2.xy / mvp2.w) - (mvp.xy / mvp.w);
2929
}
3030

31-
vec4 sample(vec2 brightness, vec3 basepoint, vec2 curvature) {
31+
vec4 sample(vec2 brightness, vec3 basepoint) {
3232
vec2 device = (gl_FragCoord.xy - basepoint.xy / basepoint.z) / resolution.xy;
33-
curvature = screenspace(texture2D(directionTex, gl_FragCoord.xy/resolution.xy).xyz);
33+
vec2 curvature = screenspace(texture2D(directionTex, basepoint.xy / basepoint.z / resolution.xy).xyz);
3434
vec2 dir = length(curvature) > 0.0 ? normalize(curvature) : vec2(0.0, 1.0);
3535
vec2 uv = vec2(dot(dir, device), -dir.y * device.x + dir.x * device.y);
3636

@@ -41,9 +41,9 @@ vec4 sample(vec2 brightness, vec3 basepoint, vec2 curvature) {
4141
}
4242

4343
void main() {
44-
vec4 textureA = sample(brightnessA, vCoordA, vCurvatureA) * brightnessA.y;
45-
vec4 textureB = sample(brightnessB, vCoordB, vCurvatureB) * brightnessB.y;
46-
vec4 textureC = sample(brightnessC, vCoordC, vCurvatureC) * brightnessC.y;
44+
vec4 textureA = sample(brightnessA, vCoordA) * brightnessA.y;
45+
vec4 textureB = sample(brightnessB, vCoordB) * brightnessB.y;
46+
vec4 textureC = sample(brightnessC, vCoordC) * brightnessC.y;
4747

4848
vec3 baseColor = (textureA + textureB + textureC).xyz;
4949
float vDotN = dot(normalize(vNormal), normalize(vPosition - eye));

realtime/index.js

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ import teapotUrl from "../models/clean_teapot.json?url";
1919
import armadilloUrl from "../models/armadillo.json?url";
2020
import csgUrl from "../models/clean_csg.json?url";
2121
import torusUrl from "../models/torus.json?url";
22+
import { Cloth } from "./cloth.js";
2223

2324
const meshes = {
25+
"Cloth": "Cloth",
2426
"Bunny Large": bunnyLargeUrl,
2527
"Bunny Small": bunnySmallUrl,
2628
"Utah Teapot": teapotUrl,
@@ -46,12 +48,13 @@ const camera = createCamera(document.getElementsByTagName("canvas")[0], {
4648
const [pane, params] = initPane();
4749

4850
let numTextures, pencilTextures, attributes, elements;
51+
let cloth = null;
4952

5053
function initPane() {
5154
const pane = new Tweakpane({ title: "Parameters" });
5255
const params = {
5356
scale: 15,
54-
mesh: bunnyLargeUrl,
57+
mesh: "Cloth",
5558
zoom: 0.8,
5659
height: 0.2,
5760
rotate: true,
@@ -108,19 +111,29 @@ function generateTextures(texParams) {
108111
}
109112

110113
async function updateMesh() {
111-
const resp = await fetch(params.mesh);
112-
const mesh = await resp.json();
113-
const data = loadMesh(mesh);
114-
attributes = data.attributes;
115-
elements = data.elements;
114+
if (params.mesh === "Cloth") {
115+
if (cloth === null) {
116+
cloth = new Cloth(25, 50);
117+
}
118+
const data = cloth.step();
119+
attributes = data.attributes;
120+
elements = data.elements;
121+
}
122+
else {
123+
const resp = await fetch(params.mesh);
124+
const mesh = await resp.json();
125+
const data = loadMesh(mesh);
126+
attributes = data.attributes;
127+
elements = data.elements;
128+
}
116129
}
117130

118131
const common = regl({
119132
attributes: {
120133
position: () => attributes.position,
121134
normal: () => attributes.normal,
122135
indexInTriangle: () => attributes.indexInTriangle,
123-
curvature: () => attributes.curvature,
136+
//curvature: () => attributes.curvature,
124137
},
125138
elements: () => elements,
126139
uniforms: {
@@ -195,6 +208,9 @@ Promise.all([initTextures(), updateMesh()]).then(() => {
195208
center: camera.center,
196209
},
197210
() => {
211+
if (params.mesh === "Cloth") {
212+
updateMesh();
213+
}
198214
fboA.resize(viewportWidth, viewportHeight);
199215
fboB.resize(viewportWidth, viewportHeight);
200216
regl.clear({ framebuffer: fboA, color: [0, 0, 0, 0], depth: 1000.0 });

0 commit comments

Comments
 (0)