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!
Updated: Jul 26, 2023
Published: Jul 22, 2023
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.
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:
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:
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;
}