]> gitweb.ps.run Git - cloth_sim/blob - Scripts/cloth.js
tweaking parameters
[cloth_sim] / Scripts / cloth.js
1 const DAMPING = 0.03;\r
2 const DRAG = 1 - DAMPING;\r
3 const MASS = 0.35;\r
4 const GRAVITY = new THREE.Vector3(0, -9.81 * MASS, 0);\r
5 const K = 1;\r
6 \r
7 const options = {\r
8   wind: true,\r
9 };\r
10 \r
11 class Constraint {\r
12   constructor(p1, p2, restDist) {\r
13     this.p1 = p1;\r
14     this.p2 = p2;\r
15     this.restDist = restDist;\r
16   }\r
17 \r
18   satisfy() {\r
19     const diff = this.p2.position.clone().sub(this.p1.position);\r
20     const currentDist = diff.length();\r
21     if (currentDist == 0) return;\r
22     if (currentDist <= this.restDist) return;\r
23     //const correction = diff.multiplyScalar(1 - (this.restDist / currentDist));\r
24     const correction = diff.multiplyScalar((currentDist - this.restDist) / currentDist);\r
25     correction.multiplyScalar(K);\r
26     correction.clampLength(0, 1);\r
27     const correctionHalf = correction.multiplyScalar(0.5);\r
28     if (this.p1.movable && this.p2.movable) {\r
29       this.p1.position.add(correctionHalf);\r
30       this.p2.position.sub(correctionHalf);\r
31     } else if (! this.p1.movable && this.p2.movable) {\r
32       this.p2.position.sub(correction);\r
33     } else if (this.p1.movable && ! this.p2.movable) {\r
34       this.p1.position.add(correction);\r
35     }\r
36   }\r
37 }\r
38 \r
39 class Particle {\r
40   movable = true;\r
41 \r
42   constructor(x, y, z, mass) {\r
43     this.position = new THREE.Vector3(x, y, z);\r
44     this.previous = new THREE.Vector3(x, y, z);\r
45     this.acceleration = new THREE.Vector3(0, 0, 0);\r
46     this.mass = mass;\r
47   }\r
48   addForce(force) {\r
49     this.acceleration.add(\r
50       force.clone().multiplyScalar(1/this.mass)\r
51     );\r
52   }\r
53   verlet(dt) {\r
54     // verlet algorithm\r
55     // next position = 2 * current Position - previous position + acceleration * (passed time)^2\r
56     // acceleration (dv/dt) = F(net)\r
57     const nextPosition = this.position.clone().sub(this.previous);\r
58     nextPosition.multiplyScalar(DRAG);\r
59     nextPosition.add(this.position);\r
60     nextPosition.add(this.acceleration.multiplyScalar(dt*dt));\r
61 \r
62     if (this.movable) {\r
63       this.previous = this.position;\r
64       this.position = nextPosition;\r
65     }\r
66 \r
67     this.acceleration.set(0, 0, 0);\r
68   }\r
69 }\r
70 \r
71 class Cloth {\r
72   constructor(width, height, numPointsWidth, numPointsHeight) {\r
73     this.width = width;\r
74     this.height = height;\r
75     this.numPointsWidth = numPointsWidth;\r
76     this.numPointsHeight = numPointsHeight;\r
77     this.windFactor = new THREE.Vector3(5, 2, 2);\r
78 \r
79     /**\r
80      * distance between two vertices horizontally/vertically\r
81      * divide by the number of points minus one\r
82      * because there are (n - 1) lines between n vertices\r
83      */\r
84     let stepWidth = width / (numPointsWidth - 1);\r
85     let stepHeight = height / (numPointsHeight - 1);\r
86 \r
87     /**\r
88      * iterate over the number of vertices in x/y axis\r
89      * and add a new Particle to "particles"\r
90      */\r
91     this.particles = [];\r
92     for (let y = 0; y < numPointsHeight; y++) {\r
93       for (let x = 0; x < numPointsWidth; x++) {\r
94         this.particles.push(\r
95           new Particle(\r
96             (x - ((numPointsWidth-1)/2)) * stepWidth,\r
97             height - (y + ((numPointsHeight-1)/2)) * stepHeight,\r
98             0,\r
99             MASS)\r
100         );\r
101       }\r
102     }\r
103 \r
104     //this.particles[this.getVertexIndex(0, 0)].movable = false;\r
105     const n = 3;\r
106     for (let i = 0; i <= n; i++)\r
107       this.particles[this.getVertexIndex(0, Math.floor((numPointsHeight-1)*(i/n)))].movable = false;\r
108     //this.particles[this.getVertexIndex(0, numPointsHeight-1)].movable = false;\r
109     //this.particles[this.getVertexIndex(numPointsWidth-1, 0)].movable = false;\r
110 \r
111     const REST_DIST_X = width / (numPointsWidth-1);\r
112     const REST_DIST_Y = height / (numPointsHeight-1);\r
113 \r
114     /**\r
115      * generate constraints (springs)\r
116      */\r
117     this.constraints = [];\r
118     for (let y = 0; y < numPointsHeight; y++) {\r
119       for (let x = 0; x < numPointsWidth; x++) {\r
120         if (x < numPointsWidth-1) {\r
121           this.constraints.push(new Constraint(\r
122             this.particles[this.getVertexIndex(x, y)],\r
123             this.particles[this.getVertexIndex(x+1, y)],\r
124             REST_DIST_X\r
125           ));\r
126         }\r
127         if (y < numPointsHeight-1) {\r
128           this.constraints.push(new Constraint(\r
129             this.particles[this.getVertexIndex(x, y)],\r
130             this.particles[this.getVertexIndex(x, y+1)],\r
131             REST_DIST_Y\r
132           ));\r
133         }\r
134       }\r
135     }\r
136   }\r
137   generateGeometry() {\r
138     const geometry = new THREE.BufferGeometry();\r
139 \r
140     const vertices = [];\r
141     const normals = [];\r
142     const indices = [];\r
143 \r
144     for (let particle of this.particles) {\r
145       vertices.push(\r
146         particle.position.x,\r
147         particle.position.y,\r
148         particle.position.z);\r
149     }\r
150 \r
151     const numPointsWidth = this.numPointsWidth;\r
152     const numPointsHeight = this.numPointsHeight;\r
153 \r
154     /**\r
155      * generate faces based on 4 vertices\r
156      * and 6 springs each\r
157      */\r
158     for (let y = 0; y < numPointsHeight - 1; y++) {\r
159       for (let x = 0; x < numPointsWidth - 1; x++) {\r
160         indices.push(\r
161           this.getVertexIndex(x, y),\r
162           this.getVertexIndex(x+1, y),\r
163           this.getVertexIndex(x+1, y+1)\r
164         );\r
165         indices.push(\r
166           this.getVertexIndex(x, y),\r
167           this.getVertexIndex(x+1, y+1),\r
168           this.getVertexIndex(x, y+1)\r
169         );\r
170       }\r
171     }\r
172 \r
173     geometry.setIndex(indices);\r
174     geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));\r
175     //geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));\r
176     geometry.computeBoundingSphere();\r
177     geometry.computeVertexNormals();\r
178 \r
179     return geometry;\r
180   }\r
181   updateGeometry(geometry) {\r
182     const positions = geometry.attributes.position.array;\r
183     for (let i in this.particles) {\r
184       let p = this.particles[i];\r
185       positions[i*3+0] = p.position.x;\r
186       positions[i*3+1] = p.position.y;\r
187       positions[i*3+2] = p.position.z;\r
188     }\r
189     geometry.attributes.position.needsUpdate = true;\r
190     geometry.computeBoundingSphere();\r
191     geometry.computeVertexNormals();\r
192   }\r
193   simulate(dt) {\r
194     let now = performance.now();\r
195     for (let particle of this.particles) {\r
196       let vertex = particle.position;\r
197       let fWind = new THREE.Vector3(\r
198         this.windFactor.x * (Math.sin(vertex.x * vertex.y * now)+1),\r
199         this.windFactor.y * Math.cos(vertex.z * now),\r
200         this.windFactor.z * Math.sin(Math.cos(5 * vertex.x * vertex.y * vertex.z))\r
201       );\r
202       // normalize then multiply?\r
203       if (options.wind)\r
204         particle.addForce(fWind);\r
205       // calculate wind with normal?\r
206 \r
207       particle.addForce(GRAVITY);\r
208 \r
209       particle.verlet(dt);\r
210     }\r
211 \r
212     \r
213     for (let constraint of this.constraints) {\r
214       constraint.satisfy();\r
215     }\r
216     //console.log(tmpCorrection);\r
217   }\r
218   /**\r
219    * helper function to calculate index of vertex\r
220    * in "vertices" array based on its x and y positions\r
221    * in the mesh\r
222    * @param {number} x - x index of vertex\r
223    * @param {number} y - y index of vertex\r
224    */\r
225   getVertexIndex(x, y) {\r
226     return y * this.numPointsWidth + x;\r
227   }\r
228 }