Last post talked about the random thoughts I’ve had about how to pull off the rope dynamics in Umihara Kawase, this classic SNES game that drives us all mad.
I made some bold suggestions, and decided to try them. After a couple hours of work, while not as complete as I want, it’s far enough along to be able to demonstrate to me if I have a right idea or not.
Rather than let this fun experiment disappear in to obscurity through e-mail or forums, and since I never have enough blog content, I’ll post it here and try to explain more broadly what’s going on in my test. Though really, this is just Raigan and I talking back and forth.
- ESC – Exit
- TAB – Reset
- SPACE – Pause
The point of this application is to simulate a stiff wrapping Umihara Kawase rope, not a nice bouncy spring built one.
Actually at this point, it doesn’t attempt to wrap around scenery. The rope is capable of it, but I’m just manually hard coding a list of points in between the end points. I’m assuming there’s will be a system that looks at the collision, notes the edges I cross, and correctly pushes them on to the front or back of an STL deque. So … uh … for now, pretend they’re pulleys, or fitting through some really fine cracks.
What you’re seeing on screen is 4 separate rope simulations. I’ve noted on the image in a 2×2 grid what is what. “Magnitude” means I use Magnitude (length = sqrt(x*x+y*y)) for all my length calculations, and “Manhattan” means I use Manhattan (length = abs(x)+abs(y)) for them. The “Free” row means both points move freely, and “locked” means one doesn’t.
If you’re just tuning in, Manhattan is correct when axis aligned (i.e. [10,0], [0,-4], etc), but increasingly more wrong (too long) as it approaches a 45 degree angle. It doesn’t break, it’s just different, very diamond shaped instead of circular. Think of it as a worst case square root approximation for vectors that still works.
To solve the rope, I need a few things. The sum of the length all rope segments, which is one place where I’m using a Magnitude/Manhattan, one for every segment. I take the difference between my set length and my calculated length to see how “broken” it is, much like a spring. I then pull or contract my end points to fix it. It’s a cheap verlet solver, so all I need to do is move the point and it will work out beautifully in a few frames. So I take the vector from end to point for each side, divide it by the length of that segment (My other Magnitude/Manhattan use, one per side), which gives me a normalized vector I can use to apply part of the rope length difference. And I subtract this from each side.
So yeah, unfortunately, I did have to use two divides, one for each side. If your target never moves, you *could* get away with 1 divide (but what’s the fun in that?).
But yeah this is a quick demonstration to see how each method works. Each test case is given the exact same data.
So, my thoughts (UmiRope_Riggid.exe).
Because the error in the Manhattan versus Magnitude, the rope is shorter. The rope being shorter to me isn’t that big of a deal, and this is why I initially didn’t see Manhattan being a problem. It’s a really cheap way to calculate an approximate total length of the rope, if it has many segments.
However, Manhattan has it’s real notable flaw in calculating the new positions of the ends. In my opinion, it’s not terrible, it’s just not very natural looking. A cheaper than a “real” square root approximation would still be ideal here.
Is this Umi-like though? Not quite yet. The Umihara Kawase rope is somehow very elastic, erratic, bouncy, where this is very rigid. Alright, more bouncy then.
I included a variant that’s more bouncy, by using a 20% of the solve amount (UmiRope_Bouncy.exe).
While reacting differently, I don’t think it’s an improvement. After it calms itself down, it looks more or less the same an the rigid example.
Hmm… I am going to have to take a look at the game again. It might be possible to introduce another error that makes the Manhattan look more sproingy.
But generally speaking, this approach to solving the rope is cheap. The only bottlenecks being a division per end, two multiplications per end to scale the vector in the normalization step, and two more to scale the normalized vector up by each side’s part of the difference, and everything else can boil down to adds, subtracts, and shifts.
And if division is a problem, it can be replaced by indexing in to a table of reciprocals (1/1, 1/2, 1/3, 1/4, …), and multiplying by this reciprocal in the place of the division. So 5 multiplications plus a bunch of adds/subtracts/shifts, per side.
At least, to solve the rope.
Everything else I think is just some good “hopefully simple” logic to figure out what points to add to your list (deque). I’ll toy with that later.
– – –
Source is included. The notable functions are, all in main.cpp:
- cParticle::Step() – which is how we move each end of the rope. The multiply there is a friction hack, removing it makes coming to rest take more time.
- cRope::CalcRopeLength() – which based on it’s contents, steps through every notable point to calculate the sum of all line segments.
- cRope::StepRope() – which solves the rope. The first multiply on each line is the important one, which is a vector by a scalar. The 2nd or 3rd on those lines were just to make it more flexible. They could be replaced by a shift.
cManhattanRope::CalcRopeLength() and cManhattanRope::StepRope() are the variants needed to use Manhattan instead of Magnitude (i.e. Normalize). You can see the divide here, which is normally hidden in the normalize function.
Steps are called in main, lower in the file.