0
votes

The study project is about warping text along bezier curves(recursive polynomial form) on processing using the geomerative library to get shape from a .ttf type font file.(It needs a ttf file in the data directory to function.) currently, the sketch seems to throws errors when fill(any color); is used at the part where the code draws the character shapes and the bezier curve's length gets shorter than a certain length. If fill(); is not used, the sketch seems to function okay without any errors. The goal is to use the fill(); function to fill the characters without errors.

I've tried; 1) getting rid of the beginContour(); and endContour(); because I thought it hasn't been written properly.(I thought it was wrong because the contours should only be drawn when the shape is the inner side of a letter but currently, it draws contours when it's not the first or last shape) But the sketch throws errors even when the contour function was not used (fill(); was used). 2) thought it had something to do with the length of the curve, so tried to add a if statement to the part where it draws the letters. So far, I've tried using the width of the RGroup generated from the initial font size and string in void setup(){}, and the length of the bezier curve. The condition examples within the if statement was as follows; -draw letters when the RGroup shape's width is smaller than the length of the curve -draw letters when the "indent"(a variable to calculate the position on the curve) value is smaller than the length of the curve. (this case made the sketch to draw letters only when the letters were placed within the curve, but the error still occurred) -draw letters when the "indent"(a variable to calculate the position on the curve) value is smaller than the width of the RGroup.

I've failed to see where exactly the problem is occurring, so I'm sharing the entire code within the sketch, but I marked the spot where I presume the error is happening with "//*******".

This study was based on the following link. The geomerative library documentation can be seen from the following link.

//build a bezier curve using the recursive polynomial form
//number of control points(num+1 points)
int num = 4;
//arraylist to store the picked values
ArrayList<Point> pt;
float nfac;

import geomerative.*;

RFont fnt;
RGroup rg;
RPoint [][]rp;

String str = "(O_o)/ Oooh";

FloatList X;
FloatList Y;
FloatList SUM;

void setup(){
  size(1000,1000,P2D);
  RG.init(this);
  pt = new ArrayList<Point>();
  //pick a number of points with random positions and store x,y values in them
  for(int i=0; i<=num; i++){
    float x = random(0,width);
    float y = random(0,height);
    pt.add(new Point(x,y));
  }

  fnt = new RFont("Zapfino.ttf",100);
  //RCommand.setSegmentAngle(random(0,HALF_PI)); 
  //RCommand.setSegmentator(RCommand.ADAPTATIVE);
  RCommand.setSegmentLength(3); 
  RCommand.setSegmentator(RCommand.UNIFORMLENGTH);

  rg = fnt.toGroup(str);
  rp = rg.getPointsInPaths();

  X = new FloatList();
  Y = new FloatList();

  SUM = new FloatList();
}

void draw(){
  background(255);
  noFill();
  strokeCap(ROUND);
  strokeWeight(2);
  //draw points
  beginShape();
  for(int i=0; i<=num; i++){
    vertex(pt.get(i).x,pt.get(i).y);
  }
  endShape();

  for(int i=0; i<=num; i++){
    ellipse(pt.get(i).x,pt.get(i).y,10,10);
  }


  //draw curve
  nfac = 1;
  for(int i=0; i<num; i++){
    nfac *= (i+1);
  }

  strokeWeight(2);
  beginShape();
  int Bzindex = 0;
  for(float t=0; t<=1; t+=0.01){
    float x = 0;
    float y = 0;
    Bzindex++;
    for(int i=0; i<=num; i++){

      float coef = 1;
      float kfac = 1;
      float k_nfac = 1;
      for(int k=i; k>0; k--){
        kfac *= k;
      }
      for(int k=(num-i); k>0; k--){
        k_nfac *= k;
      }

      coef = nfac/(kfac*k_nfac);

      x += coef*(pow((1-t),num-i)*pow(t,i)*pt.get(i).x);
      y += coef*(pow((1-t),num-i)*pow(t,i)*pt.get(i).y);

    }
    vertex(x,y);
    X.set(Bzindex,x);
    Y.set(Bzindex,y);
  }
  endShape();




  //get arclength by pulling points from a floatlist
  int numberOfDivisions = X.size()-2;
  int maxPoint = numberOfDivisions+1;

  float sum = 0;

  float prevPointX = X.get(0);
  float prevPointY = Y.get(0);

  for(int i=1; i<=maxPoint; i++){
    float pointX = X.get(i);
    float pointY = Y.get(i);
    sum += dist(pointX,pointY,prevPointX,prevPointY);
    SUM.set(i-1,sum);
    prevPointX = pointX;
    prevPointY = pointY;
  }
  //

  //*******************************************************
  noFill();
  //creates error only when using fill();
  fill(0,255,0);
  stroke(0);
  //noStroke();
  strokeWeight(4);
  float indent = SUM.get(0);
  beginShape();
  for(int i=0; i<rp.length; i++){

    if(i>0){
      beginContour();
    }

    for(int j=0; j<rp[i].length; j++){
      float t = 0;

      indent = rp[i][j].x+SUM.get(0);

      //

      float targetArcLength = indent;

        int index = IndexOfLargestValueSmallerThan(targetArcLength);

        if(SUM.get(index)==targetArcLength){
          t = index/(SUM.size()-1);
        }else{
          float lengthBefore = SUM.get(index);
          float lengthAfter = SUM.get(index+1);
          float segmentLength = lengthAfter - lengthBefore;
          float segmentFraction = (targetArcLength - lengthBefore)/segmentLength;
          t = (index+segmentFraction)/(SUM.size()-1);
        }

        float x = 0;
        float y = 0;
        float vx = 0;
        float vy = 0;

          for(int l=0; l<=num; l++){

            float coef = 1;
            float kfac = 1;
            float k_nfac = 1;
            for(int k=l; k>0; k--){
              kfac *= k;
            }
            for(int k=(num-l); k>0; k--){
              k_nfac *= k;
            }

            coef = nfac/(kfac*k_nfac);

            x += coef*(pow((1-t),num-l)*pow(t,l)*pt.get(l).x);
            y += coef*(pow((1-t),num-l)*pow(t,l)*pt.get(l).y);

            float v = (pow(1-t,num-l)*l*pow(t,l-1))+((num-l)*pow(1-t,num-l-1)*(-1)*pow(t,l));
            vx += coef*pt.get(l).x*(v);
            vy += coef*pt.get(l).y*(v);
         }

        PVector P = new PVector(x,rp[i][j].y+y);

        PVector ldir = new PVector(P.x-x,P.y-y);


        PVector dir = new PVector(vy,-vx); 
        //
        ldir.rotate(dir.heading()+PI/2);

        vertex(x+ldir.x,y+ldir.y);
      }
      if(i<rp.length&&i>0){
        endContour();
      }

    }
    endShape();
    //**************************************************************
}

int IndexOfLargestValueSmallerThan(float _targetArcLength){
  int index = 0;
  for(int i=0; i<SUM.size()-1; i++){
    if(SUM.get(i)<=_targetArcLength){
      index = i;
    }
  }
  return index;
}

void mouseDragged(){
  int which = -1;
  if((mouseX<width)&&(mouseX>0)&&(mouseY<height)&&(mouseY>0)){
  for(int i=0; i<=num; i++){
    if(dist(mouseX,mouseY,pt.get(i).x,pt.get(i).y)<50){
      which = i;
      pt.get(which).update(mouseX,mouseY);
    }
  }
  }
}

class Point{
  float x,y;

  Point(float _x, float _y){
    x = _x;
    y = _y;
  }

  void update(float _newx, float _newy){
    x = _newx;
    y = _newy;
  }

}

Sometimes the error happens when the sketch is loaded. Most of the time, it loads okay but throws an error when you drag the point around a bit. The error code sometimes refers to the point where the control points of the curve are updated by mouse position, but because error somtimes occurs when the sketch is loaded as well, I didn't think it was a problem connected to the updated positions.

The error code is as follows;

a.lang.AssertionError
    at processing.opengl.PSurfaceJOGL$2.run(PSurfaceJOGL.java:412)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.AssertionError
    at jogamp.opengl.glu.tessellator.Sweep.AddRightEdges(Sweep.java:358)
    at jogamp.opengl.glu.tessellator.Sweep.SweepEvent(Sweep.java:1105)
    at jogamp.opengl.glu.tessellator.Sweep.__gl_computeInterior(Sweep.java:1339)
    at jogamp.opengl.glu.tessellator.GLUtessellatorImpl.gluTessEndPolygon(GLUtessellatorImpl.java:526)
    at com.jogamp.opengl.glu.GLU.gluTessEndPolygon(GLU.java:896)
    at processing.opengl.PJOGL$Tessellator.endPolygon(PJOGL.java:641)
    at processing.opengl.PGraphicsOpenGL$Tessellator.tessellatePolygon(PGraphicsOpenGL.java:12621)
    at processing.opengl.PGraphicsOpenGL.tessellate(PGraphicsOpenGL.java:2255)
    at processing.opengl.PGraphicsOpenGL.endShape(PGraphicsOpenGL.java:1965)
    at processing.core.PGraphics.endShape(PGraphics.java:1707)
    at processing.core.PApplet.endShape(PApplet.java:11641)
    at bezier_polynomial_recursive_text_03.draw(bezier_polynomial_recursive_text_03.java:218)
    at processing.core.PApplet.handleDraw(PApplet.java:2475)
    at processing.opengl.PSurfaceJOGL$DrawListener.display(PSurfaceJOGL.java:866)
    at jogamp.opengl.GLDrawableHelper.displayImpl(GLDrawableHelper.java:692)
    at jogamp.opengl.GLDrawableHelper.display(GLDrawableHelper.java:674)
RuntimeException: java.lang.AssertionError
    at jogamp.opengl.GLAutoDrawableBase$2.run(GLAutoDrawableBase.java:443)
    at jogamp.opengl.GLDrawableHelper.invokeGLImpl(GLDrawableHelper.java:1293)
    at jogamp.opengl.GLDrawableHelper.invokeGL(GLDrawableHelper.java:1147)
    at com.jogamp.newt.opengl.GLWindow.display(GLWindow.java:759)
    at com.jogamp.opengl.util.AWTAnimatorImpl.display(AWTAnimatorImpl.java:81)
    at com.jogamp.opengl.util.AnimatorBase.display(AnimatorBase.java:452)
    at com.jogamp.opengl.util.FPSAnimator$MainTask.run(FPSAnimator.java:178)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)
1
You may be running out of memory on the stack. If so, the most probably culprit would be the insane 2D array rp. Switching it inside a class (creating your own or using an array of containers like ArrayList <>) would switch most of it on the heap. It would require some work and I may be wrong, though, but that's the first thing I would try. - laancelot
Thank you for your advice! I'll give it a try and let you know if it goes well. I think it is highly probable that running out of memory is why I'm having problems with the fill() function as well, because when I turned off the P2D renderer, the sketch did not throw any errors. - hlp_pls
Keep me updated, this is an interesting problem as far as I'm concerned! - laancelot
I got rid of the errors, but still unconfident about what the problem was..!! The main problem seems to have been a poorly written math formula. - hlp_pls
As is mentioned on the wikipedia page about bezier curves, this way of computing is not recommended. This made me laugh! Congratulations by the way, you probably nailed it. I'm glad I could witness it! - laancelot

1 Answers

0
votes

I don't think this would be a direct answer to my question, but it did stop the errors from occurring while using both fill() and the P2D renderer. The main problem, as pointed out by laancelot above, indeed seems to have been connected with stack overflow. So I approached the problem in two ways written below; Conclusion: The direct reason was a poorly expressed math formula.

1) switching the RPoints inside a class. -I don't think this was the direct reason the errors were occurring, because at the stage where only this part of rewriting the code was done, the errors were still there. But maybe it was part of the problem. I'm not sure.

2) rewriting the part where the code expresses the formula to evaluate bezier curves at a specific point. -Previously, the formula was made by using the explicit definition of a bezier curve with degree n. And, as a result, the formula had to be calculated(more like made) for every point in the RPoint points. As is mentioned on the wikipedia page about bezier curves, this way of computing is not recommended. -on the revised code, the formula used to warp text was expressed in the polynomial form. Thus, it was able to pre-calculate the coefficients of the polynomial before the RPoint points were iterated. This seems to have solved the problem.

I'm still not really confident about what actually caused the problem and why it has been solved, and which part of the code I should show to explain this to others, so I'll share the entire code that has been rewritten. You need processing, the geomerative library, and a ttf type font file in a data folder to test the code. I have marked the place where the revised version of the formula is implicated. (It's still really messy....)

//n number of points
int num = 4;
//arraylist to store the picked values
ArrayList<cntrlPoint> pt;

//import the geomerative library
import geomerative.*;



//string
String str = "(O_o)/ Oooh";

FloatList X;
FloatList Y;
FloatList SUM;

RClass rc;

void setup() {
  size(1000, 1000, P2D);
  pt = new ArrayList<cntrlPoint>();
  //pick a number of points with random positions
  for (int i=0; i<=num; i++) {
    float x = random(0, width);
    float y = random(0, height);
    pt.add(new cntrlPoint(x, y));
  }

  RG.init(this);
  rc = new RClass();   

  X = new FloatList();
  Y = new FloatList();  
  SUM = new FloatList();
}

void draw() {
  background(255);
  noFill();
  strokeWeight(2);
  drwCntrlPoints();
  drwCurve();
  gtArcLength();
  fill(0,255,0);
  rc.crtPoly(pt);
  rc.drwText();
}

void drwCntrlPoints() {
  //draw points
  beginShape();
  for (int i=0; i<=num; i++) {
    vertex(pt.get(i).x, pt.get(i).y);
  }
  endShape();

  for (int i=0; i<=num; i++) {
    ellipse(pt.get(i).x, pt.get(i).y, 10, 10);
  }
}

void drwCurve() {
  //draw curve
  float curveDetail = 0.01;
  float nfac = 1;
  for (int i=0; i<num; i++) {
    nfac *= (i+1);
  }
  int arcIndex = 0;
  strokeWeight(2);
  beginShape();
  for (float t=0; t<=1; t+=curveDetail) {
    float x = 0;
    float y = 0;
    arcIndex++;
    for (int i=0; i<=num; i++) {

      float coef = 1;
      float kfac = 1;
      float k_nfac = 1;
      for (int k=i; k>0; k--) {
        kfac *= k;
      }
      for (int k=(num-i); k>0; k--) {
        k_nfac *= k;
      }

      coef = nfac/(kfac*k_nfac);

      x += coef*(pow((1-t), num-i)*pow(t, i)*pt.get(i).x);
      y += coef*(pow((1-t), num-i)*pow(t, i)*pt.get(i).y);
    }
    vertex(x, y);
    X.set(arcIndex, x);
    Y.set(arcIndex, y);
  }
  endShape();
}

void gtArcLength() {
  //get arclength by pulling points from a floatlist
  int numberOfDivisions = X.size()-2;
  int maxPoint = numberOfDivisions+1;

  float sum = 0;

  float prevPointX = X.get(0);
  float prevPointY = Y.get(0);

  for (int i=1; i<=maxPoint; i++) {
    float pointX = X.get(i);
    float pointY = Y.get(i);
    sum += dist(pointX, pointY, prevPointX, prevPointY);
    SUM.set(i-1, sum);
    prevPointX = pointX;
    prevPointY = pointY;
  }
}

//*******factorial
int fact(int fa){
  if(fa==1){
    return 1;
  }
  if(fa==0){
    return 1;
  }
  else{
    return fa*fact(fa-1);
  }
}
//********************

int IndexOfLargestValueSmallerThan(float _targetArcLength) {
  int index = 0;
  for (int i=0; i<SUM.size()-1; i++) {
    if (SUM.get(i)<=_targetArcLength) {
      index = i;
    }
  }
  return index;
}

void mouseDragged() {
  int which = -1;
  if ((mouseX<width)&&(mouseX>0)&&(mouseY<height)&&(mouseY>0)) {
    for (int i=0; i<=num; i++) {
      if (dist(mouseX, mouseY, pt.get(i).x, pt.get(i).y)<80) {
        which = i;
      }
    }
    if (which>-1) {
      pt.get(which).update(mouseX, mouseY);
    }
  }
}

class RClass {
  //get ttf file
  //create rfont
  RFont fnt;
  //turn rfont to rgroup to get points
  RGroup rg;
  //going to get point in path, so that the characters in the string can be seperated
  RPoint [][]rp;

  //floatlist to store coefficients
  FloatList Cx;
  FloatList Cy;

  RClass() {
    fnt = new RFont("Zapfino.ttf", 100);
    rg = fnt.toGroup(str);
    rp = rg.getPointsInPaths();

    //RCommand.setSegmentAngle(random(0,HALF_PI)); 
    //RCommand.setSegmentator(RCommand.ADAPTATIVE);
    RCommand.setSegmentLength(3); 
    RCommand.setSegmentator(RCommand.UNIFORMLENGTH);

    Cx = new FloatList();
    Cy = new FloatList();
  }

  //**********************************here
  void crtPoly(ArrayList<cntrlPoint> _pt){
    float ptsize = _pt.size();
    for(int j=0; j<ptsize; j++){
      float coefx = 0;
      float coefy = 0;
      float pi = 1;
      float sigx = 0;
      float sigy = 0;
      for(int m=0; m<=j-1; m++){
        pi *= (ptsize-1-m);
      }
      for(int i=0; i<=j; i++){
        sigx += (pow(-1,i+j)*pt.get(i).x)/(fact(i)*fact(j-i));
        sigy += (pow(-1,i+j)*pt.get(i).y)/(fact(i)*fact(j-i));
      }
      coefx = pi*sigx;
      coefy = pi*sigy;
      Cx.set(j,coefx);
      Cy.set(j,coefy);
    }
  }
  //**************************************

  void drwText() {
    float indent = SUM.get(0);  

    beginShape();       
    for (int i=0; i<rp.length; i++) {
      if(i>0){
        beginContour();
      }
      for (int j=0; j<rp[i].length; j++) {

        float t = 0;

        indent = rp[i][j].x+SUM.get(0);

        float targetArcLength = indent;

        int index = IndexOfLargestValueSmallerThan(targetArcLength);

        if (SUM.get(index)==targetArcLength) {
          t = index/(SUM.size()-1);
        } else {
          float lengthBefore = SUM.get(index);
          float lengthAfter = SUM.get(index+1);
          float segmentLength = lengthAfter - lengthBefore;
          float segmentFraction = (targetArcLength - lengthBefore)/segmentLength;
          t = (index+segmentFraction)/(SUM.size()-1);
        }

        //***************************here
        float x = 0;
        float y = 0;
        float vx = 0;
        float vy = 0;

        for(int l=0; l<=num; l++){
          x += Cx.get(l)*pow(t,l);
          y += Cy.get(l)*pow(t,l);
        }

        for(int l=1; l<=num; l++){
          vx += l*Cx.get(l)*pow(t,l-1);
          vy += l*Cy.get(l)*pow(t,l-1);
        }
        //**************************************

        PVector P = new PVector(x, rp[i][j].y+y);

        PVector ldir = new PVector(P.x-x, P.y-y);


        PVector dir = new PVector(vy, -vx); 
        //
        ldir.rotate(dir.heading()+PI/2);

        vertex(x+ldir.x, y+ldir.y);
      }
      if(i>0&&i<rp.length){
        endContour();
      }
    }
    endShape();
  }
}

class cntrlPoint{
  float x,y;

  cntrlPoint(float _x, float _y){
    x = _x;
    y = _y;
  }

  void update(float _newx, float _newy){    
    x = _newx;
    y = _newy;   
  }

}