1 | /*
|
---|
2 | * Aug 9 2012
|
---|
3 | * Its Singapore's National Day, so
|
---|
4 | * Making a quick tweaks to simulate the Singapore flag in the wind
|
---|
5 | *
|
---|
6 | */
|
---|
7 | /*
|
---|
8 | * Aug 3 2012
|
---|
9 | *
|
---|
10 | * Since I started working for a new startup not too long ago,
|
---|
11 | * I commute between home and work for over 2 hours a day.
|
---|
12 | * Although this means less time on three.js,
|
---|
13 | * I try getting a little coding on the train.
|
---|
14 | *
|
---|
15 | * This set of experiments started from a simple hook's law doodle,
|
---|
16 | * to spring simulation, string simulation, and I realized
|
---|
17 | * I once again stepped onto physics and particle simulation,
|
---|
18 | * this time, more specifically soft body physics.
|
---|
19 | *
|
---|
20 | * Based on the "Advanced Character Physics" article,
|
---|
21 | * this experiment attempts to use a "massless"
|
---|
22 | * cloth simulation model. It's somewhat similiar
|
---|
23 | * but simplier to most cloth simulations I found.
|
---|
24 | *
|
---|
25 | * This was coded out fairly quickly, so expect more to come
|
---|
26 | * meanwhile feel free to experiment yourself and share
|
---|
27 | *
|
---|
28 | * Cheers,
|
---|
29 | * Graphics Noob (aka @Blurspline, zz85)
|
---|
30 | */
|
---|
31 |
|
---|
32 | // Suggested Readings
|
---|
33 |
|
---|
34 | // Advanced Character Physics by Thomas Jakobsen Character - http://web.archive.org/web/20070610223835/http:/www.teknikus.dk/tj/gdc2001.htm
|
---|
35 | // http://freespace.virgin.net/hugo.elias/models/m_cloth.htm
|
---|
36 | // http://en.wikipedia.org/wiki/Cloth_modeling
|
---|
37 | // http://cg.alexandra.dk/tag/spring-mass-system/
|
---|
38 | // Real-time Cloth Animation http://www.darwin3d.com/gamedev/articles/col0599.pdf
|
---|
39 |
|
---|
40 | var DAMPING = 0.02;
|
---|
41 | var DRAG = 1 - DAMPING;
|
---|
42 | var MASS = .2;
|
---|
43 | var restDistance = 20;
|
---|
44 |
|
---|
45 |
|
---|
46 | var xSegs = 15; // ratio is 2:3
|
---|
47 | var ySegs = 10; //
|
---|
48 |
|
---|
49 | var clothFunction = plane(restDistance * xSegs, restDistance * ySegs);
|
---|
50 |
|
---|
51 | var cloth = new Cloth(xSegs, ySegs);
|
---|
52 |
|
---|
53 | var GRAVITY = 981 * 1.4; //
|
---|
54 | var gravity = new THREE.Vector3( 0, -GRAVITY, 0 ).multiplyScalar(MASS);
|
---|
55 |
|
---|
56 |
|
---|
57 | var TIMESTEP = 18 / 1000;
|
---|
58 | var TIMESTEP_SQ = TIMESTEP * TIMESTEP;
|
---|
59 |
|
---|
60 | var pins = [];
|
---|
61 | var pinning = true;
|
---|
62 |
|
---|
63 | var wind = true;
|
---|
64 | var windStrength = 2;
|
---|
65 | var windForce = new THREE.Vector3(0,0,0);
|
---|
66 |
|
---|
67 | var ballPosition = new THREE.Vector3(0, -45, 0);
|
---|
68 | var ballSize = 60; //40
|
---|
69 |
|
---|
70 | var tmpForce = new THREE.Vector3();
|
---|
71 |
|
---|
72 | var lastTime;
|
---|
73 |
|
---|
74 |
|
---|
75 |
|
---|
76 |
|
---|
77 | function plane(width, height) {
|
---|
78 |
|
---|
79 | return function(u, v) {
|
---|
80 | var x = u * width; //(u-0.5)
|
---|
81 | var y = v * height;
|
---|
82 | var z = 0;
|
---|
83 |
|
---|
84 | return new THREE.Vector3(x, y, z);
|
---|
85 | };
|
---|
86 | }
|
---|
87 |
|
---|
88 | function Particle(x, y, z, mass) {
|
---|
89 | this.position = clothFunction(x, y); // position
|
---|
90 | this.previous = clothFunction(x, y); // previous
|
---|
91 | this.original = clothFunction(x, y);
|
---|
92 | this.a = new THREE.Vector3(0, 0, 0); // acceleration
|
---|
93 | this.mass = mass;
|
---|
94 | this.invMass = 1 / mass;
|
---|
95 | this.tmp = new THREE.Vector3();
|
---|
96 | this.tmp2 = new THREE.Vector3();
|
---|
97 | }
|
---|
98 |
|
---|
99 | // Force -> Acceleration
|
---|
100 | Particle.prototype.addForce = function(force) {
|
---|
101 | this.a.add(
|
---|
102 | this.tmp2.copy(force).multiplyScalar(this.invMass)
|
---|
103 | );
|
---|
104 | };
|
---|
105 |
|
---|
106 |
|
---|
107 | // Performs verlet integration
|
---|
108 | Particle.prototype.integrate = function(timesq) {
|
---|
109 | var newPos = this.tmp.subVectors(this.position, this.previous);
|
---|
110 | newPos.multiplyScalar(DRAG).add(this.position);
|
---|
111 | newPos.add(this.a.multiplyScalar(timesq));
|
---|
112 |
|
---|
113 | this.tmp = this.previous;
|
---|
114 | this.previous = this.position;
|
---|
115 | this.position = newPos;
|
---|
116 |
|
---|
117 | this.a.set(0, 0, 0);
|
---|
118 | }
|
---|
119 |
|
---|
120 |
|
---|
121 | var diff = new THREE.Vector3();
|
---|
122 |
|
---|
123 | function satisifyConstrains(p1, p2, distance) {
|
---|
124 | diff.subVectors(p2.position, p1.position);
|
---|
125 | var currentDist = diff.length();
|
---|
126 | if (currentDist==0) return; // prevents division by 0
|
---|
127 | var correction = diff.multiplyScalar(1 - distance/currentDist);
|
---|
128 | var correctionHalf = correction.multiplyScalar(0.5);
|
---|
129 | p1.position.add(correctionHalf);
|
---|
130 | p2.position.sub(correctionHalf);
|
---|
131 |
|
---|
132 | // float difference = (restingDistance - d) / d
|
---|
133 | // im1 = 1 / p1.mass // inverse mass quantities
|
---|
134 | // im2 = 1 / p2.mass
|
---|
135 | // p1.position += delta * (im1 / (im1 + im2)) * stiffness * difference
|
---|
136 |
|
---|
137 | }
|
---|
138 |
|
---|
139 | function Cloth(w, h) {
|
---|
140 | w = w || 10;
|
---|
141 | h = h || 10;
|
---|
142 | this.w = w;
|
---|
143 | this.h = h;
|
---|
144 |
|
---|
145 | var particles = [];
|
---|
146 | var constrains = [];
|
---|
147 |
|
---|
148 | var u, v;
|
---|
149 |
|
---|
150 | // Create particles
|
---|
151 | for (v=0;v<=h;v++) {
|
---|
152 | for (u=0;u<=w;u++) {
|
---|
153 | particles.push(
|
---|
154 | new Particle(u/w, v/h, 0, MASS)
|
---|
155 | );
|
---|
156 | }
|
---|
157 | }
|
---|
158 |
|
---|
159 | // Structural
|
---|
160 |
|
---|
161 | for (v=0;v<h;v++) {
|
---|
162 | for (u=0;u<w;u++) {
|
---|
163 |
|
---|
164 | constrains.push([
|
---|
165 | particles[index(u, v)],
|
---|
166 | particles[index(u, v+1)],
|
---|
167 | restDistance
|
---|
168 | ]);
|
---|
169 |
|
---|
170 | constrains.push([
|
---|
171 | particles[index(u, v)],
|
---|
172 | particles[index(u+1, v)],
|
---|
173 | restDistance
|
---|
174 | ]);
|
---|
175 |
|
---|
176 | }
|
---|
177 | }
|
---|
178 |
|
---|
179 | for (u=w, v=0;v<h;v++) {
|
---|
180 | constrains.push([
|
---|
181 | particles[index(u, v)],
|
---|
182 | particles[index(u, v+1)],
|
---|
183 | restDistance
|
---|
184 |
|
---|
185 | ]);
|
---|
186 | }
|
---|
187 |
|
---|
188 | for (v=h, u=0;u<w;u++) {
|
---|
189 | constrains.push([
|
---|
190 | particles[index(u, v)],
|
---|
191 | particles[index(u+1, v)],
|
---|
192 | restDistance
|
---|
193 | ]);
|
---|
194 | }
|
---|
195 |
|
---|
196 |
|
---|
197 | // While many system uses shear and bend springs,
|
---|
198 | // the relax constrains model seem to be just fine
|
---|
199 | // using structural springs.
|
---|
200 | // Shear
|
---|
201 | var diagonalDist = Math.sqrt(restDistance * restDistance * 2);
|
---|
202 |
|
---|
203 |
|
---|
204 | for (v=0;v<h;v++) {
|
---|
205 | for (u=0;u<w;u++) {
|
---|
206 |
|
---|
207 | constrains.push([
|
---|
208 | particles[index(u, v)],
|
---|
209 | particles[index(u+1, v+1)],
|
---|
210 | diagonalDist
|
---|
211 | ]);
|
---|
212 |
|
---|
213 | constrains.push([
|
---|
214 | particles[index(u+1, v)],
|
---|
215 | particles[index(u, v+1)],
|
---|
216 | diagonalDist
|
---|
217 | ]);
|
---|
218 |
|
---|
219 | }
|
---|
220 | }
|
---|
221 |
|
---|
222 |
|
---|
223 | // // Bend
|
---|
224 |
|
---|
225 | // var wlen = restDistance * 2;
|
---|
226 | // var hlen = restDistance * 2;
|
---|
227 | // diagonalDist = Math.sqrt(wlen * wlen + hlen * hlen);
|
---|
228 |
|
---|
229 | // for (v=0;v<h-1;v++) {
|
---|
230 | // for (u=0;u<w-1;u++) {
|
---|
231 | // constrains.push([
|
---|
232 | // particles[index(u, v)],
|
---|
233 | // particles[index(u+2, v)],
|
---|
234 | // wlen
|
---|
235 | // ]);
|
---|
236 |
|
---|
237 | // constrains.push([
|
---|
238 | // particles[index(u, v)],
|
---|
239 | // particles[index(u, v+2)],
|
---|
240 | // hlen
|
---|
241 | // ]);
|
---|
242 |
|
---|
243 | // constrains.push([
|
---|
244 | // particles[index(u, v)],
|
---|
245 | // particles[index(u+2, v+2)],
|
---|
246 | // diagonalDist
|
---|
247 | // ]);
|
---|
248 |
|
---|
249 | // constrains.push([
|
---|
250 | // particles[index(u, v+2)],
|
---|
251 | // particles[index(u+2, v+2)],
|
---|
252 | // wlen
|
---|
253 | // ]);
|
---|
254 |
|
---|
255 | // constrains.push([
|
---|
256 | // particles[index(u+2, v+2)],
|
---|
257 | // particles[index(u+2, v+2)],
|
---|
258 | // hlen
|
---|
259 | // ]);
|
---|
260 |
|
---|
261 | // constrains.push([
|
---|
262 | // particles[index(u+2, v)],
|
---|
263 | // particles[index(u, v+2)],
|
---|
264 | // diagonalDist
|
---|
265 | // ]);
|
---|
266 |
|
---|
267 | // }
|
---|
268 | // }
|
---|
269 |
|
---|
270 |
|
---|
271 | this.particles = particles;
|
---|
272 | this.constrains = constrains;
|
---|
273 |
|
---|
274 | function index(u, v) {
|
---|
275 | return u + v * (w + 1);
|
---|
276 | }
|
---|
277 |
|
---|
278 | this.index = index;
|
---|
279 |
|
---|
280 | }
|
---|
281 |
|
---|
282 | function setPinning(bool){
|
---|
283 | pinning = bool;
|
---|
284 | }
|
---|
285 |
|
---|
286 | function simulate(time) {
|
---|
287 | if (!lastTime) {
|
---|
288 | lastTime = time;
|
---|
289 | return;
|
---|
290 | }
|
---|
291 |
|
---|
292 | // TIMESTEP = (time - lastTime);
|
---|
293 | // TIMESTEP = (TIMESTEP > 30) ? TIMESTEP / 1000 : 30 / 1000;
|
---|
294 | // TIMESTEP_SQ = TIMESTEP * TIMESTEP;
|
---|
295 | // lastTime = time;
|
---|
296 | // console.log(TIMESTEP);
|
---|
297 |
|
---|
298 | var i, il, particles, particle, pt, constrains, constrain;
|
---|
299 |
|
---|
300 | // Aerodynamics forces
|
---|
301 | if (wind) {
|
---|
302 | var face, faces = clothGeometry.faces, normal;
|
---|
303 |
|
---|
304 | particles = cloth.particles;
|
---|
305 |
|
---|
306 | for (i=0,il=faces.length;i<il;i++) {
|
---|
307 | face = faces[i];
|
---|
308 | normal = face.normal;
|
---|
309 |
|
---|
310 | tmpForce.copy(normal).normalize().multiplyScalar(normal.dot(windForce));
|
---|
311 | particles[face.a].addForce(tmpForce);
|
---|
312 | particles[face.b].addForce(tmpForce);
|
---|
313 | particles[face.c].addForce(tmpForce);
|
---|
314 | }
|
---|
315 | }
|
---|
316 |
|
---|
317 | for (particles = cloth.particles, i=0, il = particles.length
|
---|
318 | ;i<il;i++) {
|
---|
319 | particle = particles[i];
|
---|
320 | particle.addForce(gravity);
|
---|
321 | //
|
---|
322 | // var x = particle.position.x, y = particle.position.y, z = particle.position.z, t=Date.now() / 1000;
|
---|
323 | // windForce.set(Math.sin(x*y*t), Math.cos(z*t), Math.sin(Math.cos(5*x*y*z))).multiplyScalar(100);
|
---|
324 | // particle.addForce(windForce);
|
---|
325 | particle.integrate(TIMESTEP_SQ);
|
---|
326 | }
|
---|
327 |
|
---|
328 | // Start Constrains
|
---|
329 |
|
---|
330 | constrains = cloth.constrains,
|
---|
331 | il = constrains.length;
|
---|
332 | for (i=0;i<il;i++) {
|
---|
333 | constrain = constrains[i];
|
---|
334 | satisifyConstrains(constrain[0], constrain[1], constrain[2]);
|
---|
335 | }
|
---|
336 |
|
---|
337 | // Ball Constrains
|
---|
338 |
|
---|
339 |
|
---|
340 | ballPosition.z = -Math.sin(Date.now()/300) * 90 ; //+ 40;
|
---|
341 | ballPosition.x = Math.cos(Date.now()/200) * 70
|
---|
342 |
|
---|
343 | if (sphere.visible)
|
---|
344 | for (particles = cloth.particles, i=0, il = particles.length
|
---|
345 | ;i<il;i++) {
|
---|
346 | particle = particles[i];
|
---|
347 | pos = particle.position;
|
---|
348 | diff.subVectors(pos, ballPosition);
|
---|
349 | if (diff.length() < ballSize) {
|
---|
350 | // collided
|
---|
351 | diff.normalize().multiplyScalar(ballSize);
|
---|
352 | pos.copy(ballPosition).add(diff);
|
---|
353 | }
|
---|
354 | }
|
---|
355 |
|
---|
356 | // Pin Constrains
|
---|
357 | if(Boolean(pinning)){
|
---|
358 | for (i=0, il=pins.length;i<il;i++) {
|
---|
359 | var xy = pins[i];
|
---|
360 | var p = particles[xy];
|
---|
361 | p.position.copy(p.original);
|
---|
362 | p.previous.copy(p.original);
|
---|
363 | }
|
---|
364 | }
|
---|
365 |
|
---|
366 | } |
---|