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 |
|
---|
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 |
|
---|
110 | var newPos = this.tmp.subVectors(this.position, this.previous);
|
---|
111 | newPos.multiplyScalar(DRAG).add(this.position);
|
---|
112 | newPos.add(this.a.multiplyScalar(timesq));
|
---|
113 |
|
---|
114 | this.tmp = this.previous;
|
---|
115 | this.previous = this.position;
|
---|
116 | this.position = newPos;
|
---|
117 |
|
---|
118 | this.a.set(0, 0, 0);
|
---|
119 |
|
---|
120 | }
|
---|
121 |
|
---|
122 |
|
---|
123 | var diff = new THREE.Vector3();
|
---|
124 |
|
---|
125 | function satisifyConstrains(p1, p2, distance) {
|
---|
126 |
|
---|
127 | diff.subVectors(p2.position, p1.position);
|
---|
128 | var currentDist = diff.length();
|
---|
129 | if (currentDist==0) return; // prevents division by 0
|
---|
130 | var correction = diff.multiplyScalar(1 - distance/currentDist);
|
---|
131 | var correctionHalf = correction.multiplyScalar(0.5);
|
---|
132 | p1.position.add(correctionHalf);
|
---|
133 | p2.position.sub(correctionHalf);
|
---|
134 |
|
---|
135 | }
|
---|
136 |
|
---|
137 | function Cloth(w, h) {
|
---|
138 | w = w || 10;
|
---|
139 | h = h || 10;
|
---|
140 | this.w = w;
|
---|
141 | this.h = h;
|
---|
142 |
|
---|
143 | var particles = [];
|
---|
144 | var constrains = [];
|
---|
145 |
|
---|
146 | var u, v;
|
---|
147 |
|
---|
148 | // Create particles
|
---|
149 | for (v=0;v<=h;v++) {
|
---|
150 | for (u=0;u<=w;u++) {
|
---|
151 | particles.push(
|
---|
152 | new Particle(u/w, v/h, 0, MASS)
|
---|
153 | );
|
---|
154 | }
|
---|
155 | }
|
---|
156 |
|
---|
157 | // Structural
|
---|
158 |
|
---|
159 | for (v=0;v<h;v++) {
|
---|
160 | for (u=0;u<w;u++) {
|
---|
161 |
|
---|
162 | constrains.push([
|
---|
163 | particles[index(u, v)],
|
---|
164 | particles[index(u, v+1)],
|
---|
165 | restDistance
|
---|
166 | ]);
|
---|
167 |
|
---|
168 | constrains.push([
|
---|
169 | particles[index(u, v)],
|
---|
170 | particles[index(u+1, v)],
|
---|
171 | restDistance
|
---|
172 | ]);
|
---|
173 |
|
---|
174 | }
|
---|
175 | }
|
---|
176 |
|
---|
177 | for (u=w, v=0;v<h;v++) {
|
---|
178 | constrains.push([
|
---|
179 | particles[index(u, v)],
|
---|
180 | particles[index(u, v+1)],
|
---|
181 | restDistance
|
---|
182 |
|
---|
183 | ]);
|
---|
184 | }
|
---|
185 |
|
---|
186 | for (v=h, u=0;u<w;u++) {
|
---|
187 | constrains.push([
|
---|
188 | particles[index(u, v)],
|
---|
189 | particles[index(u+1, v)],
|
---|
190 | restDistance
|
---|
191 | ]);
|
---|
192 | }
|
---|
193 |
|
---|
194 |
|
---|
195 | // While many system uses shear and bend springs,
|
---|
196 | // the relax constrains model seem to be just fine
|
---|
197 | // using structural springs.
|
---|
198 | // Shear
|
---|
199 | var diagonalDist = Math.sqrt(restDistance * restDistance * 2);
|
---|
200 |
|
---|
201 |
|
---|
202 | for (v=0;v<h;v++) {
|
---|
203 | for (u=0;u<w;u++) {
|
---|
204 |
|
---|
205 | constrains.push([
|
---|
206 | particles[index(u, v)],
|
---|
207 | particles[index(u+1, v+1)],
|
---|
208 | diagonalDist
|
---|
209 | ]);
|
---|
210 |
|
---|
211 | constrains.push([
|
---|
212 | particles[index(u+1, v)],
|
---|
213 | particles[index(u, v+1)],
|
---|
214 | diagonalDist
|
---|
215 | ]);
|
---|
216 |
|
---|
217 | }
|
---|
218 | }
|
---|
219 |
|
---|
220 | this.particles = particles;
|
---|
221 | this.constrains = constrains;
|
---|
222 |
|
---|
223 | function index(u, v) {
|
---|
224 | return u + v * (w + 1);
|
---|
225 | }
|
---|
226 |
|
---|
227 | this.index = index;
|
---|
228 |
|
---|
229 | }
|
---|
230 |
|
---|
231 | function setPinning(bool){
|
---|
232 | pinning = bool;
|
---|
233 | }
|
---|
234 |
|
---|
235 | function simulate(time) {
|
---|
236 | if (!lastTime) {
|
---|
237 | lastTime = time;
|
---|
238 | return;
|
---|
239 | }
|
---|
240 |
|
---|
241 | // TIMESTEP = (time - lastTime);
|
---|
242 | // TIMESTEP = (TIMESTEP > 30) ? TIMESTEP / 1000 : 30 / 1000;
|
---|
243 | // TIMESTEP_SQ = TIMESTEP * TIMESTEP;
|
---|
244 | // lastTime = time;
|
---|
245 | // console.log(TIMESTEP);
|
---|
246 |
|
---|
247 | var i, il, particles, particle, pt, constrains, constrain;
|
---|
248 |
|
---|
249 | // Aerodynamics forces
|
---|
250 | if (wind) {
|
---|
251 | var face, faces = clothGeometry.faces, normal;
|
---|
252 |
|
---|
253 | particles = cloth.particles;
|
---|
254 |
|
---|
255 | for (i=0,il=faces.length;i<il;i++) {
|
---|
256 | face = faces[i];
|
---|
257 | normal = face.normal;
|
---|
258 |
|
---|
259 | tmpForce.copy(normal).normalize().multiplyScalar(normal.dot(windForce));
|
---|
260 | particles[face.a].addForce(tmpForce);
|
---|
261 | particles[face.b].addForce(tmpForce);
|
---|
262 | particles[face.c].addForce(tmpForce);
|
---|
263 | }
|
---|
264 | }
|
---|
265 |
|
---|
266 | for (particles = cloth.particles, i=0, il = particles.length
|
---|
267 | ;i<il;i++) {
|
---|
268 | particle = particles[i];
|
---|
269 | particle.addForce(gravity);
|
---|
270 | //
|
---|
271 | //var x = particle.position.x, y = particle.position.y, z = particle.position.z, t=Date.now() / 1000;
|
---|
272 | //windForce.set(Math.sin(x*y*t), Math.cos(z*t), Math.sin(Math.cos(5*x*y*z))).multiplyScalar(0);
|
---|
273 | //particle.addForce(windForce);
|
---|
274 | particle.integrate(TIMESTEP_SQ);
|
---|
275 | }
|
---|
276 |
|
---|
277 | // Start Constrains
|
---|
278 |
|
---|
279 | constrains = cloth.constrains,
|
---|
280 | il = constrains.length;
|
---|
281 | for (i=0;i<il;i++) {
|
---|
282 | constrain = constrains[i];
|
---|
283 | satisifyConstrains(constrain[0], constrain[1], constrain[2]);
|
---|
284 | }
|
---|
285 |
|
---|
286 | // Ball Constrains
|
---|
287 |
|
---|
288 |
|
---|
289 | ballPosition.z = -Math.sin(Date.now()/300) * 90 ; //+ 40;
|
---|
290 | ballPosition.x = Math.cos(Date.now()/200) * 70
|
---|
291 |
|
---|
292 | // Pin Constrains
|
---|
293 | for (i=0, il=pins.length;i<il;i++) {
|
---|
294 | var xy = pins[i];
|
---|
295 | var p = particles[xy];
|
---|
296 | p.position.copy(p.original);
|
---|
297 | p.previous.copy(p.original);
|
---|
298 | }
|
---|
299 | } |
---|