]> gitweb.ps.run Git - cloth_sim/commitdiff
comment code
authorPatrick Schönberger <patrick.schoenberger@posteo.de>
Wed, 6 Jan 2021 09:16:26 +0000 (10:16 +0100)
committerPatrick Schönberger <patrick.schoenberger@posteo.de>
Wed, 6 Jan 2021 09:16:26 +0000 (10:16 +0100)
Scripts/main.js

index fb7a8b21c9dd444f48bb1809df003d524a85561f..4d7ac7c566b6e480450b3f2f1f91d77fdec9b0ef 100644 (file)
-function init() {\r
-  class Point {\r
-    constructor(x, y) {\r
-      this.x = x;\r
-      this.y = y;\r
-    }\r
-\r
-    add(that) {\r
-      return new Point(\r
-        this.x + that.x,\r
-        this.y + that.y\r
-      );\r
-    }\r
-\r
-    sub(that) {\r
-      return new Point(\r
-        this.x - that.x,\r
-        this.y - that.y\r
-      );\r
-    }\r
-\r
-    dist(that) {\r
-      let a = this.x - that.x;\r
-      let b = this.y - that.y;\r
-      return Math.sqrt(a * a + b * b)\r
-    }\r
+class Point {\r
+  constructor(x, y) {\r
+    this.x = x;\r
+    this.y = y;\r
   }\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
+  add(that) {\r
+    return new Point(\r
+      this.x + that.x,\r
+      this.y + that.y\r
+    );\r
   }\r
 \r
-  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
+  sub(that) {\r
+    return new Point(\r
+      this.x - that.x,\r
+      this.y - that.y\r
+    );\r
   }\r
 \r
-  class Spring {\r
-    restLength;\r
-    currentLength;\r
-    index1;\r
-    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
+  dist(that) {\r
+    let a = this.x - that.x;\r
+    let b = this.y - that.y;\r
+    return Math.sqrt(a * a + b * b)\r
   }\r
-\r
-  class Cloth {\r
-    VertexWeight = 1;\r
-\r
-    geometry = new THREE.Geometry();\r
-\r
-    faces = [];\r
-\r
-    vertexWeights = [];\r
-\r
-    createBasic(width, height, numPointsWidth, numPointsHeight) {\r
-      let vertices = [];\r
-      let faces = [];\r
-\r
-      let stepWidth = width / (numPointsWidth - 1);\r
-      let stepHeight = height / (numPointsHeight - 1);\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
-      function getVertexIndex(x, y) {\r
-        return y * numPointsWidth + x;\r
-      }\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
+ *  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
+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
+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
+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
-      this.createExplicit(vertices, faces);\r
     }\r
 \r
-    createExplicit(vertices, faces) {\r
-      for (let i in vertices) {\r
-        this.geometry.vertices.push(vertices[i]);\r
-        this.vertexWeights.push(0);\r
-      }\r
-      for (let i in faces) {\r
-        let face = faces[i];\r
-\r
-        this.faces.push(face);\r
-\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
+     * 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
-        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
-        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
+        faces.push(newFace);\r
       }\r
-\r
-      this.geometry.computeBoundingSphere();\r
     }\r
 \r
-    createDebugMesh(scene) {\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
-      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
+     * call createExplicit\r
+     * with generated vertices and faces\r
+     */\r
+    this.createExplicit(vertices, faces);\r
+  }\r
 \r
-      let lineColor = 0x000000;\r
-      let pointColor = 0xff00000;\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
+   * 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
-  let mousePos = new Point();\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
-  const canvasSpace = 200;\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
+}\r
 \r
+/**\r
+ * setup THREE JS Scene, Camera and Renderer\r
+ */\r
+function setup_scene() {\r
   const scene = new THREE.Scene();\r
   const camera = new THREE.PerspectiveCamera(75, window.innerWidth / (window.innerHeight - canvasSpace), 0.1, 1000);\r
-\r
   const renderer = new THREE.WebGLRenderer();\r
+  /** size canvas to leave some space for UI */\r
   renderer.setSize(window.innerWidth, window.innerHeight - canvasSpace);\r
+  /** embed canvas in HTML */\r
   document.getElementById("threejscontainer").appendChild(renderer.domElement);\r
 \r
+  /** add global light */\r
   const directionalLight = new THREE.DirectionalLight(0xffffff, 1);\r
   scene.add(directionalLight);\r
 \r
+  /** position camera */\r
+  camera.position.y = 5;\r
+  camera.position.z = 10;\r
+\r
+  return [scene, camera];\r
+}\r
+\r
+function init() {\r
+  let mousePos = new Point();\r
+\r
+  /**\r
+   * Space left empty under canvas\r
+   * for UI elements\r
+   */\r
+  const canvasSpace = 200;\r
+\r
+  /** Setup scene */\r
+  let [scene, camera] = setup_scene();\r
+  \r
+  /** setup cloth and generate debug mesh */\r
   let cloth = new Cloth();\r
   cloth.createBasic(10, 10, 5, 5);\r
   cloth.createDebugMesh(scene);\r
@@ -212,15 +355,16 @@ function init() {
   const mesh = new THREE.Mesh(cloth.geometry, material);\r
   scene.add(mesh);\r
 \r
-  camera.position.y = 5;\r
-  camera.position.z = 10;\r
-  //camera.lookAt(new THREE.Vector3(1, 0, 0));\r
-\r
+  /**\r
+   * function called every frame\r
+   * @param {number} dt - time passed since last frame\r
+   */\r
   function animate(dt) {\r
     requestAnimationFrame(animate);\r
     renderer.render(scene, camera);\r
   }\r
 \r
+  /** add callback for window resize */\r
   let canvas = document.getElementsByTagName("canvas")[0];\r
   let resize = function () {\r
     w = window.innerWidth;\r
@@ -230,9 +374,16 @@ function init() {
   }\r
   window.onresize = resize;\r
   resize();\r
+\r
+  /**\r
+   * if canvas has been successfully initialized\r
+   * start rendering\r
+   */\r
   if (canvas.getContext) {\r
     animate(performance.now());\r
   }\r
+\r
+  /** add mouse move callback */\r
   canvas.onmousemove = (evt) => {\r
     mousePos.x = evt.clientX;\r
     mousePos.y = evt.clientY;\r