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