What is the L-System be to generate an Penrose P3 tiling?

57 Views Asked by At

I am trying to write an L-System to generate the Penrose P3 tiling (with thin and thick rhombi, I will call them Rhombus-A and Rhombus-B.

For render, I am planning to use the following chars.

  • '+' means rotate counter-clockwise TWO_PI/20
  • '-' means rotate clockwise TWO_PI/20
  • 'A' means draw Rhombus-A
  • 'B' means draw Rhombus-B
  • '[' push transformation matrix
  • ']' pop transformation matrix

And my question is what would the L-System be to generate an Penrose P3 tiling? Any references would be appreciated.

I got this far:

enter image description here

function penroseP3(){
    this.a = 20; //side length
    this.center = createVector(width/2, height/2)
    this.theta = TWO_PI/20;
    this.axiom = "[X]++++[X]++++[X]++++[X]++++[X]";
    this.ruleX = "A-------[B[+++++A]+++++++++A[--[A[++++++A][------A]]][-----B][-------B[+A]][+B[+A]][+++B]++++++A]";
    this.sentence = this.axiom;
    
    this.substitute = function() {
        //apply substitution rules to create new iteration of sentence string 
        let newSentence = "";
    
        for (let i = 0; i < this.sentence.length; ++i) {
            let step = this.sentence.charAt(i);
            //if current character is 'W', replace current character
            //by corresponding rule
            if (step == 'W') {
                newSentence = newSentence + this.ruleW; //not defined yet
            } else if (step == 'X') {
                newSentence = newSentence + this.ruleX;
            } else if (step == 'Y') {
                newSentence = newSentence + this.ruleY; //not defined yet
            } else if (step == 'Z') {
                newSentence = newSentence + this.ruleZ; //not defined yet
            } else {
                newSentence = newSentence + step; //Do nothing
            }
        }
        this.generations++;
        this.sentence = newSentence;
    }
    this.LSystem = function(generations) {
        for (let i = 0; i < generations; i++) {
            this.substitute();
        }
    }
    
    this.drawRhombusA = function(){
        push();
        beginShape();
        //bottom vertex
        vertex(0, 0);
        vertex(this.a * sin(2*this.theta), -this.a * cos(2*this.theta));
        vertex(0, -2*this.a * cos(2*this.theta));
        vertex(-this.a * sin(2*this.theta), -this.a * cos(2*this.theta));
        endShape(CLOSE);
        pop();
    }  
    this.drawRhombusB = function(){
        push();
        beginShape();
        //bottom vertex
        vertex(0, 0);
        vertex(this.a * sin(this.theta), -this.a * cos(this.theta));
        vertex(0, -2*this.a * cos(this.theta));
        vertex(-this.a * sin(this.theta), -this.a * cos(this.theta));
        endShape(CLOSE);
        pop();
    }  
    this.draw = function() {
        let steps = this.sentence;
        // console.log(this.sentence);
        translate(this.center);
        for (let i = 0; i < steps.length; i++) {
            let step = steps.charAt(i);
            if (step == 'A') {
                this.drawRhombusA();
                translate(0, - 2*this.a*cos(2*this.theta));
            } else if (step == 'B') {
                this.drawRhombusB();
                translate(this.a*sin(this.theta), -this.a*cos(this.theta));
            } else if (step == '+') {
                rotate(this.theta);
            } else if (step == '-') {
                rotate(-this.theta);
            } else if (step == '[') {
                push();
            } else if (step == ']') {
                pop();
            }
        }
    }
}

let p3;

function setup() {
    createCanvas(windowWidth, 300);
    p3 = new penroseP3();
    p3.LSystem(2)
}

function draw() {
    background(100,200, 255);
    p3.draw();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.min.js"></script>

You can also copy/paste the code here to quickly test and manipulate the code:

https://editor.p5js.org/

1

There are 1 best solutions below

0
Ouss On

After several hours of trial and error, I gave up on the original method and digged deeper in the annals of Mathematics, and found this solution that draws the edges of the rhombi rather than the rhobmi shapes. Since it satisfies the question as it is stated above I will post it here. However I still don't have an answer for an L-System that generates the Penrose P3 tiling by drawing the thin and thick Rhombi.

let ds;

function setup() {
  createCanvas(400, 400);
  ds = new PenroseLSystem();
  //please, play around with the following line
  ds.simulate(5);
}

function draw() {
  background(100,200, 255);
  ds.render();
}

function PenroseLSystem() {
    this.steps = 0;

   //these are axiom and rules for the penrose rhombus l-system
   //a reference would be cool, but I couldn't find a good one
    this.axiom = "[X]++[X]++[X]++[X]++[X]";
    this.ruleW = "YF++ZF----XF[-YF----WF]++";
    this.ruleX = "+YF--ZF[---WF--XF]+";
    this.ruleY = "-WF++XF[+++YF++ZF]-";
    this.ruleZ = "--YF++++WF[+ZF++++XF]--XF";

    //please play around with the following two lines
    this.startLength = 400;
    this.theta = TWO_PI / 10.0; //36 degrees, try TWO_PI / 6.0, ...
    this.reset();
}

PenroseLSystem.prototype.simulate = function (gen) {
  while (this.getAge() < gen) {
    this.iterate(this.production);
  }
}

PenroseLSystem.prototype.reset = function () {
    this.production = this.axiom;
    this.drawLength = this.startLength;
    this.generations = 0;
  }

PenroseLSystem.prototype.getAge = function () {
    return this.generations;
  }

//apply substitution rules to create new iteration of production string
PenroseLSystem.prototype.iterate = function() {
    let newProduction = "";

    for(let i=0; i < this.production.length; ++i) {
      let step = this.production.charAt(i);
      //if current character is 'W', replace current character
      //by corresponding rule
      if (step == 'W') {
        newProduction = newProduction + this.ruleW;
      }
      else if (step == 'X') {
        newProduction = newProduction + this.ruleX;
      }
      else if (step == 'Y') {
        newProduction = newProduction + this.ruleY;
      }
      else if (step == 'Z') {
        newProduction = newProduction + this.ruleZ;
      }
      else {
        //drop all 'F' characters, don't touch other
        //characters (i.e. '+', '-', '[', ']'
        if (step != 'F') {
          newProduction = newProduction + step;
        }
      }
    }

    this.drawLength = this.drawLength * 0.5;
    this.generations++;
    this.production = newProduction;
}

//convert production string to a turtle graphic
PenroseLSystem.prototype.render = function () {
    translate(width / 2, height / 2);

    // this.steps += 20;
    // if(this.steps > this.production.length) {
    this.steps = this.production.length;
    // }

    for(let i=0; i<this.steps; ++i) {
      let step = this.production.charAt(i);

      //'W', 'X', 'Y', 'Z' symbols don't actually correspond to a turtle action
      if( step == 'F') {
        stroke(255, 60);
        for(let j=0; j < this.repeats; j++) {
          line(0, 0, 0, -this.drawLength);
          noFill();
          translate(0, -this.drawLength);
        }
        this.repeats = 1;
      }
      else if (step == '+') {
        rotate(this.theta);
      }
      else if (step == '-') {
        rotate(-this.theta);
      }
      else if (step == '[') {
        push();
      }
      else if (step == ']') {
        pop();
      }
    }
  }
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.min.js"></script>