Using sin + cos to imitate a snake (help)

Hi all,
I am trying to use the sin function in order to create the motion of a snake. See attached video for an example snake.ogv.zip (81.9 KB)

As the snake moves following the pointer I would like it to move on the x+y axis a certain amount to imitate this snake-like movement. If the snake moved only on the x axis it would be easy because all I would have to do is call the sin function with a ofGetFrameNum() and have it return a number between -1 and +1. Same for if it moved only on the y axis. But what about cases where the snake is moving diagonally? In that case what is the amount of sin/cos I should add on the x and y axis respectively?

thanks :smile:

Here is my code:

void sceneShortMovie::draw()
{
    ofPushStyle();
    ofBackground(210,124,111);

    dragSegment(0, posX, posY);

    for(int i=0; i<20-1; i++)
    {
        dragSegment(i+1, x[i], y[i]);
    }

    filmStrip.setAnchorPercent(0.5, 1);
    filmStrip.draw(hatCurrLoc+mustacheCurrLoc, mustacheW, mustacheH);

    hatImg.draw(hatCurrLoc);

    ofPopStyle();
}
//----------------------------------------------------------
void sceneShortMovie::dragSegment(int i, float xin, float yin)
{

  float dx = xin - x[i];
  float dy = yin - y[i];

  float angle = atan2(dy, dx);
  x[i] = xin - cos(angle) * segLength;
  y[i] = yin - sin(angle) * segLength;
  if (i==0) segment(x[i], y[i], ofRadToDeg(angle), true);
  else segment(x[i], y[i], ofRadToDeg(angle), false);
}
//-------------------------------------------------
void sceneShortMovie::segment(float x, float y, float a, bool isFirst)
{
  ofPushMatrix();
  ofTranslate(x, y);
  ofRotate(a);
  filmStrip.setAnchorPercent(0.5, 0.5);
  ofLine(0,0,segLength, 0);
  ofPopMatrix();
}

Hey @marinero, sounds like a cool effect.

In order to displace your snake, what you want is something called a normal vector. If you have a point along a curve, the normal vector is perpendicular to the curve at that point. It makes much more sense visually, so here are a bunch of normal vectors drawn at various points along curves:

That normal vector will tell you how much x-displacement and how much y-displacement you need to apply to get your sinewave. You can calculate a normal vector yourself, but the ofPolyline class calculates normals for you. Below is some code that 1) starts with a straight line, 2) rotates the line to point in a direction based on the mouse position and 3) displaces the points along the rotated line so that it forms a sinewave. That last part makes use of the normals.

It will give you something like this:

Hope this helps. Googling “normal vector” might help if you are feeling lost.

ofPolyline polyline;
ofPolyline rotatedPolyline;
ofPolyline displacedPolyline;

void testApp::setup(){
    // Create a polyline where (0,0) is the center of the line
    // This makes rotation operations simple
    for (int i=-150; i<=150; i+=10) polyline.addVertex(ofVec2f(i, 0));

    // Copy the polyline over
    rotatedPolyline = polyline;
    displacedPolyline = polyline;
}

void testApp::draw(){
    ofBackground(0);

    ofSetColor(255);

    ofPushMatrix();
        ofTranslate(ofGetWidth()*1.0/4.0, ofGetHeight()/2);
        polyline.draw();
    ofPopMatrix();

    ofPushMatrix();
        ofTranslate(ofGetWidth()*2.0/4.0, ofGetHeight()/2);
        rotatedPolyline.draw();
    ofPopMatrix();

    ofPushMatrix();
        ofTranslate(ofGetWidth()*3.0/4.0, ofGetHeight()/2);
        displacedPolyline.draw();
    ofPopMatrix();
}


//--------------------------------------------------------------
void testApp::update(){

    // Use the mouse x position to control the orientation of the polyline
    float polylineRotation = ofMap(mouseX, 0, ofGetWidth(), 0, PI);

    // Use the elapsed time to determine the x-axis position on the sin curve
    // of the first point in the polyline
    // The rest of the points in the polyline will then be defined relative
    // the first point's position
    float speed = TWO_PI / 0.5; // time = seconds * radians/second
    float startingDisplacementAngle = ofGetElapsedTimef() * speed;

    for (int i=0; i<polyline.size(); i++) {

        // Take the original polyline's point and rotate it around (0,0), the origin
        //  We could have also used ofVec3f's rotate function
        float a = polylineRotation;
        ofVec3f rotatedPoint(0.0, 0.0, 0.0);
        rotatedPoint.x = polyline[i].x * cos(a) - polyline[i].y * sin(a);
        rotatedPoint.y = polyline[i].y * cos(a) + polyline[i].x * sin(a);
        rotatedPolyline[i] = rotatedPoint;

        // Now we take that rotated polyline and use it's normal to displace the points
        // along the line according to a sinewave
        // The angleOffset describes where point i lies along the sinewave compared
        // to point 0. By multiplying by TWO_PI, we are saying that the last point
        // in polyline is 360 degrees farther along the sinewave than the first point. Or
        // in other words, it is a full cycle ahead of the first point.
        ofVec3f rotatedNormal = rotatedPolyline.getNormalAtIndex(i); 
        float angleOffset = (float)i/(float)polyline.size() * TWO_PI;
        float scale = 50.0 * sin(startingDisplacementAngle + angleOffset);
        displacedPolyline[i] = rotatedPoint + (scale * rotatedNormal);
    }
}
1 Like

thank you for your detailed anwer @mikewesthad. Pics, code and links. What more can a lost coder ask for? :smile:
much appreciated
marinero