Random movement of ideal random-walk chains

Each chain is moving is independently, the first segment completely random, the next ones restricted by segment distance and step length. Segment distance and step length have a random variation.

Each chain is created with an angle restriction between segments (low angle expandend chain, high angle compact chain).

Coil[] coil;
int boxsize = 500;
float camangle = 90; //camera angle
float angle = 90; //maximum anlge between coil[j].segments, the larger the more flexible
int snumber = 75; //number of coil[j].segments in coil
int cnumber = 5; // number of coils
int sdistance = 10; //distance between coil[j].segments
float segdistdev = 0.5; //deviation from possible sdistance (0.1 = 10%)
float stepdev = 0.5; // deviation from possible speed/speed (0.1 = 10%)
int speed = 10; //movement speed


void setup(){
 size(900, 700, P3D);
 coil = new Coil[cnumber];
 for (int j=0; j < coil.length; j++){
   coil[j] = new Coil(sdistance, snumber, speed);
 }
}


void draw(){
  background(129,200,273);
  lights();
  translate(450,350,0);
  rotateY(camangle);
  noFill();
  stroke(0);
  box(boxsize);
  println("Start: Check");
  
 for (int j=0; j < coil.length; j++){
  for (int i = 1; i < coil[j].segments.length; i++){
    PVector point0 = new PVector();
    PVector point1 = new PVector();
    point0 = coil[j].segments[i-1].location.copy();
    point1= coil[j].segments[i].location.copy();
//check that segments are within box; while instead of if to cover cases with coordinates larger than boxsize/2    
   while (point0.x > boxsize/2){ point0.x -=boxsize; }
   while (point0.x < -boxsize/2){ point0.x +=boxsize; }
   while (point0.y > boxsize/2){ point0.y -=boxsize; }
   while (point0.y < -boxsize/2){ point0.y +=boxsize; }
   while (point0.z > boxsize/2){ point0.z -=boxsize; }
   while (point0.z < -boxsize/2){ point0.z +=boxsize; }
   while (point1.x > boxsize/2){ point1.x -=boxsize; }
   while (point1.x < -boxsize/2){ point1.x +=boxsize; }
   while (point1.y > boxsize/2){ point1.y -=boxsize; }
   while (point1.y < -boxsize/2){ point1.y +=boxsize; }
   while (point1.z > boxsize/2){ point1.z -=boxsize; }
   while (point1.z < -boxsize/2){ point1.z +=boxsize; } 
    
    //check if points are on opposite ends of box, then no line is drawn, 2*sdistance to accommodate deviation
    if (abs(point1.x - point0.x) < 2*sdistance && abs(point1.y - point0.y) < 2*sdistance && abs(point1.z - point0.z) < 2*sdistance*sdistance){
      strokeWeight(5);
      line(point0.x,point0.y, point0.z, point1.x, point1.y, point1.z);
      strokeWeight(1);
    }
  }
 }
  
  if(mousePressed == true){
    camangle += 0.1;
  }

  for (int j=0; j < coil.length; j++){
    for (int i=0; i < coil[j].segments.length; i++){
      println("Start Movement: Check");
      PVector alastnew = new PVector(); //axis of last to new segment
      PVector teststep = new PVector(); //temporary step to check conditions for Segment 0 step
      PVector testaxis = new PVector(); //temporary axis to check conditions for Segment 0 step
      float taxislength = 0;
      float seg1step = 0; //actual step length calculated
      float seg1distance = 0;
      PVector seg0 = new PVector();
      PVector segment0 = new PVector();
      PVector segment1 = new PVector();
      
      
      if (i == 0){
        //random step for first segment
        segment0 = coil[j].segments[0].location.copy();
        segment1 = coil[j].segments[1].location.copy();
        while (abs(taxislength) < sdistance || abs(taxislength) > sdistance+speed) {
          seg0 = PVector.random3D();
          float step1 = speed*random(1-stepdev,1+stepdev);
          seg0.setMag(step1);
          PVector.add(segment0,seg0,teststep);
          PVector.sub(teststep,segment1,testaxis);
          taxislength = testaxis.mag();
        }
        segment0 = teststep.copy(); //1st segment updated
        coil[j].segments[0].location = segment0.copy();
      } else {
        segment0 = coil[j].segments[i-1].location.copy();
        segment1 = coil[j].segments[i].location.copy();
      
      PVector.sub(segment0,segment1,alastnew);
     float axislength = alastnew.mag();

      while (seg1step < abs(axislength-seg1distance) || seg1step > abs(axislength+seg1distance)){
        seg1distance = sdistance*random(1-segdistdev,1+segdistdev);
        seg1step=speed*random(1-stepdev,1+stepdev);
      }
      println("Movement calculation completed for segment", i);
      println(frameRate);
      segment1 = movement(segment0,segment1,seg1distance,seg1step);
      coil[j].segments[i].location = segment1.copy();
    }
    }
  }
}

class Coil{
  Segments[] segments;
  int csdistance;
  int csnumber;
  int cspeed;
  float crgcounter = 0;
  float cRg = 0;
  
  Coil(int sdistance_ , int snumber_ , int speed_){
    csdistance = sdistance_;
    csnumber = snumber_;
    cspeed = speed_;
    segments = new Segments[csnumber];
    startcond();
    println(segments[0].location, segments[snumber-1].location);
  } 
  
  void startcond(){
  //create starting point for coil
  PVector loc; //starting vector
  PVector loc_old = new PVector(); //preceding vector
  float test = 0; //angle test
    loc = new PVector (map(random(1),0,1,-boxsize/4,boxsize/4),map(random(1),0,1,-boxsize/4,boxsize/4),map(random(1),0,1,-boxsize/4,boxsize/4));   
    segments[0] = new Segments(loc);
    loc_old = loc.copy();
  //create random segments 
 for (int i = 1; i < segments.length; i++){
   PVector loc2 = loc_old;
   while (degrees(test) > angle || degrees(test)==0 || loc2 == loc_old){
     loc2=PVector.random3D();
     test = PVector.angleBetween(loc2,loc_old);
   } 
    loc2 = loc2.setMag(csdistance);
    loc_old=loc2.copy();
    loc2 = loc2.add(segments[i-1].location);
    segments[i] = new Segments(loc2);
    println(i, degrees(test), loc2);
 }
}
}

PVector movement(PVector lastseg, PVector newseg, float segdistance, float step){
  PVector out = new PVector();
  PVector axis = new PVector();
  PVector proj = new PVector();
  float cradius; //radius of intersect circle
  
  //axis vector through circle center
  PVector.sub(lastseg,newseg,axis);
  
  //vector to projection point of axis, which is middle point of circle
  float projection = (step*step - segdistance*segdistance + axis.magSq())/ (2*axis.mag());
  proj = axis.copy();
  proj.setMag(projection);
  proj.add(newseg);
  
  //radius of intersect circle
   cradius = sqrt(step*step - projection*projection);
  
  //Identify axis coordinate with smallest absolute value to create perpendicular vector
  float ax = abs(axis.x);
  float ay = abs(axis.y);
  float az = abs(axis.z);
  PVector b = new PVector();  
  if (ax <= ay && ax <= az){
    b = new PVector(1,0,0);
  } else if (ax > ay && ay <= az){
    b = new PVector(0,1,0);
  } else {
    b = new PVector(0,0,1);
  }
  
  //create vectors in plane with intersect circle
  PVector iv = new PVector();
  PVector.cross(axis,b,iv);
  PVector jv = new PVector();
  PVector.cross(axis, iv, jv);
  iv.normalize();
  jv.normalize();

  PVector c2 = new PVector();
  PVector c3 = new PVector();
  PVector p = new PVector();
  float cang = random(TWO_PI);
  PVector.mult(iv, cos(cang), c2);
  c2.mult(cradius);
  PVector.mult(jv, sin(cang), c3);
  c3.mult(cradius);
  p.add(proj);
  p.add(c2);
  p.add(c3);
  
  out = p.copy();
  return out;
}

class Segments{
  PVector location;
  
 Segments(PVector location_){
    location = location_;
    }   
}
2 Likes

Update of the code to provide radius of gyration (based on end-to-end distance) for a selected single chain or over all chains.
Chains can be selected with the up and down arrows. Cube can be turned with mouse

Coil[] coil;
int boxsize = 500;
float camangle = 90; //camera angle
float angle = 120; //maximum anlge between coil[j].segments, the larger the more flexible
int snumber = 50; //number of coil[j].segments in coil
int cnumber = 5; // number of coils
int sdistance = 10; //distance between coil[j].segments
float segdistdev = 0.1; //deviation from possible sdistance (0.1 = 10%)
float stepdev = 0.1; // deviation from possible speed/speed (0.1 = 10%)
int speed = 10; //movement speed
float rgcounter = 0;
float Rg = 0;
float rgmin = 0;
float rgmax = 0;
int coilpointer = 0; //pointer for chain selection
FloatList rghistory;

void setup(){
 size(900, 700, P3D);
 coil = new Coil[cnumber];
 for (int j=0; j < coil.length; j++){
   coil[j] = new Coil(sdistance, snumber, speed);
 }
 rghistory = new FloatList(); 
}


void draw(){
  background(129,200,273);
  perspective();
  lights();
  translate(450,350,0);
  rotateY(camangle);
  noFill();
  stroke(0);
  strokeWeight(1);
  box(boxsize);
  println("Start: Check");
  //println(coil[0].segments[0].location);
  
 for (int j=0; j < coil.length; j++){
  for (int i = 1; i < coil[j].segments.length; i++){
    //check for active chain
    if (coilpointer == j){
      stroke(125);
    } else {
      stroke(0);
    }
    
    PVector point0 = new PVector();
    PVector point1 = new PVector();
    point0 = coil[j].segments[i-1].location.copy();
    point1= coil[j].segments[i].location.copy();
//check that segments are within box; while instead of if to cover cases with coordinates larger than boxsize/2    
   while (point0.x > boxsize/2){ point0.x -=boxsize; }
   while (point0.x < -boxsize/2){ point0.x +=boxsize; }
   while (point0.y > boxsize/2){ point0.y -=boxsize; }
   while (point0.y < -boxsize/2){ point0.y +=boxsize; }
   while (point0.z > boxsize/2){ point0.z -=boxsize; }
   while (point0.z < -boxsize/2){ point0.z +=boxsize; }
   while (point1.x > boxsize/2){ point1.x -=boxsize; }
   while (point1.x < -boxsize/2){ point1.x +=boxsize; }
   while (point1.y > boxsize/2){ point1.y -=boxsize; }
   while (point1.y < -boxsize/2){ point1.y +=boxsize; }
   while (point1.z > boxsize/2){ point1.z -=boxsize; }
   while (point1.z < -boxsize/2){ point1.z +=boxsize; } 
    
    //check if points are on opposite ends of box, then no line is drawn, 2*sdistance to accommodate deviation
    if (abs(point1.x - point0.x) < 2*sdistance && abs(point1.y - point0.y) < 2*sdistance && abs(point1.z - point0.z) < 2*sdistance*sdistance){
      strokeWeight(5);
      line(point0.x,point0.y, point0.z, point1.x, point1.y, point1.z);
      strokeWeight(1);
    }
  }
 }

 //Data visualization
   textSize(18);
   hint(DISABLE_DEPTH_TEST);
   int rectx = 175;
   int recty = height - 140;
   int rectsz = 110;
   camera();
   ortho();
   stroke(255);
   strokeWeight(3);
   // Single Coil Data Visualization
   textSize(20);
   text("Radius of gyration of selected chain", rectx-150, recty-20);
   textSize(18);
   text("Max", rectx-150, recty+9);
   text(coil[coilpointer].rgmax, rectx-100, recty+9);
   text("Avg", rectx-150, recty+rectsz/2+9);
   text(coil[coilpointer].cRg, rectx-100, recty+rectsz/2+9);
   rect(rectx, recty,240,110);
   for (int i = 1; i < coil[coilpointer].rghistory.size(); i++){
     float y0 = map(coil[coilpointer].rghistory.get(i-1),coil[coilpointer].rgmin,coil[coilpointer].rgmax,rectsz,0);
     float y1 = map(coil[coilpointer].rghistory.get(i),coil[coilpointer].rgmin,coil[coilpointer].rgmax,rectsz,0);
     stroke(225);
     line(rectx+(i-1)*12,recty+y0,rectx+i*12,recty+y1);
   }
   text("Min", rectx-150, recty+rectsz+9);
   text(coil[coilpointer].rgmin, rectx-100, recty+rectsz+9);
   //All coil average datea visualization
   rectx = 625;
   stroke(255);
   textSize(20);
   text("Radius of gyration over all chains", rectx-150, recty-20);
   textSize(18);
   text("Max", rectx-150, recty+9);
   text(rgmax, rectx-100, recty+9);
   text("Avg", rectx-150, recty+rectsz/2+9);
   text(Rg, rectx-100, recty+rectsz/2+9);
   rect(rectx, recty,240,rectsz);
   for (int i = 1; i < rghistory.size(); i++){
     float y0 = map(rghistory.get(i-1),rgmin,rgmax,rectsz,0);
     float y1 = map(rghistory.get(i),rgmin,rgmax,rectsz,0);
     stroke(225);
     line(rectx+(i-1)*12,recty+y0,rectx+i*12,recty+y1);
   }
   text("Min", rectx-150, recty+rectsz+9);
   text(rgmin, rectx-100, recty+rectsz+9);
   hint(ENABLE_DEPTH_TEST);
 
 
  
  if(mousePressed == true){
    camangle += 0.1;
  }
  
  if(keyPressed == true && keyCode == UP){
    if (coilpointer == coil.length -1){
      coilpointer = 0;
    } else {
    coilpointer++;
    }
  }

  if(keyPressed == true && keyCode == DOWN){
    if (coilpointer == 0){
      coilpointer = coil.length -1;
    } else {
    coilpointer--;
    }
  }  

  for (int j=0; j < coil.length; j++){
    for (int i=0; i < coil[j].segments.length; i++){
      println("Start Movement: Check");
      PVector alastnew = new PVector(); //axis of last to new segment
      PVector teststep = new PVector(); //temporary step to check conditions for Segment 0 step
      PVector testaxis = new PVector(); //temporary axis to check conditions for Segment 0 step
      float taxislength = 0;
      float seg1step = 0; //actual step length calculated
      float seg1distance = 0;
      PVector seg0 = new PVector();
      PVector segment0 = new PVector();
      PVector segment1 = new PVector();
      
      
      if (i == 0){
        //random step for first segment
        segment0 = coil[j].segments[0].location.copy();
        segment1 = coil[j].segments[1].location.copy();
        while (abs(taxislength) < sdistance || abs(taxislength) > sdistance+speed) {
          seg0 = PVector.random3D();
          float step1 = speed*random(1-stepdev,1+stepdev);
          seg0.setMag(step1);
          PVector.add(segment0,seg0,teststep);
          PVector.sub(teststep,segment1,testaxis);
          taxislength = testaxis.mag();
        }
        segment0 = teststep.copy(); //1st segment updated
        coil[j].segments[0].location = segment0.copy();
      } else {
        segment0 = coil[j].segments[i-1].location.copy();
        segment1 = coil[j].segments[i].location.copy();
      
      PVector.sub(segment0,segment1,alastnew);
     float axislength = alastnew.mag();

      while (seg1step < abs(axislength-seg1distance) || seg1step > abs(axislength+seg1distance)){
        seg1distance = sdistance*random(1-segdistdev,1+segdistdev);
        seg1step=speed*random(1-stepdev,1+stepdev);
      }
      println("Movement calculation completed for segment", i);
      println(frameRate);
      segment1 = movement(segment0,segment1,seg1distance,seg1step);
      coil[j].segments[i].location = segment1.copy();
    }
    }
   
   sizecalc(j);
   
  }
}

class Coil{
  Segments[] segments;
  int csdistance;
  int csnumber;
  int cspeed;
  //size calculation variables
  FloatList rghistory; //stores calcluated rg for diagram
  float rgmax; //maximum value
  float rgmin; //minimum value
  float crgcounter = 0; //counter for sum
  float cRg = 0;
  
  Coil(int sdistance_ , int snumber_ , int speed_){
    csdistance = sdistance_;
    csnumber = snumber_;
    cspeed = speed_;
    segments = new Segments[csnumber];
    rghistory = new FloatList();
    startcond();
    println(segments[0].location, segments[snumber-1].location);
  } 
  
  void startcond(){
  //create starting point for coil
  PVector loc; //starting vector
  PVector loc_old = new PVector(); //preceding vector
  float test = 0; //angle test
    loc = new PVector (map(random(1),0,1,-boxsize/4,boxsize/4),map(random(1),0,1,-boxsize/4,boxsize/4),map(random(1),0,1,-boxsize/4,boxsize/4));   
    segments[0] = new Segments(loc);
    loc_old = loc.copy();
  //create random segments 
 for (int i = 1; i < segments.length; i++){
   PVector loc2 = loc_old;
   while (degrees(test) > angle || degrees(test)==0 || loc2 == loc_old){
     loc2=PVector.random3D();
     test = PVector.angleBetween(loc2,loc_old);
   } 
    loc2 = loc2.setMag(csdistance);
    loc_old=loc2.copy();
    loc2 = loc2.add(segments[i-1].location);
    segments[i] = new Segments(loc2);
    println(i, degrees(test), loc2);
 }
}
}

PVector movement(PVector lastseg, PVector newseg, float segdistance, float step){
  PVector out = new PVector();
  PVector axis = new PVector();
  PVector proj = new PVector();
  float cradius; //radius of intersect circle
  
  //axis vector through circle center
  PVector.sub(lastseg,newseg,axis);
  
  //vector to projection point of axis, which is middle point of circle
  float projection = (step*step - segdistance*segdistance + axis.magSq())/ (2*axis.mag());
  proj = axis.copy();
  proj.setMag(projection);
  proj.add(newseg);
  
  //radius of intersect circle
   cradius = sqrt(step*step - projection*projection);
  
  //Identify axis coordinate with smallest absolute value to create perpendicular vector
  float ax = abs(axis.x);
  float ay = abs(axis.y);
  float az = abs(axis.z);
  PVector b = new PVector();  
  if (ax <= ay && ax <= az){
    b = new PVector(1,0,0);
  } else if (ax > ay && ay <= az){
    b = new PVector(0,1,0);
  } else {
    b = new PVector(0,0,1);
  }
  
  //create vectors in plane with intersect circle
  PVector iv = new PVector();
  PVector.cross(axis,b,iv);
  PVector jv = new PVector();
  PVector.cross(axis, iv, jv);
  iv.normalize();
  jv.normalize();

  PVector c2 = new PVector();
  PVector c3 = new PVector();
  PVector p = new PVector();
  float cang = random(TWO_PI);
  PVector.mult(iv, cos(cang), c2);
  c2.mult(cradius);
  PVector.mult(jv, sin(cang), c3);
  c3.mult(cradius);
  p.add(proj);
  p.add(c2);
  p.add(c3);
  
  out = p.copy();
  return out;
}

class Segments{
  PVector location;
  
 Segments(PVector location_){
    location = location_;
    }   
}

void sizecalc(int j_){
  PVector etev = new PVector();
  float rgtemp = 0;
  int j = j_;
  
  coil[j].crgcounter++;
  PVector.sub(coil[j].segments[0].location,coil[j].segments[coil[j].segments.length-1].location,etev);
   rgtemp = sqrt (etev.magSq()/6);
   if (coil[j].rghistory.size() <= 20){
     coil[j].rghistory.append(rgtemp);
   } else {
     coil[j].rghistory.remove(0);
     coil[j].rghistory.append(rgtemp);
   }
   if (rgtemp > coil[j].rgmax){
     coil[j].rgmax = rgtemp;
   } else if (rgtemp < coil[j].rgmin || coil[j].rgmin==0){
     coil[j].rgmin = rgtemp;
   } 
     
   coil[j].cRg = (coil[j].crgcounter-1)*coil[j].cRg/coil[j].crgcounter + rgtemp/coil[j].crgcounter;
   rgcounter++;
   Rg = ((rgcounter-1)*Rg)/rgcounter + rgtemp/rgcounter;
   if (rghistory.size() <= 20){
     rghistory.append(Rg);
   } else {
     rghistory.remove(0);
     rghistory.append(Rg);
   }
   
   if (Rg > rgmax){
     rgmax = Rg;
   } else if (Rg < rgmin || rgmin==0){
     rgmin = Rg;
   }
  
}
1 Like

In the last version of the code the stiffness of the chain was not maintained.

This code now takes care of this. Movement was separated between movement of the whole chain (possible in all directions) and free rotation of a segment between the previous and last segment. The angle is changing but within the restriction provided for stiffness.

Other optimization for a more efficient code were done as well. Next step will be to add a quadtree to include interactions between chain segments.
Frame rate is limited to 20, so that movements is well visible.

Coil[] coils;
int cnumber = 50; //number of coils
int snumber = 200; //number of segments per coil
int snumberdev = 5; //deviation from snumber, standard deviation for gaussian
int sdistance = 10; //distance between segments
float segdistdev = sdistance*0.1; //deviation from possible sdistance (0.1 = 10%)
int step = 10; //segment movement per step
float stepdev = step*0.1; // deviation from possible speed/speed (0.1 = 10%)
int angle = 90; //the smaller the more flexible, angle between vectors to next and previous segment
float angledev = angle*0.1;
int counter;
FloatList rghistory; //Rg history for visualization for all chains
float rgcounter = 0; //counter for sum for total rg
float Rg = 0; //total rg
float rgmin = 0; //Minimum average Rg
float rgmax = 0; //Maximum average Rg

int boxsize = 500;
float camangle = 90; //camera angle

void setup() {
  size(900, 700, P3D);
  frameRate(20);

  rghistory = new FloatList(); 
  coils = new Coil[cnumber];
  for (int j=0; j < cnumber; j++) {
    coils[j] = new Coil();
    if (j == 0) {
      coils[j].selected = true;
    }
  }
}

void draw() {
  background(129, 200, 273);
  perspective();
  lights();
  translate(450, 350, 0);
  rotateY(camangle);
  noFill();
  stroke(0);
  strokeWeight(1);
  box(boxsize);

  if (mousePressed == true) {
    camangle += 0.1;
  }

  for (Coil i : coils) {
    i.show();
    i.movement();
    i.sizecalc();
  }
  //should be after coil.show so that translate applies to coil drawing
  datascreen();
}

void keyReleased() {
  if (keyCode == UP) {
    for (int j = 0; j < cnumber; j++) {
      if ( coils[j].selected == true) {
        if  (j == cnumber-1) {
          coils[0].selected = true;
        } else {
          coils[j+1].selected = true;
        }
        coils[j].selected = false;
        j = cnumber-1;
      }
    }
  }
  if (keyCode == DOWN) {
    for (int j = 0; j < cnumber; j++) {
      if ( coils[j].selected == true) {
        if  (j == 0) {
          coils[cnumber-1].selected = true;
        } else {
          coils[j-1].selected = true;
        }
        coils[j].selected = false;
        j = cnumber-1;
      }
    }
  }
}    

class Coil {
  Segments[] segments;
  boolean selected = false;
  FloatList crghistory;//stores calculated rg for diagram
  float crgmax; //stores maximum rg value
  float crgmin = 0; //stores minimum rg value
  float crgcounter = 0; //counter for Rg sum
  float cRg = 0; //Average Rg value for coil

  Coil() {
    int clength = int(randomGaussian()*snumberdev+snumber);
    segments = new Segments[clength];
    crghistory = new FloatList();
    startcond();
  } 

  void startcond() {
    //create starting point for coil
    PVector loc; //starting vector
    PVector loc_old = new PVector(); //preceding vector
    float test = 0; //angle test
    loc = new PVector (map(random(1), 0, 1, -boxsize/4, boxsize/4), map(random(1), 0, 1, -boxsize/4, boxsize/4), map(random(1), 0, 1, -boxsize/4, boxsize/4));   
    segments[0] = new Segments(loc);
    loc_old = loc.copy();
    //create random segments 
    for (int i = 1; i < segments.length; i++) {
      PVector loc2 = loc_old;
      while (degrees(test) > 180-angle || degrees(test)==0 || loc2 == loc_old) {
        loc2=PVector.random3D();
        test = PVector.angleBetween(loc2, loc_old);
      } 
      loc2 = loc2.setMag(sdistance);
      loc_old=loc2.copy();
      loc2 = loc2.add(segments[i-1].location);
      segments[i] = new Segments(loc2);
    }
  }

  void show() {
    for (int i = 1; i < segments.length; i++) {
      //check for active chain
      if (selected == true) {
        stroke(255, 0, 0);
      } else {
        stroke(0);
      }
      PVector point0 = new PVector();
      PVector point1 = new PVector();
      point0 = segments[i-1].location.copy();
      point1= segments[i].location.copy();
      //check that segments are within box; while instead of if to cover cases with coordinates larger than boxsize/2    
      while (point0.x > boxsize/2) { 
        point0.x -=boxsize;
      }
      while (point0.x < -boxsize/2) { 
        point0.x +=boxsize;
      }
      while (point0.y > boxsize/2) { 
        point0.y -=boxsize;
      }
      while (point0.y < -boxsize/2) { 
        point0.y +=boxsize;
      }
      while (point0.z > boxsize/2) { 
        point0.z -=boxsize;
      }
      while (point0.z < -boxsize/2) { 
        point0.z +=boxsize;
      }
      while (point1.x > boxsize/2) { 
        point1.x -=boxsize;
      }
      while (point1.x < -boxsize/2) { 
        point1.x +=boxsize;
      }
      while (point1.y > boxsize/2) { 
        point1.y -=boxsize;
      }
      while (point1.y < -boxsize/2) { 
        point1.y +=boxsize;
      }
      while (point1.z > boxsize/2) { 
        point1.z -=boxsize;
      }
      while (point1.z < -boxsize/2) { 
        point1.z +=boxsize;
      }

      //check if points are on opposite ends of box, then no line is drawn, 2*sdistance to accommodate deviation
      if (abs(point1.x - point0.x) < sdistance+2*segdistdev && abs(point1.y - point0.y) < sdistance+2*segdistdev && abs(point1.z - point0.z) < sdistance+2*segdistdev) {
        strokeWeight(5);
        line(point0.x, point0.y, point0.z, point1.x, point1.y, point1.z);
        strokeWeight(1);
      }
    }
  }

  void movement() {
    //all chain movement
    PVector coilmov = PVector.random3D();
    float coilstep = randomGaussian()*stepdev+step;
    coilmov = coilmov.setMag(coilstep);
    for (Segments s : segments) {
      s.location = s.location.add(coilmov);
    }
    //movement of every segment based on chosen segmentdistance and angle
    for (int i = 1; i < segments.length-2; i++) {
      PVector lastseg = segments[i-1].location.copy();
      PVector nextseg = segments[i+1].location.copy();
      PVector axis = PVector.sub(lastseg, nextseg);
      float axislength = PVector.dist(lastseg, nextseg);
      PVector proj = new PVector();
      //using random for angle selection stretches chains over time
      float newangle = random(radians(angle), PI);
      //if you want to see the effect of flexibility (low/large angle) use Gaussian angle selection
      //float newangle = radians(angle)+radians(angledev*randomGaussian());
      float distnew = random(sdistance-segdistdev, sdistance+segdistdev);
      //calculation of distance between previous and next segment
      float newdistance = sqrt(2*distnew*distnew - 2*distnew*distnew*cos(newangle));
      //adjustment of distance between previous and next, if necessary
      if (newdistance != axislength) {
        PVector d = axis.copy();
        d.setMag(axislength-newdistance);
        //all segments after i need to be moved, so that chain stays intact
        for (int j = i+1; j < segments.length; j++) {
          segments[j].location.add(d);
        }
      }

      axis = PVector.sub(segments[i-1].location, segments[i+1].location);
      proj = axis.copy();
      proj.setMag(newdistance/2);
      proj.add(segments[i+1].location);

      //radius of intersect circle
      float cradius = sqrt(sq(distnew)-sq(newdistance/2));

      //Identify axis coordinate with smallest absolute value to create perpendicular vector
      float ax = abs(axis.x);
      float ay = abs(axis.y);
      float az = abs(axis.z);
      PVector b = new PVector();  
      if (ax <= ay && ax <= az) {
        b = new PVector(1, 0, 0);
      } else if (ax > ay && ay <= az) {
        b = new PVector(0, 1, 0);
      } else {
        b = new PVector(0, 0, 1);
      }

      //create vectors in plane with intersect circle
      PVector iv = new PVector();
      PVector.cross(axis, b, iv);
      PVector jv = new PVector();
      PVector.cross(axis, iv, jv);
      iv.normalize();
      jv.normalize();

      PVector c2 = new PVector();
      PVector c3 = new PVector();
      PVector p = new PVector();
      float cang = random(TWO_PI);
      PVector.mult(iv, cos(cang), c2);
      c2.mult(cradius);
      PVector.mult(jv, sin(cang), c3);
      c3.mult(cradius);
      p.add(proj);
      p.add(c2);
      p.add(c3);

      segments[i].location = p.copy();
    }
  }

  void sizecalc() {
    PVector etev = new PVector();
    float crgtemp = 0;

    crgcounter++;
    PVector.sub(segments[0].location, segments[segments.length-1].location, etev);
    crgtemp = sqrt (etev.magSq()/6);
    if (crghistory.size() <= 20) {
      crghistory.append(crgtemp);
    } else {
      crghistory.remove(0);
      crghistory.append(crgtemp);
    }
    if (crgtemp > crgmax) {
      crgmax = crgtemp;
    } else if (crgtemp < crgmin || crgmin==0) {
      crgmin = crgtemp;
    } 

    cRg = (crgcounter-1)*cRg/crgcounter + crgtemp/crgcounter;

    rgcounter++;
    Rg = ((rgcounter-1)*Rg)/rgcounter + crgtemp/rgcounter;
    if (rghistory.size() <= 20) {
      rghistory.append(Rg);
    } else {
      rghistory.remove(0);
      rghistory.append(Rg);
    }

    if (Rg > rgmax) {
      rgmax = Rg;
    } else if (Rg < rgmin || rgmin==0) {
      rgmin = Rg;
    }
  }
}

void datascreen() {
  textSize(18);
  hint(DISABLE_DEPTH_TEST);
  int rectx = 175;
  int recty = height - 140;
  int rectsz = 110;
  camera();   
  ortho();

  noStroke();
  fill(129, 200, 273, 200);
  rect(0, recty-50, width, recty+15);

  stroke(255);
  strokeWeight(3);
  noFill();
  rect(rectx, recty, 240, 110);
  fill(255);
  // Single Coil Data Visualization
  textSize(20);
  text("Current Radius of gyration of selected chain", rectx-150, recty-20);
  textSize(18);
  text("Max", rectx-150, recty+9);
  for (Coil c : coils) {
    if (c.selected == true) {
      text(c.crgmax, rectx-100, recty+9);
      text("Avg", rectx-150, recty+rectsz/2+9);
      text(c.cRg, rectx-100, recty+rectsz/2+9);
      for (int i = 1; i < c.crghistory.size(); i++) {
        float y0 = map(c.crghistory.get(i-1), c.crgmin, c.crgmax, rectsz, 0);
        float y1 = map(c.crghistory.get(i), c.crgmin, c.crgmax, rectsz, 0);
        stroke(125);
        line(rectx+(i-1)*12, recty+y0, rectx+i*12, recty+y1);
      }
      text("Min", rectx-150, recty+rectsz+9);
      text(c.crgmin, rectx-100, recty+rectsz+9);
    }
  }
  //All coil average datea visualization
  rectx = 625;
  stroke(255);
  textSize(20);
  text("Radius of gyration over all chains", rectx-150, recty-20);
  textSize(18);
  text("Max", rectx-150, recty+9);
  text(rgmax, rectx-100, recty+9);
  text("Avg", rectx-150, recty+rectsz/2+9);
  text(Rg, rectx-100, recty+rectsz/2+9);
  text("Min", rectx-150, recty+rectsz+9);
  text(rgmin, rectx-100, recty+rectsz+9);
  noFill();
  rect(rectx, recty, 240, rectsz);
  for (int i = 1; i < rghistory.size(); i++) {
    float y0 = map(rghistory.get(i-1), rgmin, rgmax, rectsz, 0);
    float y1 = map(rghistory.get(i), rgmin, rgmax, rectsz, 0);
    stroke(125);
    line(rectx+(i-1)*12, recty+y0, rectx+i*12, recty+y1);
  }
  hint(ENABLE_DEPTH_TEST);
}

class Segments {
  PVector location;

  Segments(PVector loc) {
    location = loc.copy();
  }
}
1 Like