diff --git a/Assets/Scripts/Rope/Point.cs b/Assets/Scripts/Rope/Point.cs new file mode 100644 index 0000000..65a928d --- /dev/null +++ b/Assets/Scripts/Rope/Point.cs @@ -0,0 +1,15 @@ +using UnityEngine; + +[System.Serializable] +public class Point +{ + public Vector2 position, prevPosition; + public bool locked; + + public Point(Vector2 position, bool locked = false) + { + this.position = position; + this.prevPosition = position; + this.locked = locked; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Rope/Point.cs.meta b/Assets/Scripts/Rope/Point.cs.meta new file mode 100644 index 0000000..dc66062 --- /dev/null +++ b/Assets/Scripts/Rope/Point.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a07dfdaccf053414085b41179c0ab234 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Rope/Rope.cs b/Assets/Scripts/Rope/Rope.cs new file mode 100644 index 0000000..de4d20b --- /dev/null +++ b/Assets/Scripts/Rope/Rope.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using UnityEngine; + +public class Rope +{ + public List points { get ; private set; } + public List sticks { get; private set; } + + public Rope(List points, List sticks) + { + this.points = points; + this.sticks = sticks; + } +} diff --git a/Assets/Scripts/Rope/Rope.cs.meta b/Assets/Scripts/Rope/Rope.cs.meta new file mode 100644 index 0000000..6b679cf --- /dev/null +++ b/Assets/Scripts/Rope/Rope.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fb42a2c8fa09c32489663226dc7167e1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Rope/RopeBuilder.cs b/Assets/Scripts/Rope/RopeBuilder.cs new file mode 100644 index 0000000..2de53aa --- /dev/null +++ b/Assets/Scripts/Rope/RopeBuilder.cs @@ -0,0 +1,32 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public class RopeBuilder +{ + List points = new(); + List sticks = new(); + + public RopeBuilder AddPoint(Point point) + { + points.Add(point); + return this; + } + + public RopeBuilder ConnectPoints(Point A, Point B) + { + sticks.Add(new Stick(A, B)); + return this; + } + + public RopeBuilder ConnectPoints(int idxA, int idxB) + { + sticks.Add(new Stick(points[idxA], points[idxB])); + return this; + } + + public Rope Build() + { + return new Rope(points: points, sticks: sticks); + } +} diff --git a/Assets/Scripts/Rope/RopeBuilder.cs.meta b/Assets/Scripts/Rope/RopeBuilder.cs.meta new file mode 100644 index 0000000..d98389a --- /dev/null +++ b/Assets/Scripts/Rope/RopeBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e87b5185694f23a4390596cc0294bee7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Rope/RopeSimulator.cs b/Assets/Scripts/Rope/RopeSimulator.cs new file mode 100644 index 0000000..d7bf275 --- /dev/null +++ b/Assets/Scripts/Rope/RopeSimulator.cs @@ -0,0 +1,129 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityUtils; + +public class RopeSimulator : MonoBehaviour +{ + [SerializeField] + private float gravity = 10; + + [SerializeField] + private float solveIterations = 10; + + [SerializeField] + private float constrainStickMinLength = 0.1f; + + int[] order; + + public Vector2 testPos; + + Rope rope; + + private void Start() + { + rope = new RopeBuilder() + .AddPoint(new Point(testPos, locked: true)) + .AddPoint(new Point(testPos.Add(x:5f))) + .AddPoint(new Point(testPos.Add(x: 10f))) + .AddPoint(new Point(testPos.Add(x: 15f))) + .AddPoint(new Point(testPos.Add(x: 20f))) + .ConnectPoints(0, 1) + .ConnectPoints(1, 2) + .ConnectPoints(2, 3) + .ConnectPoints(3, 4) + .Build(); + CreateOrderArray(); + } + + private void Update() + { + Simulate(); + } + + private void OnDrawGizmos() + { + if (!Application.isPlaying) return; + + foreach (var point in rope.points) + { + Debug.Log($"pos: {point.position}"); + Gizmos.DrawSphere(point.position, 1f); + } + } + + void Simulate() + { + foreach (Point p in rope.points) + { + if (!p.locked) + { + Vector2 positionBeforeUpdate = p.position; + p.position += p.position - p.prevPosition; + p.position += Vector2.down * gravity * Time.deltaTime * Time.deltaTime; + p.prevPosition = positionBeforeUpdate; + } + } + + for (int i = 0; i < solveIterations; i++) + { + for (int s = 0; s < rope.sticks.Count; s++) + { + Stick stick = rope.sticks[order[s]]; + if (stick.dead) + { + continue; + } + + Vector2 stickCentre = (stick.A.position + stick.B.position) / 2; + Vector2 stickDir = (stick.A.position - stick.B.position).normalized; + float length = (stick.A.position - stick.B.position).magnitude; + + if (length > stick.length || length > constrainStickMinLength) + { + if (!stick.A.locked) + { + stick.A.position = stickCentre + stickDir * stick.length / 2; + } + if (!stick.B.locked) + { + stick.B.position = stickCentre - stickDir * stick.length / 2; + } + } + + } + } + } + + void CreateOrderArray() + { + order = new int[rope.sticks.Count]; + for (int i = 0; i < order.Length; i++) + { + order[i] = i; + } + ShuffleArray(order, new System.Random()); + } + + public static T[] ShuffleArray(T[] array, System.Random prng) + { + + int elementsRemainingToShuffle = array.Length; + int randomIndex = 0; + + while (elementsRemainingToShuffle > 1) + { + + // Choose a random element from array + randomIndex = prng.Next(0, elementsRemainingToShuffle); + T chosenElement = array[randomIndex]; + + // Swap the randomly chosen element with the last unshuffled element in the array + elementsRemainingToShuffle--; + array[randomIndex] = array[elementsRemainingToShuffle]; + array[elementsRemainingToShuffle] = chosenElement; + } + + return array; + } +} diff --git a/Assets/Scripts/Rope/RopeSimulator.cs.meta b/Assets/Scripts/Rope/RopeSimulator.cs.meta new file mode 100644 index 0000000..632c74c --- /dev/null +++ b/Assets/Scripts/Rope/RopeSimulator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 27ac133d9e10e544ba603e07122e3359 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Rope/Stick.cs b/Assets/Scripts/Rope/Stick.cs new file mode 100644 index 0000000..078e0fe --- /dev/null +++ b/Assets/Scripts/Rope/Stick.cs @@ -0,0 +1,16 @@ +using UnityEngine; + +[System.Serializable] +public class Stick +{ + public Point A, B; + public float length; + public bool dead; + + public Stick(Point pointA, Point pointB) + { + this.A = pointA; + this.B = pointB; + length = Vector2.Distance(pointA.position, pointB.position); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Rope/Stick.cs.meta b/Assets/Scripts/Rope/Stick.cs.meta new file mode 100644 index 0000000..0ce0dd4 --- /dev/null +++ b/Assets/Scripts/Rope/Stick.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 61cc19e8607c5c243941e77804b59b30 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: