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