X-Git-Url: https://gitweb.ps.run/cloth_sim/blobdiff_plain/14881afe547086f68764e27bd65c794b21271c63..b9cfb0c7bf25e9ebc0c21e0b32665113ea46f7b5:/Scripts/cloth.js diff --git a/Scripts/cloth.js b/Scripts/cloth.js index 1497583..f1068d4 100644 --- a/Scripts/cloth.js +++ b/Scripts/cloth.js @@ -1,463 +1,4 @@ -/** - * Convenience Function for calculating the distance between two vectors - * because THREE JS Vector functions mutate variables - * @param {Vector3} a - Vector A - * @param {Vector3} b - Vector B - */ -function vectorLength(a, b) { - let v1 = new THREE.Vector3(); - v1.copy(a); - let v2 = new THREE.Vector3(); - v2.copy(b); - - return v1.sub(v2).length(); -} - -/** - * Class representing a quad face - * Each face consists of two triangular mesh faces - * containts four indices for determining vertices - * and six springs, one between each of the vertices - */ -export class Face { - a; - b; - c; - d; - - springs = []; - - constructor(a, b, c, d) { - this.a = a; - this.b = b; - this.c = c; - this.d = d; - } -} - -/** - * Class representing a single spring - * has a current and resting length - * and indices to the two connected vertices - */ -export class Spring { - restLength; - currentLength; - index1; - index2; - - - /** - * set vertex indices - * and calculate inital length based on the - * vertex positions - * @param {Array} vertices - * @param {number} index1 - * @param {number} index2 - */ - constructor(vertices, index1, index2) { - this.index1 = index1; - this.index2 = index2; - - let length = vectorLength(vertices[index1], vertices[index2]); - this.restLength = length; - this.currentLength = length; - } - - getDirection(vertices) { - let direction = new THREE.Vector3(); - direction.copy(vertices[this.index1]); - - direction.sub(vertices[this.index2]); - direction.divideScalar(vectorLength(vertices[this.index1], vertices[this.index2])); - - return direction; - } - - update(vertices) { - let length = vectorLength(vertices[this.index1], vertices[this.index2]); - this.currentLength = length; - } -} - -/** - * Class representing a single piece of cloth - * contains THREE JS geometry, - * logically represented by an array of adjacent faces - * and vertex weights which are accessed by the same - * indices as the vertices in the Mesh - */ -export class Cloth { - VertexWeight = 1; - - geometry = new THREE.Geometry(); - - faces = []; - - vertexWeights = []; - - vertexRigidness = []; - - /** - * creates a rectangular piece of cloth - * takes the size of the cloth - * and the number of vertices it should be composed of - * @param {number} width - width of the cloth - * @param {number} height - height of the cloth - * @param {number} numPointsWidth - number of vertices in horizontal direction - * @param {number} numPointsHeight - number of vertices in vertical direction - */ - createBasic(width, height, numPointsWidth, numPointsHeight) { - /** resulting vertices and faces */ - let vertices = []; - let faces = []; - - /** - * distance between two vertices horizontally/vertically - * divide by the number of points minus one - * because there are (n - 1) lines between n vertices - */ - let stepWidth = width / (numPointsWidth - 1); - let stepHeight = height / (numPointsHeight - 1); - - /** - * iterate over the number of vertices in x/y axis - * and add a new Vector3 to "vertices" - */ - for (let y = 0; y < numPointsHeight; y++) { - for (let x = 0; x < numPointsWidth; x++) { - vertices.push( - new THREE.Vector3(x * stepWidth, height - y * stepHeight, 0) - ); - } - } - - /** - * helper function to calculate index of vertex - * in "vertices" array based on its x and y positions - * in the mesh - * @param {number} x - x index of vertex - * @param {number} y - y index of vertex - */ - function getVertexIndex(x, y) { - return y * numPointsWidth + x; - } - - /** - * generate faces based on 4 vertices - * and 6 springs each - */ - for (let y = 0; y < numPointsHeight - 1; y++) { - for (let x = 0; x < numPointsWidth - 1; x++) { - let newFace = new Face( - getVertexIndex(x, y), - getVertexIndex(x, y + 1), - getVertexIndex(x + 1, y), - getVertexIndex(x + 1, y + 1), - ); - - newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x + 1, y))); - newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x, y + 1))); - newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x + 1, y + 1))); - newFace.springs.push(new Spring(vertices, getVertexIndex(x + 1, y), getVertexIndex(x, y + 1))); - newFace.springs.push(new Spring(vertices, getVertexIndex(x + 1, y), getVertexIndex(x + 1, y + 1))); - newFace.springs.push(new Spring(vertices, getVertexIndex(x, y + 1), getVertexIndex(x + 1, y + 1))); - - faces.push(newFace); - } - } - - /** - * call createExplicit - * with generated vertices and faces - */ - this.createExplicit(vertices, faces); - - /** - * hand cloth from left and right upper corners - */ - this.vertexRigidness[0] = true; - this.vertexRigidness[numPointsWidth-1] = true; - } - - /** - * Generate THREE JS Geometry - * (list of vertices and list of indices representing triangles) - * and calculate the weight of each face and split it between - * surrounding vertices - * @param {Array} vertices - * @param {Array} faces - */ - createExplicit(vertices, faces) { - - /** - * Copy vertices and initialize vertex weights to 0 - */ - for (let i in vertices) { - this.geometry.vertices.push(vertices[i].clone()); - this.previousPositions.push(vertices[i].clone()); - // this.geometry.vertices.push(vertices[i]); - // this.previousPositions.push(vertices[i]); - this.vertexWeights.push(0); - this.vertexRigidness.push(false); - } - /** - * copy faces, - * generate two triangles per face, - * calculate weight of face as its area - * and split between the 4 vertices - */ - for (let i in faces) { - let face = faces[i]; - - /** copy faces to class member */ - this.faces.push(face); - - /** generate triangles */ - this.geometry.faces.push(new THREE.Face3( - face.a, face.b, face.c - )); - this.geometry.faces.push(new THREE.Face3( - face.c, face.b, face.d - )); - - /** - * calculate area of face as combined area of - * its two composing triangles - */ - let xLength = vectorLength(this.geometry.vertices[face.b], this.geometry.vertices[face.a]); - let yLength = vectorLength(this.geometry.vertices[face.c], this.geometry.vertices[face.a]); - let weight = xLength * yLength / 2; - - xLength = vectorLength(this.geometry.vertices[face.b], this.geometry.vertices[face.d]); - yLength = vectorLength(this.geometry.vertices[face.c], this.geometry.vertices[face.d]); - weight += xLength * yLength / 2; - - /** - * split weight equally between four surrounding vertices - */ - this.vertexWeights[face.a] += weight / 4; - this.vertexWeights[face.b] += weight / 4; - this.vertexWeights[face.c] += weight / 4; - this.vertexWeights[face.d] += weight / 4; - } - - /** - * let THREE JS compute bounding sphere around generated mesh - * needed for View Frustum Culling internally - */ - this.geometry.computeBoundingSphere(); - } - - /** - * generate a debug mesh for visualizing - * vertices and springs of the cloth - * and add it to scene for rendering - * @param {Scene} scene - Scene to add Debug Mesh to - */ - createDebugMesh(scene) { - /** - * helper function to generate a single line - * between two Vertices with a given color - * @param {Vector3} from - * @param {Vector3} to - * @param {number} color - */ - function addLine(from, to, color) { - let geometry = new THREE.Geometry(); - geometry.vertices.push(from); - geometry.vertices.push(to); - let material = new THREE.LineBasicMaterial({ color: color, linewidth: 10 }); - let line = new THREE.Line(geometry, material); - line.renderOrder = 1; - scene.add(line); - } - /** - * helper function to generate a small sphere - * at a given Vertex Position with color - * @param {Vector3} point - * @param {number} color - */ - function addPoint(point, color) { - const geometry = new THREE.SphereGeometry(0.05, 32, 32); - const material = new THREE.MeshBasicMaterial({ color: color }); - const sphere = new THREE.Mesh(geometry, material); - sphere.position.set(point.x, point.y, point.z); - scene.add(sphere); - } - - let lineColor = 0x000000; - let pointColor = 0xff00000; - - /** - * generate one line for each of the 6 springs - * and one point for each of the 4 vertices - * for all of the faces - */ - for (let i in this.faces) { - let face = this.faces[i]; - addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.b], lineColor); - addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.c], lineColor); - addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.d], lineColor); - addLine(this.geometry.vertices[face.b], this.geometry.vertices[face.c], lineColor); - addLine(this.geometry.vertices[face.b], this.geometry.vertices[face.d], lineColor); - addLine(this.geometry.vertices[face.c], this.geometry.vertices[face.d], lineColor); - - addPoint(this.geometry.vertices[face.a], pointColor); - addPoint(this.geometry.vertices[face.b], pointColor); - addPoint(this.geometry.vertices[face.c], pointColor); - addPoint(this.geometry.vertices[face.d], pointColor); - } - } - - previousPositions = []; - time = 0; - /** - * - * @param {number} dt time in seconds since last frame - */ - simulate(dt) { - for (let i in this.geometry.vertices) { - let acceleration = this.getAcceleration(i, dt); - - //acceleration.clampLength(0, 10); - - if (Math.abs(acceleration.length()) <= 10e-4) { - acceleration.set(0, 0, 0); - } - - let currentPosition = this.verlet(this.geometry.vertices[i].clone(), this.previousPositions[i].clone(), acceleration, dt); - //let currentPosition = this.euler(this.geometry.vertices[i], acceleration, dt); - - this.previousPositions[i].copy(this.geometry.vertices[i]); - this.geometry.vertices[i].copy(currentPosition); - } - //console.log(this.getAcceleration(1, dt)); - - this.time += dt; - - for (let face of this.faces) { - for (let spring of face.springs) { - spring.update(this.geometry.vertices); - } - } - - /** - * let THREE JS compute bounding sphere around generated mesh - * needed for View Frustum Culling internally - */ - - this.geometry.verticesNeedUpdate = true; - this.geometry.elementsNeedUpdate = true; - this.geometry.computeBoundingSphere(); - - } - - - -/** - * Equation of motion for each vertex which represents the acceleration - * @param {number} vertexIndex The index of the current vertex whose acceleration should be calculated - * @param {number} dt The time passed since last frame - */ -getAcceleration(vertexIndex, dt) { - if (this.vertexRigidness[vertexIndex]) - return new THREE.Vector3(0, 0, 0); - - let vertex = this.geometry.vertices[vertexIndex]; - - // Mass of vertex - let M = this.vertexWeights[vertexIndex]; - // constant gravity - let g = new THREE.Vector3(0, -9.8, 0); - // stiffness - let k = 1000; - - // Wind vector - let fWind = new THREE.Vector3( - Math.sin(vertex.x * vertex.y * this.time), - Math.cos(vertex.z * this.time), - Math.sin(Math.cos(5 * vertex.x * vertex.y * vertex.z)) - ); - fWind.set(0, 0, 0); - - /** - * constant determined by the properties of the surrounding fluids (air) - * achievement of cloth effects through try out - * */ - let a = 0.01; - - let velocity = new THREE.Vector3( - (this.previousPositions[vertexIndex].x - vertex.x) / dt, - (this.previousPositions[vertexIndex].y - vertex.y) / dt, - (this.previousPositions[vertexIndex].z - vertex.z) / dt - ); - - //console.log(velocity, vertex, this.previousPositions[vertexIndex]); - - let fAirResistance = velocity.cross(velocity).multiplyScalar(-a); - - let springSum = new THREE.Vector3(0, 0, 0); - - // Get the bounding springs and add them to the needed springs - // TODO: optimize - for (let i in this.faces) { - if (this.faces[i].a == vertexIndex || this.faces[i].b == vertexIndex || this.faces[i].c == vertexIndex || this.faces[i].d == vertexIndex) { - for (let j in this.faces[i].springs) { - if (this.faces[i].springs[j].index1 == vertexIndex || this.faces[i].springs[j].index2 == vertexIndex) { - - let spring = this.faces[i].springs[j]; - let springDirection = spring.getDirection(this.geometry.vertices); - - - if (this.faces[i].springs[j].index1 == vertexIndex) - springDirection.multiplyScalar(-1); - - springSum.add(springDirection.multiplyScalar(k * (spring.restLength - spring.currentLength))); - } - } - } - } - - let result = new THREE.Vector3(1, 1, 1); - - result.multiplyScalar(M).multiply(g).add(fWind).add(fAirResistance).sub(springSum); - - return result; -} - -/** - * The Verlet algorithm as an integrator - * to get the next position of a vertex - * @param {Vector3} currentPosition - * @param {Vector3} previousPosition - * @param {Vector3} acceleration - * @param {number} passedTime The delta time since last frame - */ -verlet(currentPosition, previousPosition, acceleration, passedTime) { - // verlet algorithm - // next position = 2 * current Position - previous position + acceleration * (passed time)^2 - // acceleration (dv/dt) = F(net) - // Dependency for one vertex: gravity, fluids/air, springs - - let nextPosition = new THREE.Vector3( - (2 * currentPosition.x) - previousPosition.x + acceleration.x * (passedTime * passedTime), - (2 * currentPosition.y) - previousPosition.y + acceleration.y * (passedTime * passedTime), - (2 * currentPosition.z) - previousPosition.z + acceleration.z * (passedTime * passedTime), - ); - - return nextPosition; -} - -euler(currentPosition, acceleration, passedTime) { - let nextPosition = new THREE.Vector3( - currentPosition.x + acceleration.x * passedTime, - currentPosition.y + acceleration.y * passedTime, - currentPosition.z + acceleration.z * passedTime, - ); - - return nextPosition; -} - -} - +// cloth rendering +// simulate +// setup scene +// orbit controls/skybox/flagge \ No newline at end of file