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
9 v1.set(a.x, a.y, a.z);
\r
10 let v2 = new THREE.Vector3();
\r
11 v2.set(b.x, b.y, b.z);
\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 vertices[this.index1].x,
\r
70 vertices[this.index1].y,
\r
71 vertices[this.index1].z
\r
74 direction.sub(vertices[this.index2]);
\r
75 direction.divideScalar(vectorLength(vertices[this.index1], vertices[this.index2]));
\r
82 * Class representing a single piece of cloth
\r
83 * contains THREE JS geometry,
\r
84 * logically represented by an array of adjacent faces
\r
85 * and vertex weights which are accessed by the same
\r
86 * indices as the vertices in the Mesh
\r
88 export class Cloth {
\r
91 geometry = new THREE.Geometry();
\r
100 * creates a rectangular piece of cloth
\r
101 * takes the size of the cloth
\r
102 * and the number of vertices it should be composed of
\r
103 * @param {number} width - width of the cloth
\r
104 * @param {number} height - height of the cloth
\r
105 * @param {number} numPointsWidth - number of vertices in horizontal direction
\r
106 * @param {number} numPointsHeight - number of vertices in vertical direction
\r
108 createBasic(width, height, numPointsWidth, numPointsHeight) {
\r
109 /** resulting vertices and faces */
\r
114 * distance between two vertices horizontally/vertically
\r
115 * divide by the number of points minus one
\r
116 * because there are (n - 1) lines between n vertices
\r
118 let stepWidth = width / (numPointsWidth - 1);
\r
119 let stepHeight = height / (numPointsHeight - 1);
\r
122 * iterate over the number of vertices in x/y axis
\r
123 * and add a new Vector3 to "vertices"
\r
125 for (let y = 0; y < numPointsHeight; y++) {
\r
126 for (let x = 0; x < numPointsWidth; x++) {
\r
128 new THREE.Vector3(x * stepWidth, height - y * stepHeight, 0)
\r
134 * helper function to calculate index of vertex
\r
135 * in "vertices" array based on its x and y positions
\r
137 * @param {number} x - x index of vertex
\r
138 * @param {number} y - y index of vertex
\r
140 function getVertexIndex(x, y) {
\r
141 return y * numPointsWidth + x;
\r
145 * generate faces based on 4 vertices
\r
146 * and 6 springs each
\r
148 for (let y = 0; y < numPointsHeight - 1; y++) {
\r
149 for (let x = 0; x < numPointsWidth - 1; x++) {
\r
150 let newFace = new Face(
\r
151 getVertexIndex(x, y),
\r
152 getVertexIndex(x, y + 1),
\r
153 getVertexIndex(x + 1, y),
\r
154 getVertexIndex(x + 1, y + 1),
\r
157 newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x + 1, y)));
\r
158 newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x, y + 1)));
\r
159 newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x + 1, y + 1)));
\r
160 newFace.springs.push(new Spring(vertices, getVertexIndex(x + 1, y), getVertexIndex(x, y + 1)));
\r
161 newFace.springs.push(new Spring(vertices, getVertexIndex(x + 1, y), getVertexIndex(x + 1, y + 1)));
\r
162 newFace.springs.push(new Spring(vertices, getVertexIndex(x, y + 1), getVertexIndex(x + 1, y + 1)));
\r
164 faces.push(newFace);
\r
169 * call createExplicit
\r
170 * with generated vertices and faces
\r
172 this.createExplicit(vertices, faces);
\r
176 * Generate THREE JS Geometry
\r
177 * (list of vertices and list of indices representing triangles)
\r
178 * and calculate the weight of each face and split it between
\r
179 * surrounding vertices
\r
180 * @param {Array<Vector3>} vertices
\r
181 * @param {Array<Face>} faces
\r
183 createExplicit(vertices, faces) {
\r
186 * Copy vertices and initialize vertex weights to 0
\r
188 for (let i in vertices) {
\r
189 this.geometry.vertices.push(vertices[i]);
\r
190 this.previousPositions.push(vertices[i]);
\r
191 this.vertexWeights.push(0);
\r
195 * generate two triangles per face,
\r
196 * calculate weight of face as its area
\r
197 * and split between the 4 vertices
\r
199 for (let i in faces) {
\r
200 let face = faces[i];
\r
202 /** copy faces to class member */
\r
203 this.faces.push(face);
\r
205 /** generate triangles */
\r
206 this.geometry.faces.push(new THREE.Face3(
\r
207 face.a, face.b, face.c
\r
209 this.geometry.faces.push(new THREE.Face3(
\r
210 face.c, face.b, face.d
\r
214 * calculate area of face as combined area of
\r
215 * its two composing triangles
\r
217 let xLength = vectorLength(this.geometry.vertices[face.b], this.geometry.vertices[face.a]);
\r
218 let yLength = vectorLength(this.geometry.vertices[face.c], this.geometry.vertices[face.a]);
\r
219 let weight = xLength * yLength / 2;
\r
221 xLength = vectorLength(this.geometry.vertices[face.b], this.geometry.vertices[face.d]);
\r
222 yLength = vectorLength(this.geometry.vertices[face.c], this.geometry.vertices[face.d]);
\r
223 weight += xLength * yLength / 2;
\r
226 * split weight equally between four surrounding vertices
\r
228 this.vertexWeights[face.a] += weight / 4;
\r
229 this.vertexWeights[face.b] += weight / 4;
\r
230 this.vertexWeights[face.c] += weight / 4;
\r
231 this.vertexWeights[face.d] += weight / 4;
\r
235 * let THREE JS compute bounding sphere around generated mesh
\r
236 * needed for View Frustum Culling internally
\r
238 this.geometry.computeBoundingSphere();
\r
242 * generate a debug mesh for visualizing
\r
243 * vertices and springs of the cloth
\r
244 * and add it to scene for rendering
\r
245 * @param {Scene} scene - Scene to add Debug Mesh to
\r
247 createDebugMesh(scene) {
\r
249 * helper function to generate a single line
\r
250 * between two Vertices with a given color
\r
251 * @param {Vector3} from
\r
252 * @param {Vector3} to
\r
253 * @param {number} color
\r
255 function addLine(from, to, color) {
\r
256 let geometry = new THREE.Geometry();
\r
257 geometry.vertices.push(from);
\r
258 geometry.vertices.push(to);
\r
259 let material = new THREE.LineBasicMaterial({ color: color, linewidth: 10 });
\r
260 let line = new THREE.Line(geometry, material);
\r
261 line.renderOrder = 1;
\r
265 * helper function to generate a small sphere
\r
266 * at a given Vertex Position with color
\r
267 * @param {Vector3} point
\r
268 * @param {number} color
\r
270 function addPoint(point, color) {
\r
271 const geometry = new THREE.SphereGeometry(0.05, 32, 32);
\r
272 const material = new THREE.MeshBasicMaterial({ color: color });
\r
273 const sphere = new THREE.Mesh(geometry, material);
\r
274 sphere.position.set(point.x, point.y, point.z);
\r
278 let lineColor = 0x000000;
\r
279 let pointColor = 0xff00000;
\r
282 * generate one line for each of the 6 springs
\r
283 * and one point for each of the 4 vertices
\r
284 * for all of the faces
\r
286 for (let i in this.faces) {
\r
287 let face = this.faces[i];
\r
288 addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.b], lineColor);
\r
289 addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.c], lineColor);
\r
290 addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.d], lineColor);
\r
291 addLine(this.geometry.vertices[face.b], this.geometry.vertices[face.c], lineColor);
\r
292 addLine(this.geometry.vertices[face.b], this.geometry.vertices[face.d], lineColor);
\r
293 addLine(this.geometry.vertices[face.c], this.geometry.vertices[face.d], lineColor);
\r
295 addPoint(this.geometry.vertices[face.a], pointColor);
\r
296 addPoint(this.geometry.vertices[face.b], pointColor);
\r
297 addPoint(this.geometry.vertices[face.c], pointColor);
\r
298 addPoint(this.geometry.vertices[face.d], pointColor);
\r
302 previousPositions = [];
\r
306 * @param {number} dt
\r
312 for (let i in this.geometry.vertices) {
\r
313 let currentPosition;
\r
314 let acceleration = this.getAcceleration(i, dt);
\r
316 currentPosition = this.verlet(this.geometry.vertices[i], this.previousPositions[i], acceleration, dt/2000);
\r
318 this.previousPositions[i] = currentPosition;
\r
319 this.geometry.vertices[i] = currentPosition;
\r
322 console.log(this.geometry.vertices[0]);
\r
326 * let THREE JS compute bounding sphere around generated mesh
\r
327 * needed for View Frustum Culling internally
\r
330 this.geometry.verticesNeedUpdate = true;
\r
331 this.geometry.elementsNeedUpdate = true;
\r
332 this.geometry.computeBoundingSphere();
\r
339 * Equation of motion for each vertex which represents the acceleration
\r
340 * @param {number} vertexIndex The index of the current vertex whose acceleration should be calculated
\r
341 * @param {number} dt The time passed since last frame
\r
343 getAcceleration(vertexIndex, dt) {
\r
345 let vertex = this.geometry.vertices[vertexIndex];
\r
348 let M = this.vertexWeights[vertexIndex];
\r
349 // constant gravity
\r
350 let g = new THREE.Vector3(0, -1.8, 0);
\r
355 let fWind = new THREE.Vector3(
\r
356 Math.sin(vertex.x * vertex.y * this.time),
\r
357 Math.cos(vertex.z* this.time),
\r
358 Math.sin(Math.cos(5 * vertex.x * vertex.y * vertex.z))
\r
362 * constant determined by the properties of the surrounding fluids (air)
\r
363 * achievement of cloth effects through try out
\r
367 let velocity = new THREE.Vector3(
\r
368 (vertex.x - this.previousPositions[vertexIndex].x) / dt,
\r
369 (vertex.y - this.previousPositions[vertexIndex].y) / dt,
\r
370 (vertex.z - this.previousPositions[vertexIndex].z) / dt
\r
374 let fAirResistance = velocity.multiplyScalar(-a);
\r
376 let springSum = new THREE.Vector3(0, 0, 0);
\r
378 // Get the bounding springs and add them to the needed springs
\r
379 for (let i in this.faces) {
\r
380 if (this.faces[i].a == vertexIndex || this.faces[i].b == vertexIndex || this.faces[i].c == vertexIndex || this.faces[i].d == vertexIndex) {
\r
381 for (let j in this.faces[i].springs) {
\r
382 if (this.faces[i].springs[j].index1 == vertexIndex || this.faces[i].springs[j].index2 == vertexIndex) {
\r
384 let spring = this.faces[i].springs[j];
\r
385 let springDirection = spring.getDirection(this.geometry.vertices);
\r
388 if (this.faces[i].springs[j].index1 == vertexIndex)
\r
389 springDirection.multiplyScalar(-1);
\r
391 springSum.add(springDirection.multiplyScalar(k * (spring.currentLength - spring.restLength)));
\r
399 let result = new THREE.Vector3(1, 1, 1);
\r
402 result.multiplyScalar(M).multiply(g).add(fWind).add(fAirResistance).sub(springSum);
\r
411 * The Verlet algorithm as an integrator
\r
412 * to get the next position of a vertex
\r
413 * @param {Vector3} currentPosition
\r
414 * @param {Vector3} previousPosition
\r
415 * @param {Vector3} acceleration
\r
416 * @param {number} passedTime The delta time since last frame
\r
418 verlet(currentPosition, previousPosition, acceleration, passedTime) {
\r
419 // verlet algorithm
\r
420 // next position = 2 * current Position - previous position + acceleration * (passed time)^2
\r
421 // acceleration (dv/dt) = F(net)
\r
422 // Dependency for one vertex: gravity, fluids/air, springs
\r
424 let nextPosition = new THREE.Vector3(
\r
425 2 * currentPosition.x - previousPosition.x + acceleration.x * (passedTime * passedTime),
\r
426 2 * currentPosition.y - previousPosition.y + acceleration.y * (passedTime * passedTime),
\r
427 2 * currentPosition.z - previousPosition.z + acceleration.z * (passedTime * passedTime),
\r
430 return nextPosition;
\r