22 let a = this.x - that.x;
\r
23 let b = this.y - that.y;
\r
24 return Math.sqrt(a * a + b * b)
\r
29 * Convenience Function for calculating the distance between two vectors
\r
30 * because THREE JS Vector functions mutate variables
\r
31 * @param {Vector3} a - Vector A
\r
32 * @param {Vector3} b - Vector B
\r
34 function vectorLength(a, b) {
\r
35 let v1 = new THREE.Vector3();
\r
36 v1.set(a.x, a.y, a.z);
\r
37 let v2 = new THREE.Vector3();
\r
38 v2.set(b.x, b.y, b.z);
\r
40 return v1.sub(v2).length();
\r
44 * Class representing a quad face
\r
45 * Each face consists of two triangular mesh faces
\r
46 * containts four indices for determining vertices
\r
47 * and six springs, one between each of the vertices
\r
57 constructor(a, b, c, d) {
\r
66 * Class representing a single spring
\r
67 * has a current and resting length
\r
68 * and indices to the two connected vertices
\r
78 * set vertex indices
\r
79 * and calculate inital length based on the
\r
81 * @param {Array of Vector3} vertices
\r
82 * @param {number} index1
\r
83 * @param {number} index2
\r
85 constructor(vertices, index1, index2) {
\r
86 this.index1 = index1;
\r
87 this.index2 = index2;
\r
89 let length = vectorLength(vertices[index1], vertices[index2]);
\r
90 this.restLength = length;
\r
91 this.currentLength = length;
\r
96 * Class representing a single piece of cloth
\r
97 * contains THREE JS geometry,
\r
98 * logically represented by an array of adjacent faces
\r
99 * and vertex weights which are accessed by the same
\r
100 * indices as the vertices in the Mesh
\r
105 geometry = new THREE.Geometry();
\r
109 vertexWeights = [];
\r
113 * creates a rectangular piece of cloth
\r
114 * takes the size of the cloth
\r
115 * and the number of vertices it should be composed of
\r
116 * @param {number} width - width of the cloth
\r
117 * @param {number} height - height of the cloth
\r
118 * @param {number} numPointsWidth - number of vertices in horizontal direction
\r
119 * @param {number} numPointsHeight - number of vertices in vertical direction
\r
121 createBasic(width, height, numPointsWidth, numPointsHeight) {
\r
122 /** resulting vertices and faces */
\r
127 * distance between two vertices horizontally/vertically
\r
128 * divide by the number of points minus one
\r
129 * because there are (n - 1) lines between n vertices
\r
131 let stepWidth = width / (numPointsWidth - 1);
\r
132 let stepHeight = height / (numPointsHeight - 1);
\r
135 * iterate over the number of vertices in x/y axis
\r
136 * and add a new Vector3 to "vertices"
\r
138 for (let y = 0; y < numPointsHeight; y++) {
\r
139 for (let x = 0; x < numPointsWidth; x++) {
\r
141 new THREE.Vector3(x * stepWidth, height - y * stepHeight, 0)
\r
147 * helper function to calculate index of vertex
\r
148 * in "vertices" array based on its x and y positions
\r
150 * @param {number} x - x index of vertex
\r
151 * @param {number} y - y index of vertex
\r
153 function getVertexIndex(x, y) {
\r
154 return y * numPointsWidth + x;
\r
158 * generate faces based on 4 vertices
\r
159 * and 6 springs each
\r
161 for (let y = 0; y < numPointsHeight - 1; y++) {
\r
162 for (let x = 0; x < numPointsWidth - 1; x++) {
\r
163 let newFace = new Face(
\r
164 getVertexIndex(x, y),
\r
165 getVertexIndex(x, y + 1),
\r
166 getVertexIndex(x + 1, y),
\r
167 getVertexIndex(x + 1, y + 1),
\r
170 newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x + 1, y)));
\r
171 newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x, y + 1)));
\r
172 newFace.springs.push(new Spring(vertices, getVertexIndex(x, y), getVertexIndex(x + 1, y + 1)));
\r
173 newFace.springs.push(new Spring(vertices, getVertexIndex(x + 1, y), getVertexIndex(x, y + 1)));
\r
174 newFace.springs.push(new Spring(vertices, getVertexIndex(x + 1, y), getVertexIndex(x + 1, y + 1)));
\r
175 newFace.springs.push(new Spring(vertices, getVertexIndex(x, y + 1), getVertexIndex(x + 1, y + 1)));
\r
177 faces.push(newFace);
\r
182 * call createExplicit
\r
183 * with generated vertices and faces
\r
185 this.createExplicit(vertices, faces);
\r
189 * Generate THREE JS Geometry
\r
190 * (list of vertices and list of indices representing triangles)
\r
191 * and calculate the weight of each face and split it between
\r
192 * surrounding vertices
\r
193 * @param {Array of Vector3} vertices
\r
194 * @param {Array of Face} faces
\r
196 createExplicit(vertices, faces) {
\r
198 * Copy vertices and initialize vertex weights to 0
\r
200 for (let i in vertices) {
\r
201 this.geometry.vertices.push(vertices[i]);
\r
202 this.vertexWeights.push(0);
\r
206 * generate two triangles per face,
\r
207 * calculate weight of face as its area
\r
208 * and split between the 4 vertices
\r
210 for (let i in faces) {
\r
211 let face = faces[i];
\r
213 /** copy faces to class member */
\r
214 this.faces.push(face);
\r
216 /** generate triangles */
\r
217 this.geometry.faces.push(new THREE.Face3(
\r
218 face.a, face.b, face.c
\r
220 this.geometry.faces.push(new THREE.Face3(
\r
221 face.c, face.b, face.d
\r
225 * calculate area of face as combined area of
\r
226 * its two composing triangles
\r
228 let xLength = vectorLength(this.geometry.vertices[face.b], this.geometry.vertices[face.a]);
\r
229 let yLength = vectorLength(this.geometry.vertices[face.c], this.geometry.vertices[face.a]);
\r
230 let weight = xLength * yLength / 2;
\r
232 xLength = vectorLength(this.geometry.vertices[face.b], this.geometry.vertices[face.d]);
\r
233 yLength = vectorLength(this.geometry.vertices[face.c], this.geometry.vertices[face.d]);
\r
234 weight += xLength * yLength / 2;
\r
237 * split weight equally between four surrounding vertices
\r
239 this.vertexWeights[face.a] += weight / 4;
\r
240 this.vertexWeights[face.b] += weight / 4;
\r
241 this.vertexWeights[face.c] += weight / 4;
\r
242 this.vertexWeights[face.d] += weight / 4;
\r
246 * let THREE JS compute bounding sphere around generated mesh
\r
247 * needed for View Frustum Culling internally
\r
249 this.geometry.computeBoundingSphere();
\r
253 * generate a debug mesh for visualizing
\r
254 * vertices and springs of the cloth
\r
255 * and add it to scene for rendering
\r
256 * @param {Scene} scene - Scene to add Debug Mesh to
\r
258 createDebugMesh(scene) {
\r
260 * helper function to generate a single line
\r
261 * between two Vertices with a given color
\r
262 * @param {Vector3} from
\r
263 * @param {Vector3} to
\r
264 * @param {number} color
\r
266 function addLine(from, to, color) {
\r
267 let geometry = new THREE.Geometry();
\r
268 geometry.vertices.push(from);
\r
269 geometry.vertices.push(to);
\r
270 let material = new THREE.LineBasicMaterial( { color: color, linewidth: 10 } );
\r
271 let line = new THREE.Line(geometry, material);
\r
272 line.renderOrder = 1;
\r
276 * helper function to generate a small sphere
\r
277 * at a given Vertex Position with color
\r
278 * @param {Vector3} point
\r
279 * @param {number} color
\r
281 function addPoint(point, color) {
\r
282 const geometry = new THREE.SphereGeometry( 0.05, 32, 32 );
\r
283 const material = new THREE.MeshBasicMaterial( { color: color } );
\r
284 const sphere = new THREE.Mesh( geometry, material );
\r
285 sphere.position.set(point.x, point.y, point.z);
\r
286 scene.add( sphere );
\r
289 let lineColor = 0x000000;
\r
290 let pointColor = 0xff00000;
\r
293 * generate one line for each of the 6 springs
\r
294 * and one point for each of the 4 vertices
\r
295 * for all of the faces
\r
297 for (let i in this.faces) {
\r
298 let face = this.faces[i];
\r
299 addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.b], lineColor);
\r
300 addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.c], lineColor);
\r
301 addLine(this.geometry.vertices[face.a], this.geometry.vertices[face.d], lineColor);
\r
302 addLine(this.geometry.vertices[face.b], this.geometry.vertices[face.c], lineColor);
\r
303 addLine(this.geometry.vertices[face.b], this.geometry.vertices[face.d], lineColor);
\r
304 addLine(this.geometry.vertices[face.c], this.geometry.vertices[face.d], lineColor);
\r
306 addPoint(this.geometry.vertices[face.a], pointColor);
\r
307 addPoint(this.geometry.vertices[face.b], pointColor);
\r
308 addPoint(this.geometry.vertices[face.c], pointColor);
\r
309 addPoint(this.geometry.vertices[face.d], pointColor);
\r
315 * setup THREE JS Scene, Camera and Renderer
\r
317 function setup_scene() {
\r
318 const scene = new THREE.Scene();
\r
319 const camera = new THREE.PerspectiveCamera(75, window.innerWidth / (window.innerHeight - canvasSpace), 0.1, 1000);
\r
320 const renderer = new THREE.WebGLRenderer();
\r
321 /** size canvas to leave some space for UI */
\r
322 renderer.setSize(window.innerWidth, window.innerHeight - canvasSpace);
\r
323 /** embed canvas in HTML */
\r
324 document.getElementById("threejscontainer").appendChild(renderer.domElement);
\r
326 /** add global light */
\r
327 const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
\r
328 scene.add(directionalLight);
\r
330 /** position camera */
\r
331 camera.position.y = 5;
\r
332 camera.position.z = 10;
\r
334 return [scene, camera];
\r
338 let mousePos = new Point();
\r
341 * Space left empty under canvas
\r
344 const canvasSpace = 200;
\r
347 let [scene, camera] = setup_scene();
\r
349 /** setup cloth and generate debug mesh */
\r
350 let cloth = new Cloth();
\r
351 cloth.createBasic(10, 10, 5, 5);
\r
352 cloth.createDebugMesh(scene);
\r
354 const material = new THREE.MeshBasicMaterial({ color: 0x0000ff });
\r
355 const mesh = new THREE.Mesh(cloth.geometry, material);
\r
359 * function called every frame
\r
360 * @param {number} dt - time passed since last frame
\r
362 function animate(dt) {
\r
363 requestAnimationFrame(animate);
\r
364 renderer.render(scene, camera);
\r
367 /** add callback for window resize */
\r
368 let canvas = document.getElementsByTagName("canvas")[0];
\r
369 let resize = function () {
\r
370 w = window.innerWidth;
\r
371 h = window.innerHeight - 200;
\r
375 window.onresize = resize;
\r
379 * if canvas has been successfully initialized
\r
382 if (canvas.getContext) {
\r
383 animate(performance.now());
\r
386 /** add mouse move callback */
\r
387 canvas.onmousemove = (evt) => {
\r
388 mousePos.x = evt.clientX;
\r
389 mousePos.y = evt.clientY;
\r