Reit Games
  • News
  • Games
  • About
  • Cover image

    Fun with cloth physics and meshes

    Playing around with cloth physics and meshes give fun and interesting results. Use it to create a net for a football goal or a basketball hoop. I've included a complete example of the technique, and you can use it in your own game!

    How I created a basketball hoop using cloth physics and a mesh

    I participated in yet another game-jam, WamJam. I think it's becoming an addiction. You can play my game on Itch.io (it works best on mobile.) As always when I create a game for a jam, I try to focus on one thing and work around that. I wanted to learn more about meshes, and thought it would be a good idea to create a combination of cloth physics and mesh to simulate a basketball hoop. Here's how the end result looks like.

    blog post image

    Notice how the net is reacting to the balls hitting it? Using the technique I describe in this blog post will help you with the tools to create something similar.

    This blog post will be focused on using the same technique I used for the hoop, but focus on a single image instead. I've picked the famous Phaser logo in this blog post.

    Creating the mesh

    A mesh in this context is just a way of representing how the image should be rendered on the screen. We partition the image into several vertices connected in a grid of triangles (the yellow and green grid we see on the main image of the Phaser logo.)

    I'm using Phaser in my examples. We can create a mesh by loading an image, and use Phaser's built in function to convert the image to a mesh, and partition the image into our grid of vertices. Note that this only works with WebGL.

    ourScene.js

    class Level extends Phaser.Scene {
      mesh;
      
      preload() {
         this.load.image('example', 'assets/sprites/phaser3-logo.png');
      }
    
      create() {
        this.createMesh();  
      }
      
      createMesh() {
        const mesh = this.add.mesh(300,300, 'example');
        // Create the mesh grid
        Phaser.Geom.Mesh.GenerateGridVerts({
          width: 412, // width of image
          height: 93, // height of image
          texture: 'example',
          mesh,
          widthSegments: 5,
          heightSegments: 2,
        });
        // need to call it once to make setOrtho work for some reason
        mesh.panZ(1); 
        // make the mesh visible in the scene
        mesh.setOrtho(mesh.width, mesh.height); 
        this.mesh = mesh;
      }
    }

    Creating the cloth physics

    I'm not going to describe the technical parts of cloth physics, but I will go through how you can set it up and use it. Phaser has a good cloth physics example on their official home page, and I recommend taking a look to play around with it.

    A cloth is very similar to the structure we created for our mesh. It's a series of vertices that make up a grid of rectangles. The cloth follows the rules of the physics engine, matter.js in this example, and it behaves similar to what we would expect a piece of cloth would be in the real world. We create it using Phaser's built in function. We define the position, width and height, as well as how large each cell should be.

    ourScene.js

    class Level extends Phaser.Scene {
      cloth;
      createCloth() {
        const group = this.matter.world.nextGroup(true);
        const particles = { 
          friction: 0.00001,
          collisionFilter: { group: group },
          render: { visible: false } 
        };
        const constraints = { stiffness: 0.06 };
        // softBody signature (x, y, columns, rows, columnGap, rowGap, crossBrace, particleRadius, particleOptions, constraintOptions)
        const cloth = this.matter.add.softBody(
          250, 400, 6, 3, 65, 35, false, 8, particles, constraints
        );
        
        // Crete two anchors, to prevent the cloth from collapsing
        cloth.bodies[0].isStatic = true;
        cloth.bodies[5].isStatic = true;
        this.cloth = cloth;
      }
    }
    

    Putting it all together

    Now comes the fun part. We will connect each cell in the cloth, and map it to each of the vertices in the mesh. The cloth will then update the position of all the vertices in the mesh, creating some fun visual effects. It will look like the image is bending, or moving in 3D, but everything is in fact 2D. I think this is very fascinating and powerful, because we're able to create some convincing 3D-looking effects in 2D.

    In order to connect the cloth to the mesh, we first need to update the order of vertices in the mesh according to UV-coordinates (from top left to bottom right.) We also need to make sure we update all the vertices in the mesh that are connected on the same position. If we only move 1 vertex, the mesh will be cut:

    blog post image

    In the video, the Phaser logo consists of two triangles (our mesh.) When we only move the position of 1 vertex, only one triangle is being affected. We need to move both vertices that are connected to prevent the logo from being cut. Here's how it will look like if we drag both vertices in the mesh:

    blog post image

    If we sort each vertex according to UV-coordinates, it will be easier to map the cloth bodies to the mesh later on.

    meshUtils.js

    export function createUniqueSortedVertices(vertices) {
      const seen = {};
      // we need to remove verticies with the same coordinates 
      // to make sure we map the bodies in the cloth correctly
      // this will become more apparent when we connect the cloth to the mesh
      const tmp = [...vertices].filter((item)=> {
        if(seen.hasOwnProperty(item.u + '-' + item.v)) return false;
        seen[item.u + '-' + item.v] = true;
        return true;
      });
    
      // sort based on UV map. UV coordinate 0,0 first, and 1,1 last
      tmp.sort((a, b) => {
          if (a.v < b.v) return -1;
          if (a.v > b.v) return 1;
          if (a.u < b.u) return -1;
          if (a.u > b.u) return 1;
          return 0;
        });
      return tmp;
    }
    
    // We need to move all vertices that share the same vertex
    // v1 is the "unique" vertex from the sorted list we created in createUniqueSortedVertices
    // we return all vertices that shares the same position as v1
    export function getSharedVerteces(v1, vertices) {
      const shared = [];
      for (let i = 0; i < vertices.length; i++) {
        const v = vertices[i];
        if (v.x === v1.x && v.y === v1.y) shared.push(v);
      }
      return shared;
    }
    

    We will be using these two utility functions when we update the mesh according to the cloth.

    ourScene.js

    import {createUniqueSortedVertices, getSharedVerteces} from 'meshUtils';
    
    class Level extends Phaser.Scene {
      mesh;
      cloth;
      uniqueVertiesSortedByUV;
    
      create() {
        // create mesh and cloth like before
        this.createMesh();
        this.createCloth();
        
        this.uniqueVertiesSortedByUV = createUniqueSortedVertices(
          this.mesh.vertices
        );
      }
    
      // update called every frame
      upcate() {
        this.updateMesh();
      }
      
      updateMesh() {
        if (!this.mesh || !this.cloth) return;
        // need to set this flag and call preUpdate to update the mesh
        this.mesh.ignoreDirtyCache = true; 
        // map each body in the cloth to a vertex in the mesh
        // and update the mesh position based on body position
        for (let i = 0; i < this.cloth.bodies.length; i++) {
          // each cell in the cloth is a body.
          // We created the cloth to have qually many bodies 
          // as we have squares in the mesh
          const body = this.cloth.bodies[i];
    
          // Since we have sorted all vertices according
          // to their UV-map, and removed duplicated vertices,
          // we know for sure that the each cell in the cloth 
          // will have a 1 to 1 mapping to each vertex.
          // That's why it's important to have cells (bodies) in 
          // the cloth equally large as the mesh
          const shareVertices = getSharedVerteces(
            this.getClosestVertex(i),
            this.mesh.vertices
          );
    
          // Change the position of the vertices in the mesh
          // according to the cloth position
          shareVertices.forEach((v) => {
            v.x = body.position.x - this.mesh.x; 
            v.y = -body.position.y + this.mesh.y;
          });
        }
    
        this.mesh.preUpdate();
        this.mesh.ignoreDirtyCache = false;
      }
      
      getClosestVertex(i) {
        return this.uniqueVertiesSortedByUV[i];
      }
    }

    With this, we should now have an image that's rendered with the help of a mesh, cloth physics which changes the position of each vertex in the mesh and follow the laws of physics.

    I've included an interactive example at the bottom of this post. Try to drag your mouse any of the vertices (green corners) of the Phaser logo below and have fun! It only works on desktop, and you have to be very accurate with your mouse pointer. The complete example is available on CodePen if you'd like to make some experiments as well.

    If you liked this blog post, or want me to elaborate on other topics, consider following me on Twitter, @ReitGames, and mention me in a Tweet. Happy coding!