abouttreesummaryrefslogcommitdiff
diff options
context:
space:
mode:
authorPatrick Schönberger2021-01-06 10:16:26 +0100
committerPatrick Schönberger2021-01-06 10:16:26 +0100
commitf3d7f6c46c59cd98eb64a079301b872ca2a6a2f3 (patch)
tree4a8463aa1a23652a418cdb4a50b66099a26985ce
parentdcdf429d920b067cf18d50ef9f12032a0cf2dcc2 (diff)
downloadcloth_sim-f3d7f6c46c59cd98eb64a079301b872ca2a6a2f3.tar.gz
cloth_sim-f3d7f6c46c59cd98eb64a079301b872ca2a6a2f3.zip
comment code
-rw-r--r--Scripts/main.js513
1 files changed, 332 insertions, 181 deletions
diff --git a/Scripts/main.js b/Scripts/main.js
index fb7a8b2..4d7ac7c 100644
--- a/Scripts/main.js
+++ b/Scripts/main.js
@@ -1,209 +1,352 @@
-function init() {
- class Point {
- constructor(x, y) {
- this.x = x;
- this.y = y;
- }
-
- add(that) {
- return new Point(
- this.x + that.x,
- this.y + that.y
- );
- }
-
- sub(that) {
- return new Point(
- this.x - that.x,
- this.y - that.y
- );
- }
-
- dist(that) {
- let a = this.x - that.x;
- let b = this.y - that.y;
- return Math.sqrt(a * a + b * b)
- }
+class Point {
+ constructor(x, y) {
+ this.x = x;
+ this.y = y;
}
- 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();
+ add(that) {
+ return new Point(
+ this.x + that.x,
+ this.y + that.y
+ );
}
- class Face {
- a;
- b;
- c;
- d;
-
- springs = [];
-
- constructor(a, b, c, d) {
- this.a = a;
- this.b = b;
- this.c = c;
- this.d = d;
- }
+ sub(that) {
+ return new Point(
+ this.x - that.x,
+ this.y - that.y
+ );
}
- class Spring {
- restLength;
- currentLength;
- index1;
- index2;
-
- constructor(vertices, index1, index2) {
- this.index1 = index1;
- this.index2 = index2;
-
- let length = vectorLength(vertices[index1], vertices[index2]);
- this.restLength = length;
- this.currentLength = length;
- }
+ dist(that) {
+ let a = this.x - that.x;
+ let b = this.y - that.y;
+ return Math.sqrt(a * a + b * b)
}
-
- class Cloth {
- VertexWeight = 1;
-
- geometry = new THREE.Geometry();
-
- faces = [];
-
- vertexWeights = [];
-
- createBasic(width, height, numPointsWidth, numPointsHeight) {
- let vertices = [];
- let faces = [];
-
- let stepWidth = width / (numPointsWidth - 1);
- let stepHeight = height / (numPointsHeight - 1);
-
- 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)
- );
- }
- }
-
- function getVertexIndex(x, y) {
- return y * numPointsWidth + x;
- }
-
- 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);
- }
+}
+
+/**
+ * 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
+ */
+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
+ */
+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
+ */
+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)
+ );
}
-
- this.createExplicit(vertices, faces);
}
- createExplicit(vertices, faces) {
- for (let i in vertices) {
- this.geometry.vertices.push(vertices[i]);
- this.vertexWeights.push(0);
- }
- for (let i in faces) {
- let face = faces[i];
-
- this.faces.push(face);
-
- 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
- ));
+ /**
+ * 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)));
- 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;
-
- this.vertexWeights[face.a] += weight / 4;
- this.vertexWeights[face.b] += weight / 4;
- this.vertexWeights[face.c] += weight / 4;
- this.vertexWeights[face.d] += weight / 4;
+ faces.push(newFace);
}
-
- this.geometry.computeBoundingSphere();
}
- createDebugMesh(scene) {
- 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);
- }
- 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 );
- }
+ /**
+ * call createExplicit
+ * with generated vertices and faces
+ */
+ this.createExplicit(vertices, faces);
+ }
- let lineColor = 0x000000;
- let pointColor = 0xff00000;
-
- 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);
- }
+ /**
+ * 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();
}
- let mousePos = new Point();
+ /**
+ * 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 );
+ }
- const canvasSpace = 200;
+ 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);
+ }
+ }
+}
+/**
+ * setup THREE JS Scene, Camera and Renderer
+ */
+function setup_scene() {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / (window.innerHeight - canvasSpace), 0.1, 1000);
-
const renderer = new THREE.WebGLRenderer();
+ /** size canvas to leave some space for UI */
renderer.setSize(window.innerWidth, window.innerHeight - canvasSpace);
+ /** embed canvas in HTML */
document.getElementById("threejscontainer").appendChild(renderer.domElement);
+ /** add global light */
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
scene.add(directionalLight);
+ /** position camera */
+ camera.position.y = 5;
+ camera.position.z = 10;
+
+ return [scene, camera];
+}
+
+function init() {
+ let mousePos = new Point();
+
+ /**
+ * Space left empty under canvas
+ * for UI elements
+ */
+ const canvasSpace = 200;
+
+ /** Setup scene */
+ let [scene, camera] = setup_scene();
+
+ /** setup cloth and generate debug mesh */
let cloth = new Cloth();
cloth.createBasic(10, 10, 5, 5);
cloth.createDebugMesh(scene);
@@ -212,15 +355,16 @@ function init() {
const mesh = new THREE.Mesh(cloth.geometry, material);
scene.add(mesh);
- camera.position.y = 5;
- camera.position.z = 10;
- //camera.lookAt(new THREE.Vector3(1, 0, 0));
-
+ /**
+ * function called every frame
+ * @param {number} dt - time passed since last frame
+ */
function animate(dt) {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
+ /** add callback for window resize */
let canvas = document.getElementsByTagName("canvas")[0];
let resize = function () {
w = window.innerWidth;
@@ -230,9 +374,16 @@ function init() {
}
window.onresize = resize;
resize();
+
+ /**
+ * if canvas has been successfully initialized
+ * start rendering
+ */
if (canvas.getContext) {
animate(performance.now());
}
+
+ /** add mouse move callback */
canvas.onmousemove = (evt) => {
mousePos.x = evt.clientX;
mousePos.y = evt.clientY;