abouttreesummaryrefslogcommitdiff
path: root/Scripts
diff options
context:
space:
mode:
authorPatrick Schönberger2021-01-06 10:25:08 +0100
committerPatrick Schönberger2021-01-06 10:25:08 +0100
commitfcfb3d929c2fefdfdcb6cb9351bcdd0d2d14f9f2 (patch)
treee67bbb551719308f1599b910e0d0a962ec265319 /Scripts
parent4079007a1ed290280e228b93432129c4a262dae9 (diff)
downloadcloth_sim-fcfb3d929c2fefdfdcb6cb9351bcdd0d2d14f9f2.tar.gz
cloth_sim-fcfb3d929c2fefdfdcb6cb9351bcdd0d2d14f9f2.zip
add cloth.js file
Diffstat (limited to 'Scripts')
-rw-r--r--Scripts/cloth.js285
1 files changed, 285 insertions, 0 deletions
diff --git a/Scripts/cloth.js b/Scripts/cloth.js
new file mode 100644
index 0000000..e84facc
--- /dev/null
+++ b/Scripts/cloth.js
@@ -0,0 +1,285 @@
+/**
+ * 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.set(a.x, a.y, a.z);
+ let v2 = new THREE.Vector3();
+ v2.set(b.x, b.y, b.z);
+
+ 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 of Vector3} 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;
+ }
+}
+
+/**
+ * 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 = [];
+
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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 of Vector3} vertices
+ * @param {Array of Face} faces
+ */
+ createExplicit(vertices, faces) {
+ /**
+ * Copy vertices and initialize vertex weights to 0
+ */
+ for (let i in vertices) {
+ this.geometry.vertices.push(vertices[i]);
+ this.vertexWeights.push(0);
+ }
+ /**
+ * 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);
+ }
+ }
+} \ No newline at end of file