]> gitweb.ps.run Git - cloth_sim/commitdiff
add cloth.js file
authorPatrick Schönberger <patrick.schoenberger@posteo.de>
Wed, 6 Jan 2021 09:25:08 +0000 (10:25 +0100)
committerPatrick Schönberger <patrick.schoenberger@posteo.de>
Wed, 6 Jan 2021 09:25:08 +0000 (10:25 +0100)
Scripts/cloth.js [new file with mode: 0644]

diff --git a/Scripts/cloth.js b/Scripts/cloth.js
new file mode 100644 (file)
index 0000000..e84facc
--- /dev/null
@@ -0,0 +1,285 @@
+/**\r
+ *  Convenience Function for calculating the distance between two vectors\r
+ *  because THREE JS Vector functions mutate variables\r
+ * @param {Vector3} a - Vector A\r
+ * @param {Vector3} b - Vector B\r
+ */\r
+function vectorLength(a, b) {\r
+  let v1 = new THREE.Vector3();\r
+  v1.set(a.x, a.y, a.z);\r
+  let v2 = new THREE.Vector3();\r
+  v2.set(b.x, b.y, b.z);\r
+\r
+  return v1.sub(v2).length();\r
+}\r
+\r
+/**\r
+ * Class representing a quad face\r
+ * Each face consists of two triangular mesh faces\r
+ * containts four indices for determining vertices\r
+ * and six springs, one between each of the vertices\r
+ */\r
+export class Face {\r
+  a;\r
+  b;\r
+  c;\r
+  d;\r
+\r
+  springs = [];\r
+\r
+  constructor(a, b, c, d) {\r
+    this.a = a;\r
+    this.b = b;\r
+    this.c = c;\r
+    this.d = d;\r
+  }\r
+}\r
+\r
+/**\r
+ * Class representing a single spring\r
+ * has a current and resting length\r
+ * and indices to the two connected vertices\r
+ */\r
+export class Spring {\r
+  restLength;\r
+  currentLength;\r
+  index1;\r
+  index2;\r
+\r
\r
+  /**\r
+   * set vertex indices\r
+   * and calculate inital length based on the\r
+   * vertex positions\r
+   * @param {Array of Vector3} vertices \r
+   * @param {number} index1 \r
+   * @param {number} index2 \r
+   */\r
+  constructor(vertices, index1, index2) {\r
+    this.index1 = index1;\r
+    this.index2 = index2;\r
+\r
+    let length = vectorLength(vertices[index1], vertices[index2]);\r
+    this.restLength = length;\r
+    this.currentLength = length;\r
+  }\r
+}\r
+\r
+/**\r
+ * Class representing a single piece of cloth\r
+ * contains THREE JS geometry,\r
+ * logically represented by an array of adjacent faces\r
+ * and vertex weights which are accessed by the same\r
+ * indices as the vertices in the Mesh\r
+ */\r
+export class Cloth {\r
+  VertexWeight = 1;\r
+\r
+  geometry = new THREE.Geometry();\r
+\r
+  faces = [];\r
+\r
+  vertexWeights = [];\r
+\r
+  \r
+  /**\r
+   * creates a rectangular piece of cloth\r
+   * takes the size of the cloth\r
+   * and the number of vertices it should be composed of\r
+   * @param {number} width - width of the cloth\r
+   * @param {number} height - height of the cloth\r
+   * @param {number} numPointsWidth - number of vertices in horizontal direction\r
+   * @param {number} numPointsHeight  - number of vertices in vertical direction\r
+   */\r
+  createBasic(width, height, numPointsWidth, numPointsHeight) {\r
+    /** resulting vertices and faces */\r
+    let vertices = [];\r
+    let faces = [];\r
+\r
+    /**\r
+     * distance between two vertices horizontally/vertically\r
+     * divide by the number of points minus one\r
+     * because there are (n - 1) lines between n vertices\r
+     */\r
+    let stepWidth = width / (numPointsWidth - 1);\r
+    let stepHeight = height / (numPointsHeight - 1);\r
+\r
+    /**\r
+     * iterate over the number of vertices in x/y axis\r
+     * and add a new Vector3 to "vertices"\r
+     */\r
+    for (let y = 0; y < numPointsHeight; y++) {\r
+      for (let x = 0; x < numPointsWidth; x++) {\r
+        vertices.push(\r
+          new THREE.Vector3(x * stepWidth, height - y * stepHeight, 0)\r
+        );\r
+      }\r
+    }\r
+\r
+    /**\r
+     * helper function to calculate index of vertex\r
+     * in "vertices" array based on its x and y positions\r
+     * in the mesh\r
+     * @param {number} x - x index of vertex\r
+     * @param {number} y - y index of vertex\r
+     */\r
+    function getVertexIndex(x, y) {\r
+      return y * numPointsWidth + x;\r
+    }\r
+    \r
+    /**\r
+     * generate faces based on 4 vertices\r
+     * and 6 springs each\r
+     */\r
+    for (let y = 0; y < numPointsHeight - 1; y++) {\r
+      for (let x = 0; x < numPointsWidth - 1; x++) {\r
+        let newFace = new Face(\r
+          getVertexIndex(x, y),\r
+          getVertexIndex(x, y + 1),\r
+          getVertexIndex(x + 1, y),\r
+          getVertexIndex(x + 1, y + 1),\r
+        );\r
+\r
+        newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x + 1, y)));\r
+        newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x, y + 1)));\r
+        newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x + 1, y + 1)));\r
+        newFace.springs.push(new Spring(vertices, getVertexIndex(x + 1, y), getVertexIndex(x, y + 1)));\r
+        newFace.springs.push(new Spring(vertices, getVertexIndex(x + 1, y), getVertexIndex(x + 1, y + 1)));\r
+        newFace.springs.push(new Spring(vertices, getVertexIndex(x, y + 1), getVertexIndex(x + 1, y + 1)));\r
+        \r
+        faces.push(newFace);\r
+      }\r
+    }\r
+\r
+    /**\r
+     * call createExplicit\r
+     * with generated vertices and faces\r
+     */\r
+    this.createExplicit(vertices, faces);\r
+  }\r
+\r
+  /**\r
+   * Generate THREE JS Geometry\r
+   * (list of vertices and list of indices representing triangles)\r
+   * and calculate the weight of each face and split it between\r
+   * surrounding vertices\r
+   * @param {Array of Vector3} vertices \r
+   * @param {Array of Face} faces \r
+   */\r
+  createExplicit(vertices, faces) {\r
+    /**\r
+     * Copy vertices and initialize vertex weights to 0\r
+     */\r
+    for (let i in vertices) {\r
+      this.geometry.vertices.push(vertices[i]);\r
+      this.vertexWeights.push(0);\r
+    }\r
+    /**\r
+     * copy faces,\r
+     * generate two triangles per face,\r
+     * calculate weight of face as its area\r
+     * and split between the 4 vertices\r
+     */\r
+    for (let i in faces) {\r
+      let face = faces[i];\r
+\r
+      /** copy faces to class member */\r
+      this.faces.push(face);\r
+\r
+      /** generate triangles */\r
+      this.geometry.faces.push(new THREE.Face3(\r
+        face.a, face.b, face.c\r
+      ));\r
+      this.geometry.faces.push(new THREE.Face3(\r
+        face.c, face.b, face.d\r
+      ));\r
+      \r
+      /**\r
+       * calculate area of face as combined area of\r
+       * its two composing triangles\r
+       */\r
+      let xLength = vectorLength(this.geometry.vertices[face.b], this.geometry.vertices[face.a]);\r
+      let yLength = vectorLength(this.geometry.vertices[face.c], this.geometry.vertices[face.a]);\r
+      let weight = xLength * yLength / 2;\r
+\r
+      xLength = vectorLength(this.geometry.vertices[face.b], this.geometry.vertices[face.d]);\r
+      yLength = vectorLength(this.geometry.vertices[face.c], this.geometry.vertices[face.d]);\r
+      weight += xLength * yLength / 2;\r
+\r
+      /**\r
+       * split weight equally between four surrounding vertices\r
+       */\r
+      this.vertexWeights[face.a] += weight / 4;\r
+      this.vertexWeights[face.b] += weight / 4;\r
+      this.vertexWeights[face.c] += weight / 4;\r
+      this.vertexWeights[face.d] += weight / 4;\r
+    }\r
+\r
+    /**\r
+     * let THREE JS compute bounding sphere around generated mesh\r
+     * needed for View Frustum Culling internally\r
+     */\r
+    this.geometry.computeBoundingSphere();\r
+  }\r
+\r
+  /**\r
+   * generate a debug mesh for visualizing\r
+   * vertices and springs of the cloth\r
+   * and add it to scene for rendering\r
+   * @param {Scene} scene - Scene to add Debug Mesh to\r
+   */\r
+  createDebugMesh(scene) {\r
+    /**\r
+     * helper function to generate a single line\r
+     * between two Vertices with a given color\r
+     * @param {Vector3} from \r
+     * @param {Vector3} to \r
+     * @param {number} color \r
+     */\r
+    function addLine(from, to, color) {\r
+      let geometry = new THREE.Geometry();\r
+      geometry.vertices.push(from);\r
+      geometry.vertices.push(to);\r
+      let material = new THREE.LineBasicMaterial( { color: color, linewidth: 10 } );\r
+      let line = new THREE.Line(geometry, material);\r
+      line.renderOrder = 1;\r
+      scene.add(line);\r
+    }\r
+    /**\r
+     * helper function to generate a small sphere\r
+     * at a given Vertex Position with color\r
+     * @param {Vector3} point \r
+     * @param {number} color \r
+     */\r
+    function addPoint(point, color) {\r
+      const geometry = new THREE.SphereGeometry( 0.05, 32, 32 );\r
+      const material = new THREE.MeshBasicMaterial( { color: color } );\r
+      const sphere = new THREE.Mesh( geometry, material );\r
+      sphere.position.set(point.x, point.y, point.z);\r
+      scene.add( sphere );\r
+    }\r
+\r
+    let lineColor = 0x000000;\r
+    let pointColor = 0xff00000;\r
+\r
+    /**\r
+     * generate one line for each of the 6 springs\r
+     * and one point for each of the 4 vertices\r
+     * for all of the faces\r
+     */\r
+    for (let i in this.faces) {\r
+      let face = this.faces[i];\r
+      addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.b], lineColor);\r
+      addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.c], lineColor);\r
+      addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.d], lineColor);\r
+      addLine(this.geometry.vertices[face.b], this.geometry.vertices[face.c], lineColor);\r
+      addLine(this.geometry.vertices[face.b], this.geometry.vertices[face.d], lineColor);\r
+      addLine(this.geometry.vertices[face.c], this.geometry.vertices[face.d], lineColor);\r
+\r
+      addPoint(this.geometry.vertices[face.a], pointColor);\r
+      addPoint(this.geometry.vertices[face.b], pointColor);\r
+      addPoint(this.geometry.vertices[face.c], pointColor);\r
+      addPoint(this.geometry.vertices[face.d], pointColor);\r
+    }\r
+  }\r
+}
\ No newline at end of file