Example #1
0
        private void Simulate()
        {
            // For each node in rope.
            for (int i = 0; i < nodes.Length; i++)
            {
                VerletNode node = nodes[i];

                // Acceleration is useful to have around if we want to apply external forces to rope nodes, just add the
                // force to `node.acceleration`.
                node.acceleration += gravity;

                float2 move = node.position - node.oldPosition;
                // Limiting maximum move gives the simulation more stability.
                if (math.length(move) > maxSimMove)
                {
                    move = math.normalize(move) * maxSimMove;
                }

                // Calculate new position.
                float2 temp = node.position;
                node.position   += move * (1f - node.friction) + stepTime * stepTime * node.acceleration;
                node.oldPosition = temp;

                node.acceleration = float2.zero;
                node.friction     = 0;

                nodes[i] = node;
            }
        }
Example #2
0
        private void ApplyConstraints()
        {
            for (int i = 0; i < nodes.Length - 1; i++)
            {
                VerletNode node1       = nodes[i];
                VerletNode node2       = nodes[i + 1];
                Constraint constraint1 = constraints[i];
                Constraint constraint2 = constraints[i + 1];

                // If the node is constrained, we need to set its position early so that the connected node adjusts
                // accordingly, but we don't want it to move, so we multiply movement by 0 later if we're constrained.
                // For the most part, we don't actually need to check `constraint2` because it's overwritten in the
                // Next iteration of the loop.  However, the last node never gets to be `node1`, so we have to check both.
                float mult1 = 1f, mult2 = 1f;
                if (constraint1.enabled)
                {
                    node1.position = constraint1.position;
                    mult1          = 0f;
                }
                if (constraint2.enabled)
                {
                    node2.position = constraint2.position;
                    mult2          = 0f;
                }


                // Get the current distance between rope nodes.
                float2 diff       = math.float2(node1.position.x - node2.position.x, node1.position.y - node2.position.y);
                float  dist       = math.length(node1.position - node2.position);
                float  difference = 0;
                // Guard against divide by 0.
                if (dist > 0)
                {
                    difference = (dist - nodeDistance) / dist;
                }

                diff *= .5f * difference;

                // Apply correction.
                node1.position -= diff * mult1;
                node2.position += diff * mult2;

                nodes[i]     = node1;
                nodes[i + 1] = node2;
            }
        }
Example #3
0
        private void AdjustCollisions()
        {
            // Loop through each collider.
            for (int i = 0; i < numCollisions; i++)
            {
                CollisionInfo nc = collisionInfos[i];

                // Looping inside the switch statement is marginally faster than the other way around.
                switch (nc.colliderType)
                {
                case ColliderType.Circle: {
                    float radius = nc.colliderSize.x * math.max(nc.scale.x, nc.scale.y);

                    // Correct each node which is colliding with this collider.
                    for (int j = 0; j < nc.numCollisions; j++)
                    {
                        VerletNode node     = nodes[collidingNodes[i * nodes.Length + j]];
                        float      distance = math.distance(nc.position, node.position);

                        // Leave if we're not actually colliding.
                        if (distance - radius > 0)
                        {
                            continue;
                        }

                        float2 dir    = math.normalize(node.position - nc.position);
                        float2 hitPos = nc.position + dir * radius;
                        node.position = hitPos;

                        // Colliding nodes should have some friction on them.
                        node.friction = friction;

                        nodes[collidingNodes[nodes.Length * i + j]] = node;
                    }
                }
                break;

                case ColliderType.Box: {
                    for (int j = 0; j < nc.numCollisions; j++)
                    {
                        VerletNode node       = nodes[collidingNodes[i * nodes.Length + j]];
                        float4     localPoint = math.mul(nc.wtl, math.float4(node.position, 0, 1));

                        // If distance from center is more than box "radius", then we can't be colliding.
                        Vector2 half   = nc.colliderSize * .5f;
                        Vector2 scalar = nc.scale;
                        float   dx     = localPoint.x;
                        float   px     = half.x - Mathf.Abs(dx);
                        if (px <= 0)
                        {
                            continue;
                        }

                        float dy = localPoint.y;
                        float py = half.x - Mathf.Abs(dy);
                        if (py <= 0)
                        {
                            continue;
                        }

                        // Need to multiply distance by scale or we'll mess up on scaled box corners.
                        if (px * scalar.x < py * scalar.y)
                        {
                            float sx = Mathf.Sign(dx);
                            localPoint.x = half.x * sx;
                        }
                        else
                        {
                            float sy = Mathf.Sign(dy);
                            localPoint.y = half.y * sy;
                        }

                        // Finally get the world space hit position.
                        float4 hitPos = math.mul(nc.ltw, localPoint);

                        node.position = hitPos.xy;
                        node.friction = friction;

                        nodes[collidingNodes[nodes.Length * i + j]] = node;
                    }
                }
                break;
                }
            }
        }
Example #4
0
        private void Awake()
        {
            if (totalNodes > MAX_RENDER_POINTS)
            {
                Debug.LogError("Total nodes is more than MAX_RENDER_POINTS, so won't be able to render the entire rope.");
            }

            // Buffer for OverlapCircleNonAlloc.
            colliderBuffer = new Collider2D[COLLIDER_BUFFER_SIZE];

            // Jobs setup.
            // You could use `NativeList` for some of these instead, and not worry about all the constants.
            nodes          = new NativeArray <VerletNode>(totalNodes, Allocator.Persistent);
            constraints    = new NativeArray <Constraint>(totalNodes, Allocator.Persistent);
            collisionInfos = new NativeArray <CollisionInfo>(MAX_ROPE_COLLISIONS, Allocator.Persistent);
            collidingNodes = new NativeArray <int>(totalNodes * MAX_ROPE_COLLISIONS, Allocator.Persistent);

            renderPositions = new Vector4[totalNodes];

            // Spawn nodes starting from the transform position and working down.
            Vector2 pos = transform.position;

            for (int i = 0; i < nodes.Length; i++)
            {
                nodes[i]           = new VerletNode(pos);
                renderPositions[i] = new Vector4(pos.x, pos.y, 1, 1);
                pos.y -= nodeDistance;
            }

            // Mesh setup.
            Mesh mesh = new Mesh();

            {
                Vector3[] vertices  = new Vector3[totalNodes * VERTICES_PER_NODE];
                int[]     triangles = new int[totalNodes * TRIANGLES_PER_NODE * 3];

                for (int i = 0; i < totalNodes; i++)
                {
                    // 4 triangles per node, 3 indices per triangle.
                    int idx = i * TRIANGLES_PER_NODE * 3;
                    // 8 vertices per node.
                    int vIdx = i * VERTICES_PER_NODE;

                    // Unity uses a CLOCKWISE WINDING ORDER -- clockwise tri indices are facing the camera.
                    triangles[idx + 0] = vIdx;        // v1 top
                    triangles[idx + 1] = vIdx + 1;    // v2 bottom
                    triangles[idx + 2] = vIdx + 2;    // v1 bottom
                    triangles[idx + 3] = vIdx;        // v1 top
                    triangles[idx + 4] = vIdx + 3;    // v2 top
                    triangles[idx + 5] = vIdx + 1;    // v2 bottom

                    triangles[idx + 6]  = vIdx + 4;   // tl
                    triangles[idx + 7]  = vIdx + 7;   // br
                    triangles[idx + 8]  = vIdx + 6;   // bl
                    triangles[idx + 9]  = vIdx + 4;   // tl
                    triangles[idx + 10] = vIdx + 5;   // tr
                    triangles[idx + 11] = vIdx + 7;   // br
                }

                // We only really care about the number of vertices, not what they actually are -- the positions aren't used.
                mesh.vertices  = vertices;
                mesh.triangles = triangles;
                // Since we pretty much want the rope to always render (it's always going to be on screen if it's active), we
                // just set the bounds super large to avoid recalculating the bounds when the rope changes.
                mesh.bounds = new Bounds(Vector3.zero, Vector3.one * 100f);
            }
            GetComponent <MeshFilter>().mesh = mesh;
            material = GetComponent <MeshRenderer>().material;
            material.SetFloat("_Width", drawWidth);
        }