private void Simulate() { for (int i = 0; i < nodes.Length; i++) { ref VerletNode node = ref nodes[i]; Vector2 temp = node.position; node.position += (node.position - node.oldPosition) + gravity * stepTime * stepTime; node.oldPosition = temp; }
private void ApplyConstraints() { Profiler.BeginSample("Constraints"); for (int i = 0; i < nodes.Length - 1; i++) { VerletNode node1 = nodes[i]; VerletNode node2 = nodes[i + 1]; // First node follows the mouse, for debugging. if (i == 0 && Input.GetMouseButton(0)) { node1.position = Camera.main.ScreenToWorldPoint(Input.mousePosition); } // Current distance between rope nodes. float diffX = node1.position.x - node2.position.x; float diffY = node1.position.y - node2.position.y; float dist = Vector2.Distance(node1.position, node2.position); float difference = 0; // Guard against divide by 0. if (dist > 0) { difference = (nodeDistance - dist) / dist; } Vector2 translate = new Vector2(diffX, diffY) * (.5f * difference); node1.position += translate; node2.position -= translate; } /* * // Distance constraint which reduces iterations, but doesn't handle stretchyness in a natural way. * VerletNode first = nodes[0]; * VerletNode last = nodes[nodes.Length-1]; * // Same distance calculation as above, but less optimal. * float distance = Vector2.Distance(first.position, last.position); * if (distance > 0 && distance > nodes.Length * nodeDistance) { * Vector2 dir = (last.position - first.position).normalized; * last.position = first.position + nodes.Length * nodeDistance * dir; * } */ Profiler.EndSample(); }
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."); } nodes = new VerletNode[totalNodes]; collisionInfos = new CollisionInfo[MAX_ROPE_COLLISIONS]; for (int i = 0; i < collisionInfos.Length; i++) { collisionInfos[i] = new CollisionInfo(totalNodes); } // Buffer for OverlapCircleNonAlloc. colliderBuffer = new Collider2D[COLLIDER_BUFFER_SIZE]; renderPositions = new Vector4[totalNodes]; // Spawn nodes starting from the transform position and working down. Vector2 pos = transform.position; for (int i = 0; i < totalNodes; 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); }
private void AdjustCollisions() { Profiler.BeginSample("Collision"); for (int i = 0; i < numCollisions; i++) { CollisionInfo ci = collisionInfos[i]; switch (ci.colliderType) { case ColliderType.Circle: { float radius = ci.colliderSize.x * Mathf.Max(ci.scale.x, ci.scale.y); for (int j = 0; j < ci.numCollisions; j++) { VerletNode node = nodes[ci.collidingNodes[j]]; float distance = Vector2.Distance(ci.position, node.position); // Early out if we're not colliding. if (distance - radius > 0) { continue; } Vector2 dir = (node.position - ci.position).normalized; Vector2 hitPos = ci.position + dir * radius; node.position = hitPos; } } break; case ColliderType.Box: { for (int j = 0; j < ci.numCollisions; j++) { VerletNode node = nodes[ci.collidingNodes[j]]; Vector2 localPoint = ci.wtl.MultiplyPoint(node.position); // If distance from center is more than box "radius", then we can't be colliding. Vector2 half = ci.colliderSize * .5f; Vector2 scalar = ci.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; } Vector2 hitPos = ci.ltw.MultiplyPoint(localPoint); node.position = hitPos; } } break; } } Profiler.EndSample(); }