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
7 function vectorLength(a, b) {
\r
8 let v1 = new THREE.Vector3();
\r
10 let v2 = new THREE.Vector3();
\r
13 return v1.sub(v2).length();
\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
30 constructor(a, b, c, d) {
\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
43 export class Spring {
\r
51 * set vertex indices
\r
52 * and calculate inital length based on the
\r
54 * @param {Array<Vector3>} vertices
\r
55 * @param {number} index1
\r
56 * @param {number} index2
\r
58 constructor(vertices, index1, index2) {
\r
59 this.index1 = index1;
\r
60 this.index2 = index2;
\r
62 let length = vectorLength(vertices[index1], vertices[index2]);
\r
63 this.restLength = length;
\r
64 this.currentLength = length;
\r
67 getDirection(vertices) {
\r
68 let direction = new THREE.Vector3();
\r
69 direction.copy(vertices[this.index1]);
\r
71 direction.sub(vertices[this.index2]);
\r
72 direction.divideScalar(vectorLength(vertices[this.index1], vertices[this.index2]));
\r
78 let length = vectorLength(vertices[this.index1], vertices[this.index2]);
\r
79 this.currentLength = length;
\r
84 * Class representing a single piece of cloth
\r
85 * contains THREE JS geometry,
\r
86 * logically represented by an array of adjacent faces
\r
87 * and vertex weights which are accessed by the same
\r
88 * indices as the vertices in the Mesh
\r
90 export class Cloth {
\r
93 geometry = new THREE.Geometry();
\r
99 vertexRigidness = [];
\r
103 externalForces = [];
\r
106 windFactor = new THREE.Vector3(0, 0, 0);
\r
109 * creates a rectangular piece of cloth
\r
110 * takes the size of the cloth
\r
111 * and the number of vertices it should be composed of
\r
112 * @param {number} width - width of the cloth
\r
113 * @param {number} height - height of the cloth
\r
114 * @param {number} numPointsWidth - number of vertices in horizontal direction
\r
115 * @param {number} numPointsHeight - number of vertices in vertical direction
\r
117 createBasic(width, height, numPointsWidth, numPointsHeight) {
\r
118 /** resulting vertices and faces */
\r
122 this.width = width;
\r
123 this.height = height;
\r
124 this.numPointsWidth = numPointsWidth;
\r
125 this.numPointsHeight = numPointsHeight;
\r
128 * distance between two vertices horizontally/vertically
\r
129 * divide by the number of points minus one
\r
130 * because there are (n - 1) lines between n vertices
\r
132 let stepWidth = width / (numPointsWidth - 1);
\r
133 let stepHeight = height / (numPointsHeight - 1);
\r
136 * iterate over the number of vertices in x/y axis
\r
137 * and add a new Vector3 to "vertices"
\r
139 for (let y = 0; y < numPointsHeight; y++) {
\r
140 for (let x = 0; x < numPointsWidth; x++) {
\r
142 new THREE.Vector3((x - ((numPointsWidth-1)/2)) * stepWidth, height - (y + ((numPointsHeight-1)/2)) * stepHeight, 0)
\r
148 * helper function to calculate index of vertex
\r
149 * in "vertices" array based on its x and y positions
\r
151 * @param {number} x - x index of vertex
\r
152 * @param {number} y - y index of vertex
\r
154 function getVertexIndex(x, y) {
\r
155 return y * numPointsWidth + x;
\r
159 * generate faces based on 4 vertices
\r
160 * and 6 springs each
\r
162 for (let y = 0; y < numPointsHeight - 1; y++) {
\r
163 for (let x = 0; x < numPointsWidth - 1; x++) {
\r
164 let newFace = new Face(
\r
165 getVertexIndex(x, y),
\r
166 getVertexIndex(x, y + 1),
\r
167 getVertexIndex(x + 1, y),
\r
168 getVertexIndex(x + 1, y + 1),
\r
171 newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x + 1, y))); // oben
\r
172 newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x, y + 1))); // links
\r
173 newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x + 1, y + 1))); // oben links -> unten rechts diagonal
\r
174 newFace.springs.push(new Spring(vertices, getVertexIndex(x + 1, y), getVertexIndex(x, y + 1))); // oben rechts -> unten links diagonal
\r
175 newFace.springs.push(new Spring(vertices, getVertexIndex(x + 1, y), getVertexIndex(x + 1, y + 1))); // rechts
\r
176 newFace.springs.push(new Spring(vertices, getVertexIndex(x, y + 1), getVertexIndex(x + 1, y + 1))); // unten
\r
178 faces.push(newFace);
\r
183 * call createExplicit
\r
184 * with generated vertices and faces
\r
186 this.createExplicit(vertices, faces);
\r
189 * hand cloth from left and right upper corners
\r
191 this.fixedPoints.push(getVertexIndex(0, 0));
\r
192 this.fixedPoints.push(getVertexIndex(0, 19));
\r
196 * Generate THREE JS Geometry
\r
197 * (list of vertices and list of indices representing triangles)
\r
198 * and calculate the weight of each face and split it between
\r
199 * surrounding vertices
\r
200 * @param {Array<Vector3>} vertices
\r
201 * @param {Array<Face>} faces
\r
203 createExplicit(vertices, faces) {
\r
206 * Copy vertices and initialize vertex weights to 0
\r
208 for (let i in vertices) {
\r
209 this.geometry.vertices.push(vertices[i].clone());
\r
210 this.previousPositions.push(vertices[i].clone());
\r
211 // this.geometry.vertices.push(vertices[i]);
\r
212 // this.previousPositions.push(vertices[i]);
\r
213 this.vertexWeights.push(0);
\r
214 this.vertexRigidness.push(false);
\r
215 this.externalForces.push(new THREE.Vector3(0,0,0));
\r
219 * generate two triangles per face,
\r
220 * calculate weight of face as its area
\r
221 * and split between the 4 vertices
\r
223 for (let i in faces) {
\r
224 let face = faces[i];
\r
226 /** copy faces to class member */
\r
227 this.faces.push(face);
\r
229 /** generate triangles */
\r
230 this.geometry.faces.push(new THREE.Face3(
\r
231 face.a, face.b, face.c
\r
233 this.geometry.faces.push(new THREE.Face3(
\r
234 face.c, face.b, face.d
\r
238 * calculate area of face as combined area of
\r
239 * its two composing triangles
\r
241 let xLength = vectorLength(this.geometry.vertices[face.b], this.geometry.vertices[face.a]);
\r
242 let yLength = vectorLength(this.geometry.vertices[face.c], this.geometry.vertices[face.a]);
\r
243 let weight = xLength * yLength / 2;
\r
245 xLength = vectorLength(this.geometry.vertices[face.b], this.geometry.vertices[face.d]);
\r
246 yLength = vectorLength(this.geometry.vertices[face.c], this.geometry.vertices[face.d]);
\r
247 weight += xLength * yLength / 2;
\r
252 * split weight equally between four surrounding vertices
\r
254 this.vertexWeights[face.a] += weight / 4;
\r
255 this.vertexWeights[face.b] += weight / 4;
\r
256 this.vertexWeights[face.c] += weight / 4;
\r
257 this.vertexWeights[face.d] += weight / 4;
\r
261 * let THREE JS compute bounding sphere around generated mesh
\r
262 * needed for View Frustum Culling internally
\r
264 this.geometry.computeBoundingSphere();
\r
265 this.geometry.computeFaceNormals();
\r
266 this.geometry.computeVertexNormals();
\r
270 * generate a debug mesh for visualizing
\r
271 * vertices and springs of the cloth
\r
272 * and add it to scene for rendering
\r
273 * @param {Scene} scene - Scene to add Debug Mesh to
\r
275 createDebugMesh(scene) {
\r
277 * helper function to generate a single line
\r
278 * between two Vertices with a given color
\r
279 * @param {Vector3} from
\r
280 * @param {Vector3} to
\r
281 * @param {number} color
\r
283 function addLine(from, to, color) {
\r
284 let geometry = new THREE.Geometry();
\r
285 geometry.vertices.push(from);
\r
286 geometry.vertices.push(to);
\r
287 let material = new THREE.LineBasicMaterial({ color: color, linewidth: 10 });
\r
288 let line = new THREE.Line(geometry, material);
\r
289 line.renderOrder = 1;
\r
293 * helper function to generate a small sphere
\r
294 * at a given Vertex Position with color
\r
295 * @param {Vector3} point
\r
296 * @param {number} color
\r
298 function addPoint(point, color) {
\r
299 const geometry = new THREE.SphereGeometry(0.05, 32, 32);
\r
300 const material = new THREE.MeshBasicMaterial({ color: color });
\r
301 const sphere = new THREE.Mesh(geometry, material);
\r
302 sphere.position.set(point.x, point.y, point.z);
\r
306 let lineColor = 0x000000;
\r
307 let pointColor = 0xff00000;
\r
310 * generate one line for each of the 6 springs
\r
311 * and one point for each of the 4 vertices
\r
312 * for all of the faces
\r
314 for (let i in this.faces) {
\r
315 let face = this.faces[i];
\r
316 addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.b], lineColor);
\r
317 addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.c], lineColor);
\r
318 addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.d], lineColor);
\r
319 addLine(this.geometry.vertices[face.b], this.geometry.vertices[face.c], lineColor);
\r
320 addLine(this.geometry.vertices[face.b], this.geometry.vertices[face.d], lineColor);
\r
321 addLine(this.geometry.vertices[face.c], this.geometry.vertices[face.d], lineColor);
\r
323 addPoint(this.geometry.vertices[face.a], pointColor);
\r
324 addPoint(this.geometry.vertices[face.b], pointColor);
\r
325 addPoint(this.geometry.vertices[face.c], pointColor);
\r
326 addPoint(this.geometry.vertices[face.d], pointColor);
\r
330 previousPositions = [];
\r
334 * @param {number} dt time in seconds since last frame
\r
337 for (let i in this.geometry.vertices) {
\r
338 let acceleration = this.getAcceleration(i, dt);
\r
340 //acceleration.clampLength(0, 10);
\r
342 if (Math.abs(acceleration.length()) <= 10e-4) {
\r
343 acceleration.set(0, 0, 0);
\r
346 let currentPosition = this.verlet(this.geometry.vertices[i].clone(), this.previousPositions[i].clone(), acceleration, dt);
\r
347 //let currentPosition = this.euler(this.geometry.vertices[i], acceleration, dt);
\r
349 this.previousPositions[i].copy(this.geometry.vertices[i]);
\r
350 this.geometry.vertices[i].copy(currentPosition);
\r
353 this.checkIntersect();
\r
357 for (let face of this.faces) {
\r
358 for (let spring of face.springs) {
\r
359 spring.update(this.geometry.vertices);
\r
364 * let THREE JS compute bounding sphere around generated mesh
\r
365 * needed for View Frustum Culling internally
\r
368 this.geometry.verticesNeedUpdate = true;
\r
369 this.geometry.elementsNeedUpdate = true;
\r
370 this.geometry.computeBoundingSphere();
\r
371 this.geometry.computeFaceNormals();
\r
372 this.geometry.computeVertexNormals();
\r
377 let npw = this.numPointsWidth;
\r
378 function getX(i, ) { return i % npw; }
\r
379 function getY(i) { return Math.floor(i / npw); }
\r
380 for (let i in this.geometry.vertices) {
\r
381 for (let j in this.geometry.vertices) {
\r
382 this.vertexRigidness[i] = false;
\r
383 this.vertexRigidness[j] = false;
\r
384 if (i == j || (Math.abs(getX(i) - getX(j)) == 1 && Math.abs(getY(i) - getY(j)) == 1))
\r
386 let posI = this.geometry.vertices[i];
\r
387 let posJ = this.geometry.vertices[j];
\r
388 let dist = posI.distanceTo(posJ);
\r
389 const collisionDistance = Math.min(this.width / this.numPointsWidth, this.height / this.numPointsHeight);
\r
390 if (dist < collisionDistance) {
\r
391 this.vertexRigidness[i] = true;
\r
392 this.vertexRigidness[j] = true;
\r
393 let diff = this.geometry.vertices[i].clone().sub(this.geometry.vertices[j]).normalize().multiplyScalar((collisionDistance - dist) * 1.001 / 2);
\r
394 if (!(this.fixedPoints.includes(i) || this.fixedPoints.includes(j))) {
\r
395 this.geometry.vertices[i].add(diff);
\r
396 this.geometry.vertices[j].sub(diff);
\r
404 * Equation of motion for each vertex which represents the acceleration
\r
405 * @param {number} vertexIndex The index of the current vertex whose acceleration should be calculated
\r
406 * @param {number} dt The time passed since last frame
\r
408 getAcceleration(vertexIndex, dt) {
\r
409 if (this.fixedPoints.includes(parseInt(vertexIndex)) ||
\r
410 this.vertexRigidness[vertexIndex]) {
\r
411 return new THREE.Vector3(0, 0, 0);
\r
414 let externalForce = this.externalForces[vertexIndex];
\r
415 let vertex = this.geometry.vertices[vertexIndex];//.add(externalForce);
\r
418 let M = this.vertexWeights[vertexIndex];
\r
419 // constant gravity
\r
420 let g = new THREE.Vector3(0, -9.8, 0);
\r
425 let fWind = new THREE.Vector3(
\r
426 this.windFactor.x * (Math.sin(vertex.x * vertex.y * this.time)+1),
\r
427 this.windFactor.y * Math.cos(vertex.z * this.time),
\r
428 this.windFactor.z * Math.sin(Math.cos(5 * vertex.x * vertex.y * vertex.z))
\r
430 //console.log(fWind);
\r
433 * constant determined by the properties of the surrounding fluids (air)
\r
434 * achievement of cloth effects through try out
\r
438 let velocity = new THREE.Vector3(
\r
439 (this.previousPositions[vertexIndex].x - vertex.x) / dt,
\r
440 (this.previousPositions[vertexIndex].y - vertex.y) / dt,
\r
441 (this.previousPositions[vertexIndex].z - vertex.z) / dt
\r
444 //console.log(velocity, vertex, this.previousPositions[vertexIndex]);
\r
446 let fAirResistance = velocity.multiply(velocity).multiplyScalar(-a);
\r
448 let springSum = new THREE.Vector3(0, 0, 0);
\r
450 // Get the bounding springs and add them to the needed springs
\r
453 const numPointsX = this.numPointsWidth;
\r
454 const numPointsY = this.numPointsHeight;
\r
455 const numFacesX = numPointsX - 1;
\r
456 const numFacesY = numPointsY - 1;
\r
458 function getFaceIndex(x, y) {
\r
459 return y * numFacesX + x;
\r
462 let indexX = vertexIndex % numPointsX;
\r
463 let indexY = Math.floor(vertexIndex / numPointsX);
\r
469 // 2 oben links -> unten rechts diagonal
\r
470 // 3 oben rechts -> unten links diagonal
\r
474 let ul = indexX > 0 && indexY < numPointsY - 1;
\r
475 let ur = indexX < numPointsX - 1 && indexY < numPointsY - 1;
\r
476 let ol = indexX > 0 && indexY > 0;
\r
477 let or = indexX < numPointsX - 1 && indexY > 0;
\r
480 let faceUL = this.faces[getFaceIndex(indexX - 1, indexY)];
\r
481 springs.push(faceUL.springs[3]);
\r
483 springs.push(faceUL.springs[0]);
\r
484 springs.push(faceUL.springs[4]);
\r
487 let faceUR = this.faces[getFaceIndex(indexX, indexY)];
\r
488 springs.push(faceUR.springs[2]);
\r
490 springs.push(faceUR.springs[0]);
\r
492 springs.push(faceUR.springs[1]);
\r
495 let faceOL = this.faces[getFaceIndex(indexX - 1, indexY - 1)];
\r
496 springs.push(faceOL.springs[2]);
\r
497 springs.push(faceOL.springs[4]);
\r
498 springs.push(faceOL.springs[5]);
\r
501 let faceOR = this.faces[getFaceIndex(indexX , indexY - 1)];
\r
502 springs.push(faceOR.springs[3]);
\r
504 springs.push(faceOR.springs[1]);
\r
505 springs.push(faceOR.springs[5]);
\r
508 for (let spring of springs) {
\r
509 let springDirection = spring.getDirection(this.geometry.vertices);
\r
511 if (spring.index1 == vertexIndex)
\r
512 springDirection.multiplyScalar(-1);
\r
514 springSum.add(springDirection.multiplyScalar(k * (spring.restLength - spring.currentLength)));
\r
517 let result = new THREE.Vector3(1, 1, 1);
\r
518 result.multiplyScalar(M).multiply(g).add(fWind).add(externalForce).add(fAirResistance).sub(springSum);
\r
520 document.getElementById("Output").innerText = "SpringSum: " + Math.floor(springSum.y);
\r
523 let forceReduktion = 0.8;
\r
524 if(Math.abs(externalForce.z) > threshold){
\r
525 externalForce.z *= forceReduktion;
\r
527 externalForce.z = 0;
\r
530 if(Math.abs(externalForce.y) > threshold){
\r
531 externalForce.y *= forceReduktion;
\r
533 externalForce.y = 0;
\r
536 if(Math.abs(externalForce.x) > threshold){
\r
537 externalForce.x *= forceReduktion;
\r
539 externalForce.x = 0;
\r
548 * The Verlet algorithm as an integrator
\r
549 * to get the next position of a vertex
\r
550 * @param {Vector3} currentPosition
\r
551 * @param {Vector3} previousPosition
\r
552 * @param {Vector3} acceleration
\r
553 * @param {number} passedTime The delta time since last frame
\r
555 verlet(currentPosition, previousPosition, acceleration, passedTime) {
\r
556 // verlet algorithm
\r
557 // next position = 2 * current Position - previous position + acceleration * (passed time)^2
\r
558 // acceleration (dv/dt) = F(net)
\r
559 // Dependency for one vertex: gravity, fluids/air, springs
\r
561 let nextPosition = new THREE.Vector3(
\r
562 (currentPosition.x - previousPosition.x) * DRAG + currentPosition.x + acceleration.x * (passedTime * passedTime),
\r
563 (currentPosition.y - previousPosition.y) * DRAG + currentPosition.y + acceleration.y * (passedTime * passedTime),
\r
564 (currentPosition.z - previousPosition.z) * DRAG + currentPosition.z + acceleration.z * (passedTime * passedTime),
\r
567 // let nextPosition = new THREE.Vector3(
\r
568 // (2 * currentPosition.x) - previousPosition.x + acceleration.x * (passedTime * passedTime),
\r
569 // (2 * currentPosition.y) - previousPosition.y + acceleration.y * (passedTime * passedTime),
\r
570 // (2 * currentPosition.z) - previousPosition.z + acceleration.z * (passedTime * passedTime),
\r
573 return nextPosition;
\r
576 euler(currentPosition, acceleration, passedTime) {
\r
577 let nextPosition = new THREE.Vector3(
\r
578 currentPosition.x + acceleration.x * passedTime,
\r
579 currentPosition.y + acceleration.y * passedTime,
\r
580 currentPosition.z + acceleration.z * passedTime,
\r
583 return nextPosition;
\r
587 let intersect = intersects[0];
\r
588 this.externalForces[intersect.face.a].z -= this.windForce;
\r
589 this.externalForces[intersect.face.b].z -= this.windForce;
\r
590 this.externalForces[intersect.face.c].z -= this.windForce;
\r
593 mousePressed = false;
\r
594 mouseMoved = false;
\r
597 mousePress(intersects){
\r
598 this.mousePressed = true;
\r
599 this.intersects = intersects;
\r
603 mouseMove(mousePos){
\r
604 this.mouseMoved = true;
\r
605 if(this.mousePressed){
\r
606 let intersect = this.intersects[0];
\r
607 this.externalForces[intersect.face.a].add(mousePos.clone().sub(this.geometry.vertices[intersect.face.a]).multiplyScalar(90));
\r
609 this.geometry.vertices[intersect.face.a].x = mousePos.x;
\r
610 this.geometry.vertices[intersect.face.a].y = mousePos.y;
\r
611 this.geometry.vertices[intersect.face.a].z = mousePos.z;
\r
617 this.mousePressed = false;
\r