Tangent and Normals to curves

This is my first pass at plotting tangents and normals.

Starting with a sine wave and will modify to work with any curves focusing on mathematical plots.

I have yet to deal with length of tangent and length of normal; just using a an “offset” for now and lengths will vary depending on slope.

Suggestions welcome.
I prefer hints and tips to a “canned” solution with complete code.
I do not want to use a library but may want to glean code for insight.

float theta = 0;

int cycles = 4;            // # cycles to display 
float x = 0, y = 0;
float w = width; 
float xScale;              //used to scale across x axis; only used for plotting! 
float yScale;              //these will distort tangents and normals if no the same

float m1, m2;
float offset;
float amp;
float x1, y1, xa, ya, xb, yb;

void setup() 
  {
  size(1280, 1024, P3D);
  background(0);
//  ortho();
  }
 
void draw() 
  {
  background(0);
  translate(0, height/2);

  stroke(64);
  strokeWeight(2);
  line(0, 0, width, 0);
      
  xScale = 100;
  yScale = 100; 
  amp = 3;
 

  // Sine wave plot  
  for (x = 0; x <= cycles*TWO_PI; x+=TWO_PI/width)     
    { 
    stroke(255, 0, 0);
    y = sin(x);
    point(xScale*x, yScale*y);
    }
 
  for (x = 0; x <= cycles*TWO_PI; x+=TAU/16)  // Try /8 /16 etc. here
      { 
      x1 = x;
      y1 = sin(x);
      offset = PI/32;

// Tangent ******************************************************************     

      m1 = cos(x);
// First point
      xa = x + offset;
      ya = m1*(xa-x1) + y1;
// Second point
      xb = x - offset;
      yb = m1*(xb-x1) + y1;

      stroke(0, 255, 0);     
      line(xScale*xa, yScale*ya, xScale*xb, yScale*yb);     
 
// Normal *******************************************************************

     m2 = -1/m1;
// First point
      xa = x + offset;
      ya = m2*(xa-x1) + y1;
// Second point
      xb = x - offset;
      yb = m2*(xb-x1) + y1;             
      
      stroke(255, 255, 0);          
      line(xScale*xa, yScale*ya, xScale*xb, yScale*yb);  
      }        
    }


3 Likes

Updated:

// Normal *******************************************************************
     m2 = -1/m1;
// First point
      xa = x + cos(atan(m2))*offset;
      ya = m2*(xa-x1) + y1;
// Second point
      xb = x - cos(atan(m2))*offset;
      yb = m2*(xb-x1) + y1;

:slight_smile:

1 Like

Getting there…

I have the code modulating through cycles to help visualize; similar can be done with mouse (and some code) or tweak mode in Processing.

float cycles;          // # cycles to display 
float x = 0, y = 0;
float w = width; 
float xS;              //Scale for x axis for plotting
float yS;              //Scale for y axis for plotting; distorts tangents and normals if not the same as xS

float m1, m2;
float offset;
float amp;
float x1, y1, xa, ya, xb, yb;
float theta;

void setup() 
  {
  size(1280, 1024, P2D);
  background(0);
  }
 
void draw() 
  {
  background(0);
  translate(0, height/2);

  stroke(64);
  strokeWeight(2);
  line(0, 0, width, 0);
      
  theta += TAU/500;

  //cycles = 3;
  cycles = 1*sin(theta) + 2; // Modulates cycles from 1 to 3
  
  xS = 200/cycles;
  yS = 200/cycles; 
  amp = 1*cycles;
 
  float den = 3*TAU;
  
  // Sine wave plot  
  for (x = 0; x <= cycles*TWO_PI; x+=TAU/den)     
    { 
    stroke(255, 0, 0);
    y = amp*sin(x);
    point(xS*x, yS*y);
    }
 
  for (x = 0; x <= cycles*TWO_PI; x+=TAU/den)  // Try /8 /16 etc. here
      { 
      x1 = x;
      y1 = amp*sin(x);
      offset = PI/15;

// Tangent ******************************************************************     

      m1 = amp*cos(x);
// First point
//      xa = x + offset;
      xa = x + cos(atan(m1))*offset;
      ya = m1*(xa-x1) + y1;
// Second point
//      xb = x - offset;
      xb = x - cos(atan(m1))*offset;
      yb = m1*(xb-x1) + y1;
      
//      x = x;
      y = amp*sin(x);

      stroke(0, 255, 0);     
      line(xS*xa, yS*ya, xS*x, yS*y); 
      line(xS*x, yS*y, xS*xb, yS*yb);
      //line(xS*xa, yS*ya, xS*xb, yS*yb);
 
// Normal *******************************************************************

     m2 = -1/m1;
// First point
      xa = x + cos(atan(m2))*offset;
      ya = m2*(xa-x1) + y1;
// Second point
      xb = x - cos(atan(m2))*offset;
      yb = m2*(xb-x1) + y1;

//      x = x;
      y = amp*sin(x);
      
      stroke(255, 255, 0);
      line(xS*x, yS*y, xS*xa, yS*ya); 
      line(xS*x, yS*y, xS*xb, yS*yb); 
      //line(xS*xa, yS*ya, xS*xb, yS*yb);  
      }        
    }

I may have to move to Gallery…

:slight_smile:

3 Likes

First of all I suggest that you investigate the PVector class since it can store

  • a position on the curve
  • the tangent at a position on the curve
  • the normal at a position on the curve

as single entities and it will reduce the code complexity.

Calculating the position, tangent and normal for a continuous curve in 2D space is simple enough and I suggest you create separate functions that return PVector objects for each of these actions e.g.

/**
 Calculate the position of a point on the curve
 y = sin(x) + cos(2*x)
 */
PVector fX(float x) {
  float y = sin(x) + cos(2*x);
  return new PVector(x, y);
}

/**
 Calculate the tangent to the curve y=f(x) at the 
 given position.
 The returned tangent will be of unit length so would
 need to be scaled for display.
 */
PVector tan_fX(float x, float deltaX) {
  PVector tan = PVector.sub(fX(x + deltaX), fX(x));
  tan.normalize();
  return tan;
}

/**
 Calculate the normal to the curve y=f(x) at the 
 given position.
 The returned normal will be of unit length so would
 need to be scaled for display.
 Note there are two normals for any point on a 2D curve
 and this function returns one of them. To get the other
 simply multiple the returned vector by -1
 */
PVector norm_fX(float x, float deltaX) {
  PVector norm = PVector.sub(fX(x + deltaX), fX(x));
  norm.set(-norm.y, norm.x);
  norm.normalize();
  return norm;
}

I notice that you are using P3D and I suggest you change this to P2D if you are only interested in 2D curves. If you decide to work in 3D then you can use the same approach for the position and the tangent to the curve but not for the normal.For a 3D continuous curve there are an infinite number of normals for any point on the curve.

EDIT:
I should have said the parameter deltaX is a very small number e.g. 0.001
The method used in this post uses approximation to calculate the tangent and normal. The calculation is more accurate as deltaX approaches zero but deltaX must not be so small that the value PVector.sub(fX(x + deltaX), fX(x)); creates a zero length vector.

5 Likes

Final project was a success!
I challenged myself to do this with basic math operators, accomplished that and learned so much along the way. :slight_smile:
PVectors were not used.
This is all my hand stitched code. :slight_smile:

You can do some cool stuff with Processing!

Animated GIF of final project:

I posted some of the work that lead up to this but will not be sharing final project.

:slight_smile:

8 Likes

Well done, nice animation :slight_smile:

2 Likes

Congratulations – nice work.

You might also be interested in the bundled ArcLengthParameterization example – it specifically addresses the two ways that you could approach vertex distribution (and so, whether / how your animated figure slows down on the curves).

3 Likes