1

I'm putting together a p5 sketch with little wiggling snakes that move randomly across the screen.

Unfortunately, the tail keeps catching up to the head every time it does a sharpish turn.

Here is the function I'm using to calculate the move, I've tried with a few different ways of calculating the speed, fixed numbers, relative to the snake's length.

It's supposed to work by moving the snakes head (points[3]) in a semi-random direction and then having each body point move towards the one before it by the same amount. This isn't working, and I feel there's something wrong with my algorithm itself. I'm not familiar with these kinds of intermediate random-walks, so I've just been going by guesswork for the most part.

this["moveCurve"] = function() {
    let newDir = this["oldDir"] + (random() - 1/2)*PI/6; 
    let velocity =  createVector(1,0); 
    velocity.setMag(5); 
    velocity.setHeading(newDir); 
    this["points"][3].add(velocity); 
    for (let i = 2; i >= 0; i--) { 
        this["points"][i].add(p5.Vector.sub(this["points"][i + 1],this["points"][i]).setMag(5)); 
    } 
    this["oldDir"] = newDir; 
}

If you have any idea what I could do to make this work properly, I'd love to hear your advice. Thanks!

  • I just thought I'd share this for inspirational purposes: https://openprocessing.org/sketch/1182306 (warning: does not perform well without hardware acceleration enabled). – Paul Wheeler Aug 06 '21 at 20:26

2 Answers2

2

This does look like an algorithmic issue / not a bug with how you implemented it.

Here's my go at explaining why the gap between two points must decrease in this algorithm:

Let's consider just a two point snake, with two points Hi (head) and Ti (tail) at an initial locations Hi: (20, 0), and Ti: (0, 0). So, the heading here is 0 radians.

What happens when moveCurve is called? A new heading is chosen (let's use PI/2, a right angle to make it easy to imagine) and using a fixed velocity of 5 we calculate a new position for the head of (20, 5), let's call it Hf. T also moves, but it also moves toward Hf at the same 5 unit velocity, ending up at about (4.85, 1.21). The distance between these two final positions is now 15.62657, which is smaller than the initial distance.

To visualize this, think of the triangle formed between Ti, Hi, and Hf. Ti, and Hi, form the base of this triangle. Ti will move along the hypotenuse to get to Tf, while Hi will move along the other side. The directions they are moving in form an angle which is smaller than PI radians and both points are moving at the same speed so intuitively the points must be getting closer together.

So how to solve this? Well if we consider our tiny snake's movement, the tail moved in a decent direction but too far. One solution might be to scale the velocity vector in order to maintain a fixed distance between points instead of using a fixed velocity. For example instead of stepping 5 units along the hypotenuse from Ti toward Hf in the example, you could step 20 units along the hypotenuse from Hf toward Ti. I'm not sure how this would work out for your snake, just an idea!

Keep slithering!

  • I think I get your suggestion, but did you mean that Hf should move by 20, not Ti? – TheG_Ghaladron Aug 06 '21 at 04:38
  • I accidentally had a stray 20 in the sentence I think you are referring to. Basically, I was suggesting that Tf could be computed as followed: Let D = a unit vector pointing from Ti to Hf. Then, Tf = Hf - D * 20 – kyle-marshall Aug 06 '21 at 05:10
  • I'm still not quite sure how that would help, in my visualisation that would make the tail even faster. I found an answer anyway based on an example on the p5 docs. – TheG_Ghaladron Aug 06 '21 at 11:43
1

Fortunately, it turns out p5's documentation itself had the answer for me. By adapting the code from here to use p5 Vectors, I was able to get it all working.

The segLengths property is defined when the object is made, just takes the distances between all the points.

this["moveCurve"] = function() {
    let newDir = this["oldDir"] + (random() - 1/2)*PI/6;
    let velocity = p5.Vector.fromAngle(newDir).setMag(5);
    this["points"][3].add(velocity);
    for (let i = 2; i >= 0; i--) {
      this["points"][i].set(p5.Vector.sub(this["points"][i+1], p5.Vector.fromAngle(p5.Vector.sub(this["points"][i+1],this["points"][i]).heading()).setMag(this["segLengths"][i])));
    }
    this["oldDir"] = newDir;
  }

I might spend a little time trying to clean up the code a bit, it's a jot messy for my tastes at the moment. But it works.