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