原文地址:http://www.paulsprojects.net/opengl/cloth/cloth.html
https://blog.csdn.net/qq_30116831/article/details/87162564
我做了些修改,效果还挺好玩的哈~ 貌似扩展性比较好😄
准备(高中物理)
- 牛顿第二定律
F = m a F = ma F=ma - 速度
v = a t v = at v=at v 1 = v 2 + a t v_1 = v_2 + at v1=v2+at - 加速度
a = v t = v 2 − v 1 t a = \frac{v}{t}= \frac{ v2 - v1}{t} a=tv=tv2−v1
代码
using UnityEngine;
public class Ball : MonoBehaviour
{
public Vector3 position;
public Vector3 velocity;
public bool isFixed;
public Vector3 normal;
public float mass;
private void Update()
{
if (!isFixed)
{
transform.position = position;
}
else
{
position = transform.position;
}
}
}
using UnityEngine;
public class ClothSimulation2 : MonoBehaviour
{
public class Spring
{
// Spring head and tail
public int ball1;
public int ball2;
// Tension in the spring
public float tension;
public float springConstant;
public float naturalLength;
}
public int gridSize;
[HideInInspector]
public int ballsNum;
[HideInInspector]
public Ball[] balls1;
[HideInInspector]
public Ball[] balls2;
[HideInInspector]
public Ball[] currentBalls;
[HideInInspector]
public Ball[] nextBalls;
public Ball ball;
public Transform bigSphere;
public float gravityScale = 1;
public float springConstant = 15.0f;
public float slantConstant = 15.0f;
public float intervalConstant = 15.0f;
public float naturalLength = 1f;
public float horLength = 0.2f;
public float verLength = 0.2f;
public float mass = 0.01f;
public float dampFactor = 0.7f;
[HideInInspector]
public int springNum;
public Spring[] springs;
public Vector3 firstPosition;
private void Start()
{
Init();
ResetCloth();
}
private void Update()
{
CalculateState();
//balls1[0].position
//balls1[horCount * (horCount - 1)].isFixed = true;
//if(Input.GetKeyDown(KeyCode.Space))
//{
// Reset();
//}
DrawLine();
}
public void Reset()
{
Init();
ResetCloth();
}
public void Init()
{
ballsNum = gridSize * gridSize;
springNum = (gridSize - 1) * gridSize * 2;
springNum += (gridSize - 1) * (gridSize - 1) * 2;
springNum += (gridSize - 2) * gridSize * 2;
balls1 = new Ball[ballsNum];
balls2 = new Ball[ballsNum];
springs = new Spring[springNum];
}
public void ResetCloth()
{
for (int i = 0; i < gridSize; ++i)
{
for (int j = 0; j < gridSize; ++j)
{
balls1[i * gridSize + j] = Instantiate<Ball>(ball, transform.position, Quaternion.Euler(new Vector3(0, 0, 0)));
balls1[i * gridSize + j].name = balls1[i * gridSize + j].name + i + "" + j;
balls1[i * gridSize + j].transform.SetParent(transform);
balls1[i * gridSize + j].position.Set( j - (gridSize - naturalLength) / 2,
8.5f,
i - (gridSize - naturalLength) / 2);
balls1[i * gridSize + j].velocity = Vector3.zero;
balls1[i * gridSize + j].mass = mass;
balls1[i * gridSize + j].isFixed = false;
}
}
//Fix the top left & top right balls in place
balls1[0].isFixed = true;
balls1[0].GetComponent<Renderer>().material.color = Color.red;
balls1[gridSize - 1].isFixed = true;
balls1[gridSize - 1].GetComponent<Renderer>().material.color = Color.red;
balls1[gridSize - 1].transform.position += Vector3.right * gridSize;
//Fix the bottom left & bottom right balls
//balls1[gridSize * (gridSize - 1)].isFixed = true;
//balls1[gridSize * gridSize - 1].isFixed = true;
for (int i = 0; i < ballsNum; ++i)
balls2[i] = balls1[i];
// Set the currentBalls and nextBalls pointers
currentBalls = balls1;
nextBalls = balls2;
//Initialise the springs
for (int i = 0; i < springNum; i++)
{
springs[i] = new Spring();
}
Spring currentSpring = springs[0];
int index = 0;
// The first (gridSize-1)*gridSize springs go from one ball to the next,
// excluding those on the right hand edge
for (int i = 0; i < gridSize; ++i)
{
for (int j = 0; j < gridSize - 1; ++j)
{
currentSpring.ball1 = i * gridSize + j;
currentSpring.ball2 = i * gridSize + j + 1;
currentSpring.springConstant = springConstant;
currentSpring.naturalLength = naturalLength;
currentSpring = springs[++index];
}
}
// The next (gridSize-1)*gridSize springs go from one ball to the one below,
// excluding those on the bottom edge
for (int i = 0; i < gridSize - 1; ++i)
{
for (int j = 0; j < gridSize; ++j)
{
currentSpring.ball1 = i * gridSize + j;
currentSpring.ball2 = (i + 1) * gridSize + j;
currentSpring.springConstant = springConstant;
currentSpring.naturalLength = naturalLength;
currentSpring = springs[++index];
}
}
// The next (gridSize-1)*(gridSize-1) go from a ball to the one below and right
// excluding those on the bottom or right
for (int i = 0; i < gridSize - 1; ++i)
{
for (int j = 0; j < gridSize - 1; ++j)
{
currentSpring.ball1 = i * gridSize + j;
currentSpring.ball2 = (i + 1) * gridSize + j + 1;
currentSpring.springConstant = slantConstant;
currentSpring.naturalLength = Mathf.Sqrt(naturalLength * naturalLength * 2);
currentSpring = springs[++index];
}
}
// The next (gridSize-1)*(gridSize-1) go from a ball to the one below and left
// excluding those on the bottom or right
for (int i = 0; i < gridSize - 1; ++i)
{
for (int j = 1; j < gridSize; ++j)
{
currentSpring.ball1 = i * gridSize + j;
currentSpring.ball2 = (i + 1) * gridSize + j - 1;
currentSpring.springConstant = slantConstant;
currentSpring.naturalLength = Mathf.Sqrt(naturalLength * naturalLength * 2);
currentSpring = springs[++index];
}
}
// The first (gridSize-2)*gridSize springs go from one ball to the next but one,
// excluding those on or next to the right hand edge
for (int i = 0; i < gridSize; ++i)
{
for (int j = 0; j < gridSize - 2; ++j)
{
currentSpring.ball1 = i * gridSize + j;
currentSpring.ball2 = i * gridSize + j + 2;
currentSpring.springConstant = intervalConstant;
currentSpring.naturalLength = naturalLength * 2;
currentSpring = springs[++index];
}
}
// The next (gridSize-2)*gridSize springs go from one ball to the next but one below,
// excluding those on or next to the bottom edge
for (int i = 0; i < gridSize - 2; ++i)
{
for (int j = 0; j < gridSize; ++j)
{
currentSpring.ball1 = i * gridSize + j;
currentSpring.ball2 = (i + 2) * gridSize + j;
currentSpring.springConstant = intervalConstant;
currentSpring.naturalLength = naturalLength * 2;
++index;
if (index >= springs.Length)
{
return;
}
currentSpring = springs[index];
}
}
}
public void CalculateState()
{
for (int i = 0; i < springNum; ++i)
{
float springLength = (currentBalls[springs[i].ball1].position -
currentBalls[springs[i].ball2].position).magnitude;
float extension = springLength - springs[i].naturalLength;
springs[i].tension = springs[i].springConstant * extension / springs[i].naturalLength;
}
// Calculate the nextBalls from the currentBalls
for (int i = 0; i < ballsNum; ++i)
{
// Transfer properties which do not change
nextBalls[i].isFixed = currentBalls[i].isFixed;
nextBalls[i].mass = currentBalls[i].mass;
// If the ball is fixed, transfer the position and zero the velocity, otherwise calculate
// the new values
if (currentBalls[i].isFixed)
{
nextBalls[i].position = currentBalls[i].position;
nextBalls[i].velocity = Vector3.zero;
}
else
{
// Calculate the force on this ball
Vector3 force;
force = gravityScale * Physics2D.gravity;
// Loop through springs
for (int j = 0; j < springNum; ++j)
{
// If this ball is "ball1" for this spring, add the tension to the force
if (springs[j].ball1 == i)
{
Vector3 tensionDirection = currentBalls[springs[j].ball2].position -
currentBalls[i].position;
tensionDirection.Normalize();
force += springs[j].tension * tensionDirection;
}
// Similarly if the ball is "ball2"
if (springs[j].ball2 == i)
{
Vector3 tensionDirection = currentBalls[springs[j].ball1].position -
currentBalls[i].position;
tensionDirection.Normalize();
force += springs[j].tension * tensionDirection;
}
}
//Calculate the acceleration
Vector3 acceleration = force / currentBalls[i].mass;
//Update velocity
nextBalls[i].velocity = currentBalls[i].velocity + acceleration * 0.01f;
//Damp the velocity
nextBalls[i].velocity *= dampFactor;
//Calculate new position
nextBalls[i].GetComponent<Renderer>().material.color = Color.white;
nextBalls[i].position = currentBalls[i].position +
(nextBalls[i].velocity + currentBalls[i].velocity) * 0.01f;
// move collider
SphereCollider nextBallCollider = nextBalls[i].GetComponent<SphereCollider>();
float sphereRadius = bigSphere.GetComponent<SphereCollider>().radius * bigSphere.transform.localScale.x;
float particleRadius = nextBallCollider.radius * nextBallCollider.transform.localScale.x;
float r = sphereRadius + particleRadius;
float r2 = r * r;
//Vector3 d = nextBallCollider.transform.position - bigSphere.transform.position; // error
Vector3 d = nextBalls[i].position - bigSphere.position;
float len2 = d.sqrMagnitude;
if is inside sphere, project onto sphere surface
if (len2 < r2)
{
nextBalls[i].GetComponent<Renderer>().material.color = Color.yellow;
//float len = Mathf.Sqrt(len2);
//nextBalls[i].position = sphereCollider.transform.position + d * (sphereRadius / len);
nextBalls[i].position = bigSphere.transform.position + d.normalized * r;
}
//Check against sphere (at origin)
//if (nextBalls[i].position.sqrMagnitude < sphereRadius * sphereRadius)
// nextBalls[i].position = nextBalls[i].position.normalized * sphereRadius;
//Check against floor
//if (nextBalls[i].position.y < -8.5f)
// nextBalls[i].position.y = -8.5f;
}
}
// Swap the currentBalls and newBalls pointers
Ball[] temp = currentBalls;
currentBalls = nextBalls;
nextBalls = currentBalls;
}
// Draw spring
private void DrawLine()
{
for (int i = 0; i < springNum; ++i)
{
//Check the spring has been initialised and the ball numbers are in bounds
if (springs[i].ball1 != -1 && springs[i].ball2 != -1 &&
springs[i].ball1 < ballsNum && springs[i].ball2 < ballsNum)
{
Debug.DrawLine(currentBalls[springs[i].ball1].position, currentBalls[springs[i].ball2].position, Color.green);
}
}
}
}
总结
其中有个地方获取position遇到了问题,直接从transform获取和从collider间接获取,貌似是有区别的,最好直接从transform获取,否则会存在精度的问题,不知道是不是Unity的bug🙁