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