rope physics
This commit is contained in:
parent
ab098c3370
commit
d781aace61
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -11,8 +11,8 @@ TagManager:
|
||||||
-
|
-
|
||||||
- Water
|
- Water
|
||||||
- UI
|
- UI
|
||||||
-
|
- Rope
|
||||||
-
|
- Player
|
||||||
-
|
-
|
||||||
-
|
-
|
||||||
-
|
-
|
||||||
|
|
Loading…
Reference in New Issue