rope physics

This commit is contained in:
Sveske Juice 2024-02-02 15:15:07 -08:00
parent ab098c3370
commit d781aace61
6 changed files with 1463 additions and 35 deletions

File diff suppressed because it is too large Load Diff

View File

@ -11,4 +11,15 @@ public class Rope
this.points = points; this.points = points;
this.sticks = sticks; this.sticks = sticks;
} }
public float CalculateLengthOvershoot()
{
float sum = 0f;
foreach (Stick stick in sticks)
{
float dist = Vector3.Distance(stick.A.position, stick.B.position);
sum += dist - stick.desiredLength;
}
return sum;
}
} }

View File

@ -25,6 +25,12 @@ public class RopeBuilder
return this; return this;
} }
public RopeBuilder ConnectPointsWithDesiredLength(int idxA, int idxB, float desiredLength)
{
sticks.Add(new Stick(points[idxA], points[idxB], desiredLength));
return this;
}
public Rope Build() public Rope Build()
{ {
return new Rope(points: points, sticks: sticks); return new Rope(points: points, sticks: sticks);

View File

@ -1,6 +1,12 @@
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting;
using UnityEditor.ShaderGraph.Drawing;
using UnityEngine; using UnityEngine;
using UnityEngine.Analytics;
using UnityEngine.Rendering;
using UnityUtils; using UnityUtils;
public class RopeSimulator : MonoBehaviour public class RopeSimulator : MonoBehaviour
@ -9,36 +15,120 @@ public class RopeSimulator : MonoBehaviour
private float gravity = 10; private float gravity = 10;
[SerializeField] [SerializeField]
private float solveIterations = 10; private int solveIterations = 10;
[SerializeField] [SerializeField]
private float constrainStickMinLength = 0.1f; private bool constrainStickMinLength;
[SerializeField]
Transform start, end;
[SerializeField]
int subDivision = 50;
[SerializeField]
float collisionCheckDist = 0.5f;
[SerializeField, Range(0f, 1f)]
float distBetweenRopePoints = 0.1f;
[SerializeField, Range(0.01f, 1f)]
float ropeRadius;
[SerializeField]
float ignoreResolveThreshold = 0.08f;
[SerializeField]
Transform ropeCollidersParent;
[SerializeField]
LayerMask staticColliderMask;
[SerializeField, Range(0f, 100f)]
float pullForce = 20f;
int[] order; int[] order;
public Vector2 testPos; public Vector2 testPos;
Vector2 prevStartPos;
Rope rope; Rope rope;
private void Start() private void Start()
{ {
rope = new RopeBuilder() //rope = new RopeBuilder()
.AddPoint(new Point(testPos, locked: true)) // .AddPoint(new Point(testPos, locked: true))
.AddPoint(new Point(testPos.Add(x:5f))) // .AddPoint(new Point(testPos.Add(x:5f)))
.AddPoint(new Point(testPos.Add(x: 10f))) // .AddPoint(new Point(testPos.Add(x: 10f)))
.AddPoint(new Point(testPos.Add(x: 15f))) // .AddPoint(new Point(testPos.Add(x: 15f)))
.AddPoint(new Point(testPos.Add(x: 20f))) // .AddPoint(new Point(testPos.Add(x: 20f)))
.ConnectPoints(0, 1) // .ConnectPoints(0, 1)
.ConnectPoints(1, 2) // .ConnectPoints(1, 2)
.ConnectPoints(2, 3) // .ConnectPoints(2, 3)
.ConnectPoints(3, 4) // .ConnectPoints(3, 4)
.Build(); // .Build();
RopeBuilder builder = new RopeBuilder();
builder.AddPoint(new Point(start.position, locked: true));
for (int i = 1; i < subDivision; i++)
{
Vector2 pointPos = Vector2.Lerp(start.position, end.position, (float)i / (float)subDivision);
//Debug.Log($"pos: {pointPos}, t={i / subDivision}");
Debug.DrawRay(pointPos, (end.position - start.position).normalized);
builder.AddPoint(new Point(pointPos));
}
builder.AddPoint(new Point(end.position, locked: true));
for (int i = 0; i < subDivision; i++)
{
builder.ConnectPointsWithDesiredLength(i, i + 1, desiredLength: distBetweenRopePoints);
}
rope = builder.Build();
foreach (var point in rope.points)
{
GameObject ropeCollider = new GameObject("Rope Collider");
ropeCollider.transform.parent = ropeCollidersParent;
ropeCollider.layer = LayerMask.NameToLayer("Rope");
var colliderComponent = ropeCollider.AddComponent<CircleCollider2D>();
colliderComponent.radius = ropeRadius;
}
CreateOrderArray(); CreateOrderArray();
} }
private void Update() private void Update()
{ {
rope.points.First().position = start.position;
rope.points.Last().position = end.position;
Simulate(); Simulate();
// Update the rope collider positions
for (int i = 0; i < rope.points.Count; i++)
{
ropeCollidersParent.GetChild(i).position = rope.points[i].position;
}
// Handle static colliders
foreach (var point in rope.points)
{
if (point.locked) continue;
HandleStaticCollidersOfPoint(point);
}
// Constrain start transform based on overshoot
float overshoot = rope.CalculateLengthOvershoot();
if (overshoot > 0)
{
//start.position = prevStartPos;
Vector2 pullDirection = (rope.points.ElementAt(1).position - new Vector2(start.position.x, start.position.y)).normalized;
start.gameObject.GetComponent<Rigidbody2D>().AddForce(pullDirection * overshoot * pullForce);
}
prevStartPos = start.position;
} }
private void OnDrawGizmos() private void OnDrawGizmos()
@ -47,8 +137,8 @@ public class RopeSimulator : MonoBehaviour
foreach (var point in rope.points) foreach (var point in rope.points)
{ {
Debug.Log($"pos: {point.position}"); //Debug.Log($"pos: {point.position}");
Gizmos.DrawSphere(point.position, 1f); Gizmos.DrawSphere(point.position, ropeRadius);
} }
} }
@ -79,22 +169,60 @@ public class RopeSimulator : MonoBehaviour
Vector2 stickDir = (stick.A.position - stick.B.position).normalized; Vector2 stickDir = (stick.A.position - stick.B.position).normalized;
float length = (stick.A.position - stick.B.position).magnitude; float length = (stick.A.position - stick.B.position).magnitude;
if (length > stick.length || length > constrainStickMinLength) if (length > stick.desiredLength || constrainStickMinLength)
{ {
if (!stick.A.locked) if (!stick.A.locked)
{ {
stick.A.position = stickCentre + stickDir * stick.length / 2; TryMovePointToPosition(stick.A, stickCentre + stickDir * stick.desiredLength / 2);
} }
if (!stick.B.locked) if (!stick.B.locked)
{ {
stick.B.position = stickCentre - stickDir * stick.length / 2; TryMovePointToPosition(stick.B, stickCentre - stickDir * stick.desiredLength / 2);
} }
} }
} }
} }
} }
private void TryMovePointToPosition(Point point, Vector2 position)
{
Vector2 moveDir = position - point.position;
int stepsRequired = (int) Mathf.Ceil(moveDir.magnitude / collisionCheckDist);
moveDir.Normalize();
Vector2 initialPos = point.position;
for (int i = 0 ; i < stepsRequired; i++)
{
Vector2 newPos = Vector2.MoveTowards(point.position, position, collisionCheckDist);
point.position = newPos;
Collider2D collider = Physics2D.OverlapCircle(point.position, ropeRadius, staticColliderMask);
if (collider == null) continue;
// A static collider was met, dont move any further
Vector2 resolvedPos = collider.ClosestPoint(initialPos);
if (Vector2.Distance(initialPos, resolvedPos) < ignoreResolveThreshold) continue;
Vector2 penetrationDir = (resolvedPos - point.position).normalized;
Vector2 finalPos = resolvedPos - penetrationDir * ropeRadius;
//Debug.Log($"resolved pos: {point.position}->{finalPos}");
point.position = finalPos;
//point.prevPosition = finalPos;
break;
}
}
private void HandleStaticCollidersOfPoint(Point p)
{
Collider2D hitCollider = Physics2D.OverlapCircle(p.position, ropeRadius, staticColliderMask);
if (hitCollider == null) return;
Vector2 resolvedPos = hitCollider.ClosestPoint(p.position);
Vector2 penetrationDir = (resolvedPos - p.position).normalized;
Vector2 finalPos = resolvedPos - penetrationDir * ropeRadius;
p.position = finalPos;
}
void CreateOrderArray() void CreateOrderArray()
{ {
order = new int[rope.sticks.Count]; order = new int[rope.sticks.Count];

View File

@ -4,13 +4,20 @@ using UnityEngine;
public class Stick public class Stick
{ {
public Point A, B; public Point A, B;
public float length; public float desiredLength;
public bool dead; public bool dead;
public Stick(Point pointA, Point pointB) public Stick(Point pointA, Point pointB)
{ {
this.A = pointA; this.A = pointA;
this.B = pointB; this.B = pointB;
length = Vector2.Distance(pointA.position, pointB.position); desiredLength = Vector2.Distance(pointA.position, pointB.position);
}
public Stick(Point pointA, Point pointB, float desiredLenght)
{
this.A = pointA;
this.B = pointB;
this.desiredLength = desiredLenght;
} }
} }

View File

@ -11,8 +11,8 @@ TagManager:
- -
- Water - Water
- UI - UI
- - Rope
- - Player
- -
- -
- -