1 const DAMPING = 0.03;
\r
2 const DRAG = 1 - DAMPING;
\r
4 const GRAVITY = new THREE.Vector3(0, -9.81 * MASS, 0);
\r
6 const MAX_STRETCH = 1.5;
\r
13 constructor(p1, p2, restDist) {
\r
16 this.restDist = restDist;
\r
20 /** calculate current spring length */
\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
27 /** calculate necessary correction length and direction */
\r
28 const correction = diff.multiplyScalar((currentDist - this.restDist) / currentDist);
\r
29 correction.multiplyScalar(K);
\r
30 const correctionHalf = correction.multiplyScalar(0.5);
\r
32 let p1movable = this.p1.movable && this.p1.movableTmp;
\r
33 let p2movable = this.p2.movable && this.p2.movableTmp;
\r
35 /** apply correction if masses aren't fixed */
\r
36 /** divide correction if both are movable */
\r
37 if (p1movable && p2movable) {
\r
38 this.p1.position.add(correctionHalf);
\r
39 this.p2.position.sub(correctionHalf);
\r
40 } else if (! p1movable && p2movable) {
\r
41 this.p2.position.sub(correction);
\r
42 } else if (p1movable && ! p2movable) {
\r
43 this.p1.position.add(correction);
\r
52 constructor(x, y, z, mass) {
\r
53 this.position = new THREE.Vector3(x, y, z);
\r
54 this.previous = new THREE.Vector3(x, y, z);
\r
55 this.acceleration = new THREE.Vector3(0, 0, 0);
\r
59 this.acceleration.add(
\r
60 force.clone().multiplyScalar(1/this.mass)
\r
65 // next position = 2 * current Position - previous position + acceleration * (passed time)^2
\r
66 // acceleration (dv/dt) = F(net)
\r
67 /** calculate velocity */
\r
68 const nextPosition = this.position.clone().sub(this.previous);
\r
70 nextPosition.multiplyScalar(DRAG);
\r
71 /** add to current position and add acceleration */
\r
72 nextPosition.add(this.position);
\r
73 nextPosition.add(this.acceleration.multiplyScalar(dt*dt));
\r
75 if (this.movable && this.movableTmp) {
\r
76 this.previous = this.position;
\r
77 this.position = nextPosition;
\r
80 /** reset for next frame */
\r
81 this.acceleration.set(0, 0, 0);
\r
86 constructor(width, height, numPointsWidth, numPointsHeight) {
\r
88 this.height = height;
\r
89 this.numPointsWidth = numPointsWidth;
\r
90 this.numPointsHeight = numPointsHeight;
\r
91 this.windFactor = new THREE.Vector3(3, 2, 2);
\r
94 * distance between two vertices horizontally/vertically
\r
95 * divide by the number of points minus one
\r
96 * because there are (n - 1) lines between n vertices
\r
98 let stepWidth = width / (numPointsWidth - 1);
\r
99 let stepHeight = height / (numPointsHeight - 1);
\r
102 * iterate over the number of vertices in x/y axis
\r
103 * and add a new Particle to "masses"
\r
106 for (let y = 0; y < numPointsHeight; y++) {
\r
107 for (let x = 0; x < numPointsWidth; x++) {
\r
110 (x - ((numPointsWidth-1)/2)) * stepWidth,
\r
111 height - (y + ((numPointsHeight-1)/2)) * stepHeight,
\r
118 /** attach cloth to flag pole */
\r
120 for (let i = 0; i < numPointsHeight; i++)
\r
121 this.masses[this.getVertexIndex(0, i)].movable = false;
\r
123 const REST_DIST_X = width / (numPointsWidth-1);
\r
124 const REST_DIST_Y = height / (numPointsHeight-1);
\r
127 * generate springs (constraints)
\r
130 for (let y = 0; y < numPointsHeight; y++) {
\r
131 for (let x = 0; x < numPointsWidth; x++) {
\r
132 if (x < numPointsWidth-1) {
\r
133 this.springs.push(new Spring(
\r
134 this.masses[this.getVertexIndex(x, y)],
\r
135 this.masses[this.getVertexIndex(x+1, y)],
\r
139 if (y < numPointsHeight-1) {
\r
140 this.springs.push(new Spring(
\r
141 this.masses[this.getVertexIndex(x, y)],
\r
142 this.masses[this.getVertexIndex(x, y+1)],
\r
149 generateGeometry() {
\r
150 const geometry = new THREE.BufferGeometry();
\r
152 const vertices = [];
\r
153 const indices = [];
\r
156 /** create one vertex and one uv coordinate per mass */
\r
157 for (let i in this.masses) {
\r
158 let particle = this.masses[i];
\r
160 particle.position.x,
\r
161 particle.position.y,
\r
162 particle.position.z);
\r
164 this.getX(i) / (this.numPointsWidth-1),
\r
165 1 - (this.getY(i) / (this.numPointsHeight-1))
\r
170 * generate faces based on 4 vertices
\r
171 * and 6 springs each
\r
173 for (let y = 0; y < this.numPointsHeight - 1; y++) {
\r
174 for (let x = 0; x < this.numPointsWidth - 1; x++) {
\r
176 this.getVertexIndex(x, y),
\r
177 this.getVertexIndex(x+1, y),
\r
178 this.getVertexIndex(x+1, y+1)
\r
181 this.getVertexIndex(x, y),
\r
182 this.getVertexIndex(x+1, y+1),
\r
183 this.getVertexIndex(x, y+1)
\r
188 /** set up geometry */
\r
189 geometry.setIndex(indices);
\r
190 geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
\r
191 geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
\r
192 geometry.computeBoundingSphere();
\r
193 geometry.computeVertexNormals();
\r
197 updateGeometry(geometry) {
\r
198 /** update vertex positions in place */
\r
199 const positions = geometry.attributes.position.array;
\r
200 for (let i in this.masses) {
\r
201 let p = this.masses[i];
\r
202 positions[i*3+0] = p.position.x;
\r
203 positions[i*3+1] = p.position.y;
\r
204 positions[i*3+2] = p.position.z;
\r
206 /** update internally and recalculate bounding volume */
\r
207 geometry.attributes.position.needsUpdate = true;
\r
208 geometry.computeBoundingSphere();
\r
209 geometry.computeVertexNormals();
\r
212 let now = performance.now();
\r
213 for (let mass of this.masses) {
\r
214 /** accumulate acceleration:
\r
218 let vertex = mass.position;
\r
219 let fWind = new THREE.Vector3(
\r
220 this.windFactor.x * (Math.sin(vertex.x * vertex.y * now)+1),
\r
221 this.windFactor.y * Math.cos(vertex.z * now),
\r
222 this.windFactor.z * Math.sin(Math.cos(5 * vertex.x * vertex.y * vertex.z))
\r
224 // normalize then multiply?
\r
226 mass.addForce(fWind);
\r
227 // calculate wind with normal?
\r
229 mass.addForce(GRAVITY);
\r
231 /** integrate motion */
\r
235 /** run satisfy step */
\r
236 for (let constraint of this.springs) {
\r
237 constraint.satisfy();
\r
240 /** prevent self-intersections */
\r
245 for (let i in this.masses) {
\r
246 for (let j in this.masses) {
\r
247 let p1 = this.masses[i];
\r
248 let p2 = this.masses[j];
\r
250 p1.movableTmp = true;
\r
251 p2.movableTmp = true;
\r
253 /** skip if i == j or if masses are adjacent */
\r
254 if (i == j || (Math.abs(this.getX(i) - this.getX(j)) == 1 && Math.abs(this.getY(i) - this.getY(j)) == 1))
\r
257 /** calculate distance of points */
\r
258 let dist = p1.position.distanceTo(p2.position);
\r
259 /** calculate minimal resting distance (largest distance that should not be fallen below) */
\r
260 let collisionDistance = Math.min(this.width / this.numPointsWidth, this.height / this.numPointsHeight);
\r
261 // collisionDistance /= 2;
\r
262 /** calculate "sphere intersection" */
\r
263 if (dist < collisionDistance) {
\r
264 // p1.movableTmp = false;
\r
265 // p2.movableTmp = false;
\r
267 /** vectors from p1 to p2 and the other way round */
\r
268 let diffP2P1 = p1.position.clone().sub(p2.position).normalize();
\r
269 diffP2P1.multiplyScalar((collisionDistance - dist) * 1.001 / 2);
\r
270 let diffP1P2 = diffP2P1.clone().multiplyScalar(-1);
\r
272 // let v1 = p1.position.clone().sub(p1.previous).normalize();
\r
273 // let v2 = p2.position.clone().sub(p2.previous).normalize();
\r
275 // let factor1 = (Math.PI - Math.acos(v1.dot(diffP2P1))) / Math.PI * 2;
\r
276 // let factor2 = (Math.PI - Math.acos(v2.dot(diffP1P2))) / Math.PI * 2;
\r
278 /** move masses apart */
\r
280 p1.position.add(diffP2P1);
\r
281 //p1.position.add(diffP2P1.multiplyScalar(factor1));
\r
283 p2.position.add(diffP1P2);
\r
284 //p2.position.add(diffP1P2.multiplyScalar(factor2));
\r
289 blow(camPos, intersects) {
\r
290 let face = intersects[0].face;
\r
291 /** vector from cam to intersection (wind) */
\r
292 let dir = intersects[0].point.clone().sub(camPos).multiplyScalar(100);
\r
293 /** apply to all vertices of affected face */
\r
294 this.masses[face.a].addForce(dir);
\r
295 this.masses[face.b].addForce(dir);
\r
296 this.masses[face.c].addForce(dir);
\r
298 drag(mousePosWorld, index) {
\r
299 /** calculate vector from vertex to cursor */
\r
300 let dir = mousePosWorld.clone().sub(this.masses[index].position).multiplyScalar(200);
\r
301 /** apply to grabbed vertex */
\r
302 this.masses[index].addForce(dir);
\r
306 * helper function to calculate index of vertex
\r
307 * in "vertices" array based on its x and y positions
\r
309 * @param {number} x - x index of vertex
\r
310 * @param {number} y - y index of vertex
\r
312 getVertexIndex(x, y) {
\r
313 return y * this.numPointsWidth + x;
\r
315 getX(i) { return i % this.numPointsWidth; }
\r
316 getY(i) { return Math.floor(i / this.numPointsWidth); }
\r