Unity 基于质点弹簧模型的cloth(简易版)

原文地址: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=tv2v1
    在这里插入图片描述

代码

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🙁

  • 9
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值