-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Verlet Rope in Games #8
Comments
Hello, I try to add each node has a radius. Now your code just push away particle don't take in account node radius, just it position. I implement this but have jittéring issue now and don't know how to solve it, can we communicate in any place, where I can send you code snippets of media examples of problem? Also I do in 3D :) |
Hey, this is the best place to contact as others can benefit from the conversation as well. You'll have to elaborate a bit more on what you've done and what's happening. When you say radius, are you referring to giving the rope a custom "thickness" value, and adjusting collisions so that they still push that rope out along the thickness? The main complication here IIRC is making sure you get the calculation right for both checking if a node is inside a collider, and for how far it should move out of the collider. This actually gets pretty tricky if you're calculating box collisions in local space, because you need to convert the rope thickness/node radius to local space as well to check the collision, and then convert the value back to get the correct world-space distance you need to move the node. Feel free to post images and code right here. |
Thanks for answer here's example of Sphere Case Collision Handling:
Also my fixed update loop(I know that you are using custom update loop, but for simplicity I choose this variant for now):
Update Positions Method:
Adjust Distances Method:
|
Also what UTC +... you have?) I mean, that when we can be online at same time?) I have UTC+3 |
I can't see anything obviously wrong with your implementation. You have the right idea with the Since you don't seem to be using the jobs version, you should be able to trivially debug this further by stepping through one of the problematic nodes/particles in a debugger:
Try running with Also, how zoomed in is the gif? It's unlikely, but you could be seeing floating point errors. |
Thanks for answer!
About debuging:
Also, I noticed, that if I set gravity to 0, then rope still colliding,but has no jittering at all! Also if comment Adjust Distances method and try set gravity negative and place Collidable Sphere under place of spawn rope. Then rope nice and smoothly fall down along sphere normals without jittering :/ If you need examples (video,gif os images) about what I talking I send it as soon as possible to you) |
Toqozz, Hello! Did you get my messages? |
Please don't bump, I'm just busy! I added thickness for circles to my rope sim again (https://gist.github.com/Toqozz/23726eae4b39c0f7e314a83987f9a911) -- just added line 71, and changed line 327 -- and was unable to observe the results you're getting. I tried changing iterations, node distance, step time, and a few other things and couldn't get an unstable result. ee.mp4My best advice here is to take the gist I've posted above and work backwards towards your solution until the problem re-appears, then you can pinpoint where the issue actually is. |
Thanks, I will try do in in this way and write back then found a place where I will get an error |
Toqozz, I finally figured out what's wrong with my code! I used Thanks for your help! Also if we are here, can I ask you for check this code, which handles
And one more question, do you know how to prevent rope from tunneling? I have troubles now with very thick planes/cubes. I saw method named |
Great work! Regarding collision thickness + box collisions, I never got it fully working, but I have something close: https://gist.github.com/Toqozz/ea6f4414a1298a8b309e5ddb94317d60 It has an issue with nodes "sticking" to the surface which seems to be related to float errors (probably because of transforming to box local space and back?), but hopefully it is helpful regardless. I think it could be made to work by just rotating and translating the point, to box local space, so you don't have to deal with the scale (and so, avoiding a matrix multiply). Here's a quick draft on how that might look (completely untested): // The collision data is different in blog and your post, I know. This is just easiest to write.
if (node.collisionBuffer[j] is BoxCollider) {
BoxCollider col = node.collisionBuffer[j] as BoxCollider;
// Translate to box origin.
Vector3 pos = node.position - col.transform.position;
// Rotate pos by the same rotation as box.
// Note this actually seems to be doing `rot * pos * rot^-1` internally by unity: https://forum.unity.com/threads/rotate-vector-by-quaternion.21687/#post-6587407
pos = col.transform.rotation * pos;
// `pos` is now in local space, except for the scale.
// Not respecting hierarchy. Use lossyScale, or if that has float issues just calculate the scale yourself by multiplying parent transforms: https://gabormakesgames.com/blog_transforms_transforms.html
Vector3 scale = col.transform.localScale;
// Much faster ways to process and early out here, see blog post.
Vector3 half = scale * 0.5f;
if pos.x > half.x + skinWidth {
pos.x = half.x + skinWidth;
} else if pos.x < -half.x - skinWidth {
pos.x = -half.x - skinWidth
}
if pos.y > half.y + skinWidth {
pos.y = half.y + skinWidth;
} else if pos.y < -half.y - skinWidth {
pos.y = -half.y - skinWidth;
}
if pos.z > half.z + skinWidth {
pos.z = half.z + skinWidth;
} else if pos.z < -half.z - skinWidth {
pos.z = -half.z - skinWidth;
}
// Transform back.
pos = col.transform.rotation.inverse() * pos;
pos += col.transform.position;
node.position = pos;
} Hopefully that's enough vague information to translate it into your solution. Regarding tunneling, there's a section at the very end of the blog post talking about it, and I've also written a couple comments about it that still sum up my thoughts:
|
Thanks for your answer! I already somehow make this solution and it's works, but I definitely look into your solution,cause I have to many
|
Mesh isn't big problem if I know orientation of particle, Then I just can take node position and normal vector and offset vertices by normal rotated around forward vector for deltaAngle = 360/resolution |
I can't stop thinking about it) If I some how can make that rope can attach object to itself, then when,for simplicity, box collider collides with any other collider I need to also calculate torque force and impact force to influence on rope... OMG... Unity joints by default have this features, but implementing it from scratch is sound crazy... |
Also I thought that worked... But on angle like 45,135,225 and etc. its convert nodeSizeLocal not as at 0,90,180,270,360 angles. It take in account sin,cos or something, that make nodeRadius to be not (0.1,0.1,0.1) but to be (0.1,0.1,0.05). Hm... trouble.. |
I'm having some trouble understanding you, but I'll try answer as best I can.
Since the rope is a cylinder, you should be able to calculate normals from the forward vector by just grabbing any other perpendicular vector: // Lazy implementation to find some other perpendicular vector: https://stackoverflow.com/a/38407105/5628824
Vector3 somePerpendicular(Vector3 input) {
if (Mathf.Abs(input.z) < Mathf.Abs(input.x) {
return new Vector3(input.y, -input.x, 0);
} else {
return new Vector3(0, -input.z, input.y);
}
}
Vector3 fwd = (nodes[i].position - nodes[i-1].position).normalized;
Vector3 perp = somePerpendicular(fwd);
Vector3 cross = Vector3.Cross(fwd, perp);
// You now have 3 axes, but you probably only need 2 anyway since the rope is a cylinder. By the way, if you're just using transforms you can simply do
Yes, swinging with character input is deceptively difficult with a rope like this. There are 2 main options that I've come up with:
If you want full physics then yeah, this solution probably isn't for you. This rope works best when you can control the interactions. |
Merged some multi-comments into one to make things more readable. |
Hi, im a beginner... I try to read and understand how to implement simple rope using verlet method..But the problem is when i try to add gravity and run the rope simulation my rope is stuck at 90 degrees in the middle and it cannot swing.. please help.. Thank you |
Sorry for the late reply. The simulation as per the blog post already has gravity. What part are you struggling with? |
@Toqozz, hi! Thank you very much for code samples, your collision handling approach is very useful. But there are solutions only for two types of colliders - CircleCollider2D and BoxCollider2D. Do you have any ideas about collision handling with PolygonCollider2D? I think you can use Collider2D.OverlapPoint to detect collisions and Collider2D.ClosestPoint to determine the final position, but this will probably have a bad effect on the performance of the code. |
I actually do have some basic polygon collision code that was working, but I never ended up optimizing it properly and I don't promise that it's correct. It should be a great starting point though. Please let me know if I'm missing anything there, or if you need any more information, I posted this kind of hastily. Here are the relevant parts: AdjustCollisions (RopeJob.cs): ...
public struct RopeJob : IJob {
...
[ReadOnly] public NativeList<float2> polygonPaths;
[ReadOnly] public NativeList<int> polygonPathsNumPoints;
...
private void AdjustCollisions() {
// Loop through each collider.
for (int i = 0; i < numCollisions; i++) {
NodeCollisionInfo nc = collisionInfos[i];
// Looping inside the switch statement is marginally faster than the other way around.
switch (nc.colliderType) {
...
case ColliderType.Polygon: {
for (int j = 0; j < nc.numCollisions; j++) {
VerletNode node = nodes[collidingNodes[i * nodes.Length + j]];
int indexStart = 0;
for (int k = 0; k < polygonPathsNumPoints.Length; k++) {
int numPoints = polygonPathsNumPoints[k];
NativeSlice<float2> path = new NativeSlice<float2>(polygonPaths, indexStart, numPoints);
if (BurstUtility.PointInPolygon(path, node.position)) {
// If we're inside the polygon, then push it out to the edge.
node.position = BurstUtility.ClosestPointOnPolygonEdge(path, node.position);
}
indexStart += numPoints;
}
nodes[collidingNodes[i * nodes.Length + j]] = node;
}
} break;
}
}
} // http://alienryderflex.com/polygon/
/// <summary>
/// Determine if a point lies within a given polygon.
/// <param name="corners">Polygon vertices / corners.</param>
/// <param name="y">The point to determine.</param>
/// <returns>True if the point lies within the polygon, false if not.</returns>
public static bool PointInPolygon(NativeSlice<float2> corners, float2 point) {
int j = corners.Length-1;
bool oddNodes = false;
for (int i = 0; i < corners.Length; i++) {
if (corners[i].y < point.y && corners[j].y >= point.y ||
corners[j].y < point.y && corners[i].y >= point.y &&
(corners[i].x <= point.x || corners[j].x <= point.x)) {
oddNodes ^= (corners[i].x + (point.y - corners[i].y) / (corners[j].y - corners[i].y) * (corners[j].x - corners[i].x) < point.x);
}
j = i;
}
return oddNodes;
}
/// <summary>
/// Push `point` to the closest edge of a polygon.
/// </summary>
/// <param name="corners">Polygon vertices / corners.</param>
/// <param name="point">The point to push against the polygon edge.</param>
/// <returns>`point` constrained to the edge of the polygon.</returns>
public static float2 ClosestPointOnPolygonEdge(NativeSlice<float2> corners, float2 point) {
// Find closest edge.
float2 projectionPoint = float2.zero;
float closestDistance = float.PositiveInfinity;
for (int i = 0; i < corners.Length; i++) {
float2 p1 = corners[i];
float2 p2;
// The last point needs to wrap around to the first point.
if (i == corners.Length - 1) {
p2 = corners[0];
} else {
p2 = corners[i + 1];
}
// Find the minimum distance projection of our node onto the polygon edge.
float2 projection = ClosestPointOnLine(p1, p2, point);
float distance = math.distance(projection, point);
if (distance < closestDistance) {
closestDistance = distance;
projectionPoint = projection;
}
}
return projectionPoint;
}
/// <summary>
/// Given a point and a line, find the closest point which lies on the line segment in relation to the original point.
/// </summary>
/// <param name="v1">Line segment vertex 1.</param>
/// <param name="v2">Line segment vertex 2.</param>
/// <param name="p">The point.</param>
/// <returns>The closest point on the line.</returns>
public static float2 ClosestPointOnLine(float2 v1, float2 v2, float2 p) {
float l2 = math.distancesq(v1, v2);
// v1 == v2 case.
if (l2 == 0f) {
return v1; //math.distance(p, v1);
}
// Consider the line extending the segment, parameterized as v1 + t (v2 - v1).
// We find projection of point p onto the line.
// It falls where t = [(p-v1) . (v2-v1)] / |v2-v1|^2
// We clamp t from [0,1] to handle points outside of the segment v1v2.
float t = math.clamp(math.dot(p - v1, v2 - v1) / l2, 0f, 1f);
float2 projection = v1 + t * (v2 - v1); // Projection falls on the segment.
return projection;
} Setup (Rope.cs) ...
private NativeList<float2> polygonPaths;
private NativeList<int> polygonPathsNumPoints;
...
private void Setup() {
...
polygonPaths = new NativeList<float2>(32, Allocator.Persistent);
polygonPathsNumPoints = new NativeList<int>(8, Allocator.Persistent);
...
} Snapshot Collision (Rope.cs): private void SnapshotCollision() {
// Clear polygon collider buffers.
// This just sets the length to 0, no actual work is done, so we're good :).
polygonPaths.Clear();
polygonPathsNumPoints.Clear();
// Update the colliders in range of each node.
// A good optimization here would be to only run on the first fixed update.
numCollisions = 0;
for (int i = 0; i < nodes.Length; i++) {
int collisions =
Physics2D.OverlapCircleNonAlloc(nodes[i].position, COLLISION_RADIUS, colliderBuffer, filter.layerMask);
for (int j = 0; j < collisions; j++) {
// If we couldn't find the collider in the array, then it's new, and we need to set stuff up.
if (idx < 0) {
..
switch (col) {
...
case PolygonCollider2D p:
nc.colliderType = ColliderType.Polygon;
// Push all paths of this polygon onto our collision list.
for (int k = 0; k < p.pathCount; k++) {
// This generates garbage :(.
Vector2[] path = p.GetPath(k);
polygonPathsNumPoints.Add(path.Length);
for (int w = 0; w < path.Length; w++) {
polygonPaths.Add((Vector2)p.transform.TransformPoint(path[w]));
}
}
break;
...
} |
Does ClosestPointOnLine function looks like this? Or is there some specificity? I didn't find the implementation in your comment.
|
Ahh! Sorry about that. I've updated the post to include that function. |
The code does not match the formula - you forget the squaring) There should be |
Isn't |
Hmm, yeah, you're right, sorry. I was confused by the documentation of the math package: v 1.2: Anyway, are you using mathematics package and native containers because of the burst compiler? Or are they faster than similar built-in methods in all situations? |
A bit of both. You have to use them to use Burst, but also they give much more control over allocations which means you should be able to do better with them as well. I believe they also try to make more guarantees about memory layout, so should be faster for general usage as well. The main performance boost is really from using Burst itself: https://www.jacksondunstan.com/articles/5211
|
Hi, how to fix the stick constraint of verlet the will not stretch and
sag?thank you
|
If you're asking how to make the rope stiffer, the general advice is to increase number of iterations and decrease number of nodes as much as possible. I'm not entirely sure what you mean because a rope will (and should!) always sag in the middle, otherwise it's not really a rope. If you're asking how to stop/limit the rope from stretching past its limit, you probably have to get a bit creative with your usage code and how you handle moving the rope. An option here is manually measuring how long the rope should be and then restricting mouse movement past that length. |
Hi, i have a problem of self collision using verlet position based without
storing velocity. how can i fix this?
…On Thu, Dec 30, 2021, 9:13 PM Michael Palmos ***@***.***> wrote:
Hi, how to fix the stick constraint of verlet the will not stretch and
sag?thank you
If you're asking how to make the rope stiffer, the general advice is to
increase number of iterations and decrease number of nodes as much as
possible. I'm not entirely sure what you mean because a rope will (and
should!) always sag in the middle, otherwise it's not really a rope.
If you're asking how to stop/limit the rope from stretching past its
limit, you probably have to get a bit creative with your usage code and how
you handle moving the rope. An option here is manually measuring how long
the rope *should* be and then restricting mouse movement past that length.
—
Reply to this email directly, view it on GitHub
<#8 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AVLVWCZHWFKJYPHD24GBU23UTQ5FVANCNFSM4YIDLHBA>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
You are receiving this because you commented.Message ID:
***@***.***>
|
Hi, i'm implementing rope for my game. and i found this great article, i have a question is there any way to hang an object on the rope and cut the rope? |
Doing both of these should be pretty easy. You can "hang" an object just by making it follow the end position, but you probably want to give it some weight too, which you can achieve by applying some multiplier to make it move less at the integration step. To "cut" the rope, you'd probably want to just spawn another rope at the same initial position and copy the prev values over to give it the same initial velocity. Don't forget to also remove the nodes from the initial rope. Detecting a cut depends on how you're going to be doing it, but you'll probably have to iterate over every node and check some condition -- e.g. find the closest node to the scissors. |
Yes thank you, I thought so too at first but I wonder if there is any other way?. I'll probably implement it first then think about it later. |
Hey, it's an amazing blog post. I am making a game and I want to make the rope not stretch after some length but I couldn't figure out the solution.
but it always ended up being messed up because I am not a very good programmer. Is there any way to implement this? |
To elaborate on the solution I was describing there: Basically, measure the current length of the rope by adding the distance of each node together. If the current length is >= the desired length, don't let whatever is stretching the rope stretch anymore. It's an OK solution but doesn't really feel very robust, does it? Looking back on this now you might be better off just running a final pass over the rope after the simulation finishes:
I'm not sure how well it will work in practice, but there's the two options I see for implementing it. |
Just came across this today: https://www.gamedeveloper.com/programming/the-secrets-of-cloth-simulation-in-i-alan-wake-i- Seems like in industry a few techniques are used to counteract the stretchiness of verlet things and make it a bit more robust. |
Hi Toqozz, first of, great work. Really helped me a lot in my approach of implementing an Cable in VR. My Question is do you know if its possible to implement additional Rotation on Start and Endpoint of the Cable? Like if i have the StartPoint in my Hand and rotate the Controller, the Cable should bend accordingly since for now it only calculates the Position and Rotation, as if its hanging from an fixed Point. Do you perhaps have an Idea or per chance already tried that approach? From what ive heard, it messes with the Physic Calculation of the Verlet approach itself but i just want to make sure. Thanks in advance. |
Thanks! I haven't touched on this all that much, so just ideas here. In more advanced verlet simulations (typically cloth sims) they have more types of constraints than just the one I used in the post, which is the simplest type of constraint. I think the main ones are called shear, stretch, and bend constraints (this post seems to have a pretty good explanation -- not much on the implementation though). I imagine a bend constraint is probably pretty useful for you in the context of a cable in VR, but maybe not enough on its own to accomplish something realistic. To make the rope turn, I think that some kind of rotation constraint which tries to align the orientation of adjacent nodes working. I can see this being really easy or pretty challenging; not sure. Then you probably want to give the rope some rigidity with a bend constraint, which should look pretty good. I don't see any reason you wouldn't be able to make it work. You should be able to get most of the behavioural change you'd want through a series of constraints applied to the nodes. Sorry if I misunderstood your question. I have a feeling all you're asking for is actually the bend constraint. |
Thanks for your response. |
Discuss the blog post here; share your thoughts, improvements, or any issues you run into so that others can find them.
The text was updated successfully, but these errors were encountered: