]> gitweb.ps.run Git - cloth_sim/blob - Scripts/cloth.js
add cloth.js file
[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.set(a.x, a.y, a.z);\r
10   let v2 = new THREE.Vector3();\r
11   v2.set(b.x, b.y, b.z);\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 of 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 \r
68 /**\r
69  * Class representing a single piece of cloth\r
70  * contains THREE JS geometry,\r
71  * logically represented by an array of adjacent faces\r
72  * and vertex weights which are accessed by the same\r
73  * indices as the vertices in the Mesh\r
74  */\r
75 export class Cloth {\r
76   VertexWeight = 1;\r
77 \r
78   geometry = new THREE.Geometry();\r
79 \r
80   faces = [];\r
81 \r
82   vertexWeights = [];\r
83 \r
84   \r
85   /**\r
86    * creates a rectangular piece of cloth\r
87    * takes the size of the cloth\r
88    * and the number of vertices it should be composed of\r
89    * @param {number} width - width of the cloth\r
90    * @param {number} height - height of the cloth\r
91    * @param {number} numPointsWidth - number of vertices in horizontal direction\r
92    * @param {number} numPointsHeight  - number of vertices in vertical direction\r
93    */\r
94   createBasic(width, height, numPointsWidth, numPointsHeight) {\r
95     /** resulting vertices and faces */\r
96     let vertices = [];\r
97     let faces = [];\r
98 \r
99     /**\r
100      * distance between two vertices horizontally/vertically\r
101      * divide by the number of points minus one\r
102      * because there are (n - 1) lines between n vertices\r
103      */\r
104     let stepWidth = width / (numPointsWidth - 1);\r
105     let stepHeight = height / (numPointsHeight - 1);\r
106 \r
107     /**\r
108      * iterate over the number of vertices in x/y axis\r
109      * and add a new Vector3 to "vertices"\r
110      */\r
111     for (let y = 0; y < numPointsHeight; y++) {\r
112       for (let x = 0; x < numPointsWidth; x++) {\r
113         vertices.push(\r
114           new THREE.Vector3(x * stepWidth, height - y * stepHeight, 0)\r
115         );\r
116       }\r
117     }\r
118 \r
119     /**\r
120      * helper function to calculate index of vertex\r
121      * in "vertices" array based on its x and y positions\r
122      * in the mesh\r
123      * @param {number} x - x index of vertex\r
124      * @param {number} y - y index of vertex\r
125      */\r
126     function getVertexIndex(x, y) {\r
127       return y * numPointsWidth + x;\r
128     }\r
129     \r
130     /**\r
131      * generate faces based on 4 vertices\r
132      * and 6 springs each\r
133      */\r
134     for (let y = 0; y < numPointsHeight - 1; y++) {\r
135       for (let x = 0; x < numPointsWidth - 1; x++) {\r
136         let newFace = new Face(\r
137           getVertexIndex(x, y),\r
138           getVertexIndex(x, y + 1),\r
139           getVertexIndex(x + 1, y),\r
140           getVertexIndex(x + 1, y + 1),\r
141         );\r
142 \r
143         newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x + 1, y)));\r
144         newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x, y + 1)));\r
145         newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x + 1, y + 1)));\r
146         newFace.springs.push(new Spring(vertices, getVertexIndex(x + 1, y), getVertexIndex(x, y + 1)));\r
147         newFace.springs.push(new Spring(vertices, getVertexIndex(x + 1, y), getVertexIndex(x + 1, y + 1)));\r
148         newFace.springs.push(new Spring(vertices, getVertexIndex(x, y + 1), getVertexIndex(x + 1, y + 1)));\r
149         \r
150         faces.push(newFace);\r
151       }\r
152     }\r
153 \r
154     /**\r
155      * call createExplicit\r
156      * with generated vertices and faces\r
157      */\r
158     this.createExplicit(vertices, faces);\r
159   }\r
160 \r
161   /**\r
162    * Generate THREE JS Geometry\r
163    * (list of vertices and list of indices representing triangles)\r
164    * and calculate the weight of each face and split it between\r
165    * surrounding vertices\r
166    * @param {Array of Vector3} vertices \r
167    * @param {Array of Face} faces \r
168    */\r
169   createExplicit(vertices, faces) {\r
170     /**\r
171      * Copy vertices and initialize vertex weights to 0\r
172      */\r
173     for (let i in vertices) {\r
174       this.geometry.vertices.push(vertices[i]);\r
175       this.vertexWeights.push(0);\r
176     }\r
177     /**\r
178      * copy faces,\r
179      * generate two triangles per face,\r
180      * calculate weight of face as its area\r
181      * and split between the 4 vertices\r
182      */\r
183     for (let i in faces) {\r
184       let face = faces[i];\r
185 \r
186       /** copy faces to class member */\r
187       this.faces.push(face);\r
188 \r
189       /** generate triangles */\r
190       this.geometry.faces.push(new THREE.Face3(\r
191         face.a, face.b, face.c\r
192       ));\r
193       this.geometry.faces.push(new THREE.Face3(\r
194         face.c, face.b, face.d\r
195       ));\r
196       \r
197       /**\r
198        * calculate area of face as combined area of\r
199        * its two composing triangles\r
200        */\r
201       let xLength = vectorLength(this.geometry.vertices[face.b], this.geometry.vertices[face.a]);\r
202       let yLength = vectorLength(this.geometry.vertices[face.c], this.geometry.vertices[face.a]);\r
203       let weight = xLength * yLength / 2;\r
204 \r
205       xLength = vectorLength(this.geometry.vertices[face.b], this.geometry.vertices[face.d]);\r
206       yLength = vectorLength(this.geometry.vertices[face.c], this.geometry.vertices[face.d]);\r
207       weight += xLength * yLength / 2;\r
208 \r
209       /**\r
210        * split weight equally between four surrounding vertices\r
211        */\r
212       this.vertexWeights[face.a] += weight / 4;\r
213       this.vertexWeights[face.b] += weight / 4;\r
214       this.vertexWeights[face.c] += weight / 4;\r
215       this.vertexWeights[face.d] += weight / 4;\r
216     }\r
217 \r
218     /**\r
219      * let THREE JS compute bounding sphere around generated mesh\r
220      * needed for View Frustum Culling internally\r
221      */\r
222     this.geometry.computeBoundingSphere();\r
223   }\r
224 \r
225   /**\r
226    * generate a debug mesh for visualizing\r
227    * vertices and springs of the cloth\r
228    * and add it to scene for rendering\r
229    * @param {Scene} scene - Scene to add Debug Mesh to\r
230    */\r
231   createDebugMesh(scene) {\r
232     /**\r
233      * helper function to generate a single line\r
234      * between two Vertices with a given color\r
235      * @param {Vector3} from \r
236      * @param {Vector3} to \r
237      * @param {number} color \r
238      */\r
239     function addLine(from, to, color) {\r
240       let geometry = new THREE.Geometry();\r
241       geometry.vertices.push(from);\r
242       geometry.vertices.push(to);\r
243       let material = new THREE.LineBasicMaterial( { color: color, linewidth: 10 } );\r
244       let line = new THREE.Line(geometry, material);\r
245       line.renderOrder = 1;\r
246       scene.add(line);\r
247     }\r
248     /**\r
249      * helper function to generate a small sphere\r
250      * at a given Vertex Position with color\r
251      * @param {Vector3} point \r
252      * @param {number} color \r
253      */\r
254     function addPoint(point, color) {\r
255       const geometry = new THREE.SphereGeometry( 0.05, 32, 32 );\r
256       const material = new THREE.MeshBasicMaterial( { color: color } );\r
257       const sphere = new THREE.Mesh( geometry, material );\r
258       sphere.position.set(point.x, point.y, point.z);\r
259       scene.add( sphere );\r
260     }\r
261 \r
262     let lineColor = 0x000000;\r
263     let pointColor = 0xff00000;\r
264 \r
265     /**\r
266      * generate one line for each of the 6 springs\r
267      * and one point for each of the 4 vertices\r
268      * for all of the faces\r
269      */\r
270     for (let i in this.faces) {\r
271       let face = this.faces[i];\r
272       addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.b], lineColor);\r
273       addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.c], lineColor);\r
274       addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.d], lineColor);\r
275       addLine(this.geometry.vertices[face.b], this.geometry.vertices[face.c], lineColor);\r
276       addLine(this.geometry.vertices[face.b], this.geometry.vertices[face.d], lineColor);\r
277       addLine(this.geometry.vertices[face.c], this.geometry.vertices[face.d], lineColor);\r
278 \r
279       addPoint(this.geometry.vertices[face.a], pointColor);\r
280       addPoint(this.geometry.vertices[face.b], pointColor);\r
281       addPoint(this.geometry.vertices[face.c], pointColor);\r
282       addPoint(this.geometry.vertices[face.d], pointColor);\r
283     }\r
284   }\r
285 }