Introduction
First of all, what do I mean by a fabric? This model I'm describing can be used for any surface that can bend, so it can be a flag, some curtains or anything else you want it to be. This fabric will follow some basic rules such as gravity and elasticity. The physics model is pretty solid, but uses TRIapi to draw, so due to some bugs and limitations in the engine there will be problems.
So here goes:
- The flag is made up of a grid of points.
- Each of the points has its own position and velocity.
- Each point will interact only with its neighbouring points.
- Each point will feel the force of gravity and possibly any wind or other forces.
This is a picture of the fabric in action. I haven't got a better picture at the moment. The full experience can't be gained from just seeing the picture. The texture can be a picture of anything, but it has to be saved as a sprite, which means only 256 colours.
The Grid:
The grid will be made up of points arranged in either a trigonal or rectangular structure. I advise going for a rectangular grid to begin with. Then once you have a working model and understand exactly how it works, switch to a grid made up of triangles.
I advise using multiple arrays, each holding a different property.
eg.
vec3_t gOrigin[20][20];
vec3_t gVelocity[20][20];
vec3_t gForce[20][20]; //The constant forces applied to the fabric(gravity, wind etc.)
float gSprPosX[20][20]; //I'll explain these later
float gSprPosY[20][20];
These are the basic arrays needed. I only picked the value of 20 because this is a suitable value for the size of a flag. So the grid would have 20 rows and 20 columns. The distance between the points is up to you. It is a trade-off between being able to see the individual polygons, so the fabric looks smooth, and rendering speed.
Organisation of Code
I would suggest dividing the code into different parts.
- The Initialisation
- The Physics
- The Rendering
Initialisation
This is called on start-up. It consists of a loop that defines every point's starting point, velocity and constant forces (eg. gravity). It also assigns every point's texture location (I'll come to this later).
Physics
For particles it is sufficient to use the same loop for physics and rendering, but it will not work for fabrics. This loop will go through every point and work out the physics -- both the interactions with other points and gravity and wind. I call this function every frame before the rendering but to lighten the load you could call it less frequently.
Rendering
This is yet another loop that draws the fabric. Use triangles to draw it, even if you are using a rectangular grid. If you don't, some of the surfaces will be invalid because every point wont lie on the same plane. If you have followed my recommendation of only trying this after doing a particle engine, you should have a deeper understanding of this process.
The maths
I'll assume you have a knowledge of basic mechanics and pure mathmatics.
Interactions between points:
First work out the distance between the two points in question, using
3D pythagoras.
Now take away the distance you want between your points. If the result is positive, the points need to go together; negative, they need to move apart. Using this value, the force that has to be exerted on the points is worked out. To do this, the position from the first point is subtracted from the second to get the direction. This is then multiplied by the result obtained earlier, which gives the force its direction and magnitude. This force then needs to act upon the two particles. Add the force to the velocity for one point and take it away from the other.
This shows a trigonal grid and how every point must interact with its neighbouring points. These red lines must be kept the same length to keep the structure rigid. However, notice that the stucture can bend if in 3D dimensions, giving its fabric properties.
If a little bit of range between the points if allowed then the fabric will become stretchy. Remember to make sure that the points on the sides of the grid don't try to interact with points that don't exist. That would just cause Half-Life to crash on running. It's this sort of problem that makes the maths difficult. An infinite fabric would be comparitively easy.
This section is very difficult to get right because one bit of confusion over whether a value is + or - can result in the points repelling instead of attacting or the other way round.
The interval between this function being called depends on the frame-rate. So without any correction, the flag will flap far faster on a system with a frame-rate of 72fps than on a system with only 20fps. So a way is needed to regulate the speed. This is done by moving the flag less per calculation, if there is a higher frame-rate. To do this I recommend
working out the time since the last time the function was called. Then use this value to work out how big a force is needed and everything.
ie.
dTime = gEngfuncs.GetClientTime()-LastTime;
LastTime = gEngfuncs.GetClientTime();
||||||||WARNING||||||||During each step you will need to multiply the vectors by a certain factor. If the force vector is too large, the points will pass each other during their movement. Think about the consequences of this. From now on these particles will push away from each other to try and return to their proper positions. This can cause extremely unpredictable results, sually involving every point travelling away from every other point at an exponential velocity.
Make sure this doesn't happen!Using a loop, this function will have to be applied to every point for each of its neighbours. The purpose of this interaction is to keep the fabric in the shape you want it. To make the fabric elastic, just make the reaction force less strong, so the grid is allowed to stretch a little.
Fabric Texture
This section will explain how to place your chosen texture onto your fabric. Normally to place a sprite on a surface you would have the texture position commands as 1 and 0, which defines the corners of the texture, so the whole texture is drawn. Sadly, it's not as simple as that because there are many polygons, so the positions have to be assigned for each polygon. That's what those mysterious arrays were for at the beginning. Each point has its own (x, y) which defines where the point maps onto the texture. This value is defined in the initialisation function. I'll let you do the maths. An obvious product of this is that the texture will be back-to-front if viewed from the wrong side. There is no easy work-around for this, unless your sprite is symmetrical or you want it to be back-to front.
Example of how the points map onto a texture.
In addition to the triangle and upside-down triangles, you also need to draw some triangles at the sides to avoid a zig-zag pattern. If you do all this you're left with the corners having a bit cut off, but that's barely noticeable, especially when the fabric's flapping about in the wind.
Other Forces
The inter-point forces are the most important, but without the influence of other forces the fabric will go to a certain shape and position and just stop, it will not move and be pretty boring. So I advise adding gravity and some wind. Even with gravity and wind, the fabric will reach equilibrium and stay at rest. So, as in real life, you have to make the wind change its direction and magnitude for a realistic effect. How you do this is up to you.
Another important force is a dampening force, this force will allow the effect of air resistance. It also ensures that if there are any slight problems in the maths, the fabric wont start doing unexpected things. To add this
dampening force just multiply the velocity by a certain factor, very close to 1.
eg.
gVelocity[a][b] = gVelocity[a][b] * pow(0.95, dTime);
Getting Started
Hopefully you've at least half understood the physics model so far, but that's a long way from being able to make one yourself. I'd advise you to first make a 2D example, which is just a piece of string swinging about. It uses exactly the same principles as the fabric, but it's simpler to program.
Once you've got a working version of that then you should understand far more how the system works.
Possible Problems
These are just a few of the problems I've run into:
- When using a rectangular grid, so each point only interacts with the one above, the one below, the one to the left and the one to thr right, the grid can collapse into an acute lattice pattern.
- If the flag is bent over, you may be able to see part of the flag that is behind the other part. This problem is very difficult to overcome, while keeping the frame-rate up.
- Similar to the last point, if there are two TRIapi effects running then the one rendered last will appear in front, irrespective of their positions in 3D space.
- Yet another graphical glitch, entities will appear in front of the fabric, this also goes with particles too. The mappers will have to deal with this and make sure that there can not be a situation, where this problem can be observed.
- As each point only reacts with the points next to it, the fabric can go through itself, if this happens, you will know what I mean.
Conclusion
If you have understood it all then well-done. I hope you will find it useful. If you do succeed in coding it then please
E-mail me with some screenshots and include my name in the read-me. Don't E-mail me with coding problems, because it's doubtful if I could help you even if I had the time. If there's any bits that after a few times reading you can't understand then please tell me, so I can improve the article.
Good Luck!