]> gitweb.ps.run Git - cloth_sim/blob - Scripts/cloth.js
Distinguish between right and left mouse click for wind and dragging
[cloth_sim] / Scripts / cloth.js
1 /**\r
2  *  Convenience Function for calculating the distance between two vectors\r
3  *  because THREE JS Vector functions mutate variables\r
4  * @param {Vector3} a - Vector A\r
5  * @param {Vector3} b - Vector B\r
6  */\r
7 function vectorLength(a, b) {\r
8   let v1 = new THREE.Vector3();\r
9   v1.copy(a);\r
10   let v2 = new THREE.Vector3();\r
11   v2.copy(b);\r
12 \r
13   return v1.sub(v2).length();\r
14 }\r
15 \r
16 /**\r
17  * Class representing a quad face\r
18  * Each face consists of two triangular mesh faces\r
19  * containts four indices for determining vertices\r
20  * and six springs, one between each of the vertices\r
21  */\r
22 export class Face {\r
23   a;\r
24   b;\r
25   c;\r
26   d;\r
27 \r
28   springs = [];\r
29 \r
30   constructor(a, b, c, d) {\r
31     this.a = a;\r
32     this.b = b;\r
33     this.c = c;\r
34     this.d = d;\r
35   }\r
36 }\r
37 \r
38 /**\r
39  * Class representing a single spring\r
40  * has a current and resting length\r
41  * and indices to the two connected vertices\r
42  */\r
43 export class Spring {\r
44   restLength;\r
45   currentLength;\r
46   index1;\r
47   index2;\r
48 \r
49 \r
50   /**\r
51    * set vertex indices\r
52    * and calculate inital length based on the\r
53    * vertex positions\r
54    * @param {Array<Vector3>} vertices \r
55    * @param {number} index1 \r
56    * @param {number} index2 \r
57    */\r
58   constructor(vertices, index1, index2) {\r
59     this.index1 = index1;\r
60     this.index2 = index2;\r
61 \r
62     let length = vectorLength(vertices[index1], vertices[index2]);\r
63     this.restLength = length;\r
64     this.currentLength = length;\r
65   }\r
66 \r
67   getDirection(vertices) {\r
68     let direction = new THREE.Vector3();\r
69     direction.copy(vertices[this.index1]);\r
70 \r
71     direction.sub(vertices[this.index2]);\r
72     direction.divideScalar(vectorLength(vertices[this.index1], vertices[this.index2]));\r
73 \r
74     return direction;\r
75   }\r
76 \r
77   update(vertices) {\r
78     let length = vectorLength(vertices[this.index1], vertices[this.index2]);\r
79     this.currentLength = length;\r
80   }\r
81 }\r
82 \r
83 /**\r
84  * Class representing a single piece of cloth\r
85  * contains THREE JS geometry,\r
86  * logically represented by an array of adjacent faces\r
87  * and vertex weights which are accessed by the same\r
88  * indices as the vertices in the Mesh\r
89  */\r
90 export class Cloth {\r
91   VertexWeight = 1;\r
92 \r
93   geometry = new THREE.Geometry();\r
94 \r
95   faces = [];\r
96 \r
97   vertexWeights = [];\r
98 \r
99   vertexRigidness = [];\r
100 \r
101   externalForces = [];\r
102   windForce = 100;\r
103 \r
104   /**\r
105    * creates a rectangular piece of cloth\r
106    * takes the size of the cloth\r
107    * and the number of vertices it should be composed of\r
108    * @param {number} width - width of the cloth\r
109    * @param {number} height - height of the cloth\r
110    * @param {number} numPointsWidth - number of vertices in horizontal direction\r
111    * @param {number} numPointsHeight  - number of vertices in vertical direction\r
112    */\r
113   createBasic(width, height, numPointsWidth, numPointsHeight) {\r
114     /** resulting vertices and faces */\r
115     let vertices = [];\r
116     let faces = [];\r
117 \r
118     this.numPointsWidth = numPointsWidth;\r
119     this.numPointsHeight = numPointsHeight;\r
120 \r
121     /**\r
122      * distance between two vertices horizontally/vertically\r
123      * divide by the number of points minus one\r
124      * because there are (n - 1) lines between n vertices\r
125      */\r
126     let stepWidth = width / (numPointsWidth - 1);\r
127     let stepHeight = height / (numPointsHeight - 1);\r
128 \r
129     /**\r
130      * iterate over the number of vertices in x/y axis\r
131      * and add a new Vector3 to "vertices"\r
132      */\r
133     for (let y = 0; y < numPointsHeight; y++) {\r
134       for (let x = 0; x < numPointsWidth; x++) {\r
135         vertices.push(\r
136           new THREE.Vector3(x * stepWidth, height - y * stepHeight, 0)\r
137         );\r
138       }\r
139     }\r
140 \r
141     /**\r
142      * helper function to calculate index of vertex\r
143      * in "vertices" array based on its x and y positions\r
144      * in the mesh\r
145      * @param {number} x - x index of vertex\r
146      * @param {number} y - y index of vertex\r
147      */\r
148     function getVertexIndex(x, y) {\r
149       return y * numPointsWidth + x;\r
150     }\r
151 \r
152     /**\r
153      * generate faces based on 4 vertices\r
154      * and 6 springs each\r
155      */\r
156     for (let y = 0; y < numPointsHeight - 1; y++) {\r
157       for (let x = 0; x < numPointsWidth - 1; x++) {\r
158         let newFace = new Face(\r
159           getVertexIndex(x, y),\r
160           getVertexIndex(x, y + 1),\r
161           getVertexIndex(x + 1, y),\r
162           getVertexIndex(x + 1, y + 1),\r
163         );\r
164 \r
165         newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x + 1, y)));         // oben\r
166         newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x, y + 1)));         // links\r
167         newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x + 1, y + 1)));     // oben links  -> unten rechts diagonal\r
168         newFace.springs.push(new Spring(vertices, getVertexIndex(x + 1, y), getVertexIndex(x, y + 1)));     // oben rechts -> unten links diagonal\r
169         newFace.springs.push(new Spring(vertices, getVertexIndex(x + 1, y), getVertexIndex(x + 1, y + 1))); // rechts\r
170         newFace.springs.push(new Spring(vertices, getVertexIndex(x, y + 1), getVertexIndex(x + 1, y + 1))); // unten\r
171 \r
172         faces.push(newFace);\r
173       }\r
174     }\r
175 \r
176     /**\r
177      * call createExplicit\r
178      * with generated vertices and faces\r
179      */\r
180     this.createExplicit(vertices, faces);\r
181 \r
182     /**\r
183      * hand cloth from left and right upper corners\r
184      */\r
185     this.vertexRigidness[0] = true;\r
186     this.vertexRigidness[numPointsWidth-1] = true;\r
187   }\r
188 \r
189   /**\r
190    * Generate THREE JS Geometry\r
191    * (list of vertices and list of indices representing triangles)\r
192    * and calculate the weight of each face and split it between\r
193    * surrounding vertices\r
194    * @param {Array<Vector3>} vertices \r
195    * @param {Array<Face>} faces \r
196    */\r
197   createExplicit(vertices, faces) {\r
198 \r
199     /**\r
200      * Copy vertices and initialize vertex weights to 0\r
201      */\r
202     for (let i in vertices) {\r
203       this.geometry.vertices.push(vertices[i].clone());\r
204       this.previousPositions.push(vertices[i].clone());\r
205       // this.geometry.vertices.push(vertices[i]);\r
206       // this.previousPositions.push(vertices[i]);\r
207       this.vertexWeights.push(0);\r
208       this.vertexRigidness.push(false);\r
209       this.externalForces.push(new THREE.Vector3(0,0,0));\r
210     }\r
211     /**\r
212      * copy faces,\r
213      * generate two triangles per face,\r
214      * calculate weight of face as its area\r
215      * and split between the 4 vertices\r
216      */\r
217     for (let i in faces) {\r
218       let face = faces[i];\r
219 \r
220       /** copy faces to class member */\r
221       this.faces.push(face);\r
222 \r
223       /** generate triangles */\r
224       this.geometry.faces.push(new THREE.Face3(\r
225         face.a, face.b, face.c\r
226       ));\r
227       this.geometry.faces.push(new THREE.Face3(\r
228         face.c, face.b, face.d\r
229       ));\r
230 \r
231       /**\r
232        * calculate area of face as combined area of\r
233        * its two composing triangles\r
234        */\r
235       let xLength = vectorLength(this.geometry.vertices[face.b], this.geometry.vertices[face.a]);\r
236       let yLength = vectorLength(this.geometry.vertices[face.c], this.geometry.vertices[face.a]);\r
237       let weight = xLength * yLength / 2;\r
238 \r
239       xLength = vectorLength(this.geometry.vertices[face.b], this.geometry.vertices[face.d]);\r
240       yLength = vectorLength(this.geometry.vertices[face.c], this.geometry.vertices[face.d]);\r
241       weight += xLength * yLength / 2;\r
242 \r
243       /**\r
244        * split weight equally between four surrounding vertices\r
245        */\r
246       this.vertexWeights[face.a] += weight / 4;\r
247       this.vertexWeights[face.b] += weight / 4;\r
248       this.vertexWeights[face.c] += weight / 4;\r
249       this.vertexWeights[face.d] += weight / 4;\r
250     }\r
251 \r
252     /**\r
253      * let THREE JS compute bounding sphere around generated mesh\r
254      * needed for View Frustum Culling internally\r
255      */\r
256     this.geometry.computeBoundingSphere();\r
257     this.geometry.computeFaceNormals();\r
258     this.geometry.computeVertexNormals();\r
259   }\r
260 \r
261   /**\r
262    * generate a debug mesh for visualizing\r
263    * vertices and springs of the cloth\r
264    * and add it to scene for rendering\r
265    * @param {Scene} scene - Scene to add Debug Mesh to\r
266    */\r
267   createDebugMesh(scene) {\r
268     /**\r
269      * helper function to generate a single line\r
270      * between two Vertices with a given color\r
271      * @param {Vector3} from \r
272      * @param {Vector3} to \r
273      * @param {number} color \r
274      */\r
275     function addLine(from, to, color) {\r
276       let geometry = new THREE.Geometry();\r
277       geometry.vertices.push(from);\r
278       geometry.vertices.push(to);\r
279       let material = new THREE.LineBasicMaterial({ color: color, linewidth: 10 });\r
280       let line = new THREE.Line(geometry, material);\r
281       line.renderOrder = 1;\r
282       scene.add(line);\r
283     }\r
284     /**\r
285      * helper function to generate a small sphere\r
286      * at a given Vertex Position with color\r
287      * @param {Vector3} point \r
288      * @param {number} color \r
289      */\r
290     function addPoint(point, color) {\r
291       const geometry = new THREE.SphereGeometry(0.05, 32, 32);\r
292       const material = new THREE.MeshBasicMaterial({ color: color });\r
293       const sphere = new THREE.Mesh(geometry, material);\r
294       sphere.position.set(point.x, point.y, point.z);\r
295       scene.add(sphere);\r
296     }\r
297 \r
298     let lineColor = 0x000000;\r
299     let pointColor = 0xff00000;\r
300 \r
301     /**\r
302      * generate one line for each of the 6 springs\r
303      * and one point for each of the 4 vertices\r
304      * for all of the faces\r
305      */\r
306     for (let i in this.faces) {\r
307       let face = this.faces[i];\r
308       addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.b], lineColor);\r
309       addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.c], lineColor);\r
310       addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.d], lineColor);\r
311       addLine(this.geometry.vertices[face.b], this.geometry.vertices[face.c], lineColor);\r
312       addLine(this.geometry.vertices[face.b], this.geometry.vertices[face.d], lineColor);\r
313       addLine(this.geometry.vertices[face.c], this.geometry.vertices[face.d], lineColor);\r
314 \r
315       addPoint(this.geometry.vertices[face.a], pointColor);\r
316       addPoint(this.geometry.vertices[face.b], pointColor);\r
317       addPoint(this.geometry.vertices[face.c], pointColor);\r
318       addPoint(this.geometry.vertices[face.d], pointColor);\r
319     }\r
320   }\r
321 \r
322   previousPositions = [];\r
323   time = 0;\r
324   /**\r
325    * \r
326    * @param {number} dt time in seconds since last frame\r
327    */\r
328   simulate(dt) {\r
329     for (let i in this.geometry.vertices) {\r
330       let acceleration = this.getAcceleration(i, dt);\r
331 \r
332       //acceleration.clampLength(0, 10);\r
333 \r
334       if (Math.abs(acceleration.length()) <= 10e-4) {\r
335         acceleration.set(0, 0, 0);\r
336       }\r
337  \r
338       let currentPosition = this.verlet(this.geometry.vertices[i].clone(), this.previousPositions[i].clone(), acceleration, dt);\r
339       //let currentPosition = this.euler(this.geometry.vertices[i], acceleration, dt);\r
340      \r
341       this.previousPositions[i].copy(this.geometry.vertices[i]);\r
342       this.geometry.vertices[i].copy(currentPosition);\r
343     }\r
344     //console.log(this.getAcceleration(1, dt));\r
345     \r
346     this.time += dt;\r
347 \r
348     for (let face of this.faces) {\r
349       for (let spring of face.springs) {\r
350         spring.update(this.geometry.vertices);\r
351       }\r
352     }\r
353 \r
354     /**\r
355      * let THREE JS compute bounding sphere around generated mesh\r
356      * needed for View Frustum Culling internally\r
357      */\r
358 \r
359     this.geometry.verticesNeedUpdate = true;\r
360     this.geometry.elementsNeedUpdate = true;\r
361     this.geometry.computeBoundingSphere();\r
362     this.geometry.computeFaceNormals();\r
363     this.geometry.computeVertexNormals();\r
364 \r
365   }\r
366 \r
367 \r
368 \r
369 /**\r
370  * Equation of motion for each vertex which represents the acceleration \r
371  * @param {number} vertexIndex The index of the current vertex whose acceleration should be calculated\r
372  *  @param {number} dt The time passed since last frame\r
373  */\r
374 getAcceleration(vertexIndex, dt) {\r
375   if (this.vertexRigidness[vertexIndex])\r
376     return new THREE.Vector3(0, 0, 0);\r
377 \r
378   let externalForce = this.externalForces[vertexIndex];\r
379   let vertex = this.geometry.vertices[vertexIndex];//.add(externalForce);\r
380 \r
381   // Mass of vertex\r
382   let M = this.vertexWeights[vertexIndex];\r
383   // constant gravity\r
384   let g = new THREE.Vector3(0, -9.8, 0);\r
385   // stiffness\r
386   let k = 1000;\r
387 \r
388   // Wind vector\r
389   let fWind = new THREE.Vector3(\r
390     Math.sin(vertex.x * vertex.y * this.time),\r
391     Math.cos(vertex.z * this.time),\r
392     Math.sin(Math.cos(5 * vertex.x * vertex.y * vertex.z))\r
393   );\r
394 \r
395   /**\r
396    * constant determined by the properties of the surrounding fluids (air)\r
397    * achievement of cloth effects through try out\r
398    * */\r
399   let a = 0.1;\r
400   \r
401   let velocity = new THREE.Vector3(\r
402     (this.previousPositions[vertexIndex].x - vertex.x) / dt,\r
403     (this.previousPositions[vertexIndex].y - vertex.y) / dt,\r
404     (this.previousPositions[vertexIndex].z - vertex.z) / dt\r
405   );\r
406 \r
407   //console.log(velocity, vertex, this.previousPositions[vertexIndex]);\r
408 \r
409   let fAirResistance = velocity.multiply(velocity).multiplyScalar(-a);\r
410   \r
411   let springSum = new THREE.Vector3(0, 0, 0);\r
412 \r
413   // Get the bounding springs and add them to the needed springs\r
414   // TODO: optimize\r
415 \r
416   const numPointsX = this.numPointsWidth;\r
417   const numPointsY = this.numPointsHeight;\r
418   const numFacesX = numPointsX - 1;\r
419   const numFacesY = numPointsY - 1;\r
420 \r
421   function getFaceIndex(x, y) {\r
422     return y * numFacesX + x;\r
423   }\r
424 \r
425   let indexX = vertexIndex % numPointsX;\r
426   let indexY = Math.floor(vertexIndex / numPointsX);\r
427 \r
428   let springs = [];\r
429 \r
430   // 0  oben\r
431   // 1  links\r
432   // 2  oben links  -> unten rechts diagonal\r
433   // 3  oben rechts -> unten links diagonal\r
434   // 4  rechts\r
435   // 5  unten\r
436 \r
437   let ul = indexX > 0 && indexY < numPointsY - 1;\r
438   let ur = indexX < numPointsX - 1 && indexY < numPointsY - 1;\r
439   let ol = indexX > 0 && indexY > 0;\r
440   let or = indexX < numPointsX - 1 && indexY > 0;\r
441 \r
442   if (ul) {\r
443     let faceUL = this.faces[getFaceIndex(indexX - 1, indexY)];\r
444     springs.push(faceUL.springs[3]);\r
445     if (!ol)\r
446       springs.push(faceUL.springs[0]);\r
447     springs.push(faceUL.springs[4]);\r
448   }\r
449   if (ur) {\r
450     let faceUR = this.faces[getFaceIndex(indexX, indexY)];\r
451     springs.push(faceUR.springs[2]);\r
452     if (!or)\r
453       springs.push(faceUR.springs[0]);\r
454     if (!ul)\r
455       springs.push(faceUR.springs[1]);\r
456   }\r
457   if (ol) {\r
458     let faceOL = this.faces[getFaceIndex(indexX - 1, indexY - 1)];\r
459     springs.push(faceOL.springs[2]);\r
460     springs.push(faceOL.springs[4]);\r
461     springs.push(faceOL.springs[5]);\r
462   }\r
463   if (or) {\r
464     let faceOR = this.faces[getFaceIndex(indexX , indexY - 1)];\r
465     springs.push(faceOR.springs[3]);\r
466     if (!ol)\r
467       springs.push(faceOR.springs[1]);\r
468     springs.push(faceOR.springs[5]);\r
469   }\r
470 \r
471   for (let spring of springs) {\r
472     let springDirection = spring.getDirection(this.geometry.vertices);\r
473 \r
474     if (spring.index1 == vertexIndex)\r
475       springDirection.multiplyScalar(-1);\r
476 \r
477     springSum.add(springDirection.multiplyScalar(k * (spring.restLength - spring.currentLength)));\r
478   }\r
479   \r
480   let result = new THREE.Vector3(1, 1, 1);\r
481   result.multiplyScalar(M).multiply(g).add(fWind).add(externalForce).add(fAirResistance).sub(springSum);\r
482 \r
483   document.getElementById("Output").innerText = "SpringSum: " + Math.floor(springSum.y);\r
484 \r
485   let threshold = 1;\r
486   let forceReduktion = 0.8;\r
487   if(Math.abs(externalForce.z) > threshold){\r
488     externalForce.z *= forceReduktion;\r
489   } else {\r
490     externalForce.z = 0;\r
491   }\r
492 \r
493   if(Math.abs(externalForce.y) > threshold){\r
494     externalForce.y *= forceReduktion;\r
495   } else {\r
496     externalForce.y = 0;\r
497   }\r
498 \r
499   if(Math.abs(externalForce.x) > threshold){\r
500     externalForce.x *= forceReduktion;\r
501   } else {\r
502     externalForce.x = 0;\r
503   }\r
504     \r
505   \r
506 \r
507   return result;\r
508 }\r
509 \r
510 /**\r
511  * The Verlet algorithm as an integrator \r
512  * to get the next position of a vertex  \r
513  * @param {Vector3} currentPosition \r
514  * @param {Vector3} previousPosition \r
515  * @param {Vector3} acceleration \r
516  * @param {number} passedTime The delta time since last frame\r
517  */\r
518 verlet(currentPosition, previousPosition, acceleration, passedTime) {\r
519   // verlet algorithm\r
520   // next position = 2 * current Position - previous position + acceleration * (passed time)^2\r
521   // acceleration (dv/dt) = F(net)\r
522   // Dependency for one vertex: gravity, fluids/air, springs\r
523   const DRAG = 0.96;\r
524   let nextPosition = new THREE.Vector3(\r
525     (currentPosition.x - previousPosition.x) * DRAG + currentPosition.x + acceleration.x * (passedTime * passedTime),\r
526     (currentPosition.y - previousPosition.y) * DRAG + currentPosition.y + acceleration.y * (passedTime * passedTime),\r
527     (currentPosition.z - previousPosition.z) * DRAG + currentPosition.z + acceleration.z * (passedTime * passedTime),\r
528   );\r
529 \r
530   // let nextPosition = new THREE.Vector3(\r
531   //   (2 * currentPosition.x) - previousPosition.x + acceleration.x * (passedTime * passedTime),\r
532   //   (2 * currentPosition.y) - previousPosition.y + acceleration.y * (passedTime * passedTime),\r
533   //   (2 * currentPosition.z) - previousPosition.z + acceleration.z * (passedTime * passedTime),\r
534   // );\r
535 \r
536   return nextPosition;\r
537 }\r
538 \r
539 euler(currentPosition, acceleration, passedTime) {\r
540   let nextPosition = new THREE.Vector3(\r
541     currentPosition.x + acceleration.x * passedTime,\r
542     currentPosition.y + acceleration.y * passedTime,\r
543     currentPosition.z + acceleration.z * passedTime,\r
544   );\r
545 \r
546   return nextPosition;\r
547 }\r
548 \r
549 wind(intersects) {\r
550   let intersect = intersects[0];\r
551   this.externalForces[intersect.face.a].z -= this.windForce;\r
552   this.externalForces[intersect.face.b].z -= this.windForce;\r
553   this.externalForces[intersect.face.c].z -= this.windForce;\r
554 }\r
555 \r
556 mousePressed = false;\r
557 mouseMoved = false;\r
558 intersects;\r
559 \r
560 mousePress(intersects){\r
561   this.mousePressed = true;\r
562   this.intersects = intersects;\r
563 \r
564 }\r
565 \r
566 mouseMove(mousePos){\r
567   this.mouseMoved = true;\r
568   if(this.mousePressed){\r
569     let intersect = this.intersects[0];\r
570     this.externalForces[intersect.face.a].add(mousePos.clone().sub(this.geometry.vertices[intersect.face.a]).multiplyScalar(90));\r
571     /*\r
572     this.geometry.vertices[intersect.face.a].x = mousePos.x;\r
573     this.geometry.vertices[intersect.face.a].y = mousePos.y;\r
574     this.geometry.vertices[intersect.face.a].z = mousePos.z;\r
575   */  \r
576   }\r
577 }\r
578 \r
579 mouseRelease(){\r
580   this.mousePressed = false;\r
581 }\r
582 \r
583 }\r
584 \r