text: import { CGFobject } from '../lib/CGF.js';
  
  export class MyParametricLSystem extends CGFobject {
      constructor(scene, x, y, z) {
          super(scene);
  
          this.init();
  
          this.x = x;
          this.y = y;
          this.z = z;
          this.time = 0;
          this.y0 = y;
          this.x0 = x;
          this.z0 = z;
          this.hasNormals = false;
          this.debug = 0;
          this.occlusionPenalty = 0;
          this.numberOfComponents = 0;
  
          // Each alage has a specific movement in ZZ coordinate to avoid that all the algae move equal
          // This value will be used when the animation is done on CPU
          this.movz = Math.ceil(Math.random() * 1) * (Math.round(Math.random()) ? 1 : -1)
      }
  
      init() {
          this.initGrammar()
          this.setDepthPenaltyScore();
      }
  
      initGrammar() {
          this.grammar = {};
      }
  
      // According with the depth where algae is instantieted a penalty factor is set.
      // More depth is equal to more penalty, because the algae has less access to light and temperatures less suitable to the algae growth
      setDepthPenaltyScore() {
          this.dpScore = new Map();
          this.dpScore.set(0, 0); // no penalty
          this.dpScore.set(-2.5, 0.2);
          this.dpScore.set(-3.5, 0.5);
      }
  
      // Set the weight for each rule to random generate Branch
      chooseRuleForBranch(weight, axiomProductions) {
          if (weight <= 0.60) {
              return axiomProductions[0];
          }
          else {
              return axiomProductions[1];
          }
      }
  
      // Set the weight for each rule to random generate Filaments
      chooseRuleForFilaments(weight, axiomProductions) {
          if (weight <= 0.30) {
              return axiomProductions[0];
          }
          else if (weight > 0.30 && weight < 60) {
              return axiomProductions[1];
          }
          else {
              return axiomProductions[2];
          }
      }
  
      // Set the weight for each rule to random generate Stolons
      chooseRuleForStolons(weight, axiomProductions) {
          if (weight <= 0.30) {
              return axiomProductions[0];
          }
          else if (weight > 0.30 && weight < 60) {
              return axiomProductions[1];
          }
          else {
              return axiomProductions[2];
          }
      }
  
      // Generate Algae components according with the grammar passed by the reules on the User Interface
      generate(_axiom, _productions, _angle, _iterations, isStochastic) {
          this.axiom = _axiom;
          this.productions = _productions;
          this.defaultAngle = _angle * Math.PI / 180.0;
          this.iterations = _iterations;
  
          if (!isStochastic) {
              this.generateSameAlgaeGrammar();
          }
          else {
              this.iterateStochastic();
          }
  
          this.calculateNumberOfComponents();
  
      }
  
  
      // Less algae components will result in more occlusion penalty
      // Because alage with more components will need more space to growth and will occlude the others
      calculateNumberOfComponents() {
          for (var i = 0; i < this.axiom.length; i++) {
              if (this.axiom[i] == 'F' || this.axiom[i] == 'S' || this.axiom[i] == 'B') {
                  this.numberOfComponents++;
              }
          }
      }
  
      getNumberOfAlgaeComponents() {
          return this.numberOfComponents;
      }
  
  
      generateSameAlgaeGrammar() {
          for (var i = 0; i < this.iterations; i++) {
              var newString = "";
              for (var j = 0; j < this.axiom.length; j++) {
                  var axiomProductions = this.productions[this.axiom[j]];
                  if (axiomProductions === undefined) {
                      newString += this.axiom[j];
                  }
                  else {
                      if (axiomProductions.length > 0) {
                          newString += axiomProductions[0]; /* only get the base rule, that is located on first index */
                      }
                  }
              }
              this.axiom = newString;
          }
      }
  
      // Stochastic L-System
      // Add random rules for the algae components, and give different weights to the diferent rules 
      iterateStochastic() {
          for (var i = 0; i < this.iterations; i++) {
              var newString = "";
              for (var j = 0; j < this.axiom.length; j++) {
                  var axiomProductions = this.productions[this.axiom[j]];
                  if (axiomProductions === undefined) {
                      newString += this.axiom[j];
                  }
                  else {
                      newString += this.generateStochastic(this.axiom[j], axiomProductions);
                  }
              }
              this.axiom = newString;
          }
      }
  
      generateStochastic(axiom, axiomProductions) {
          var randomWeight = (Math.floor(Math.random() * 10) + 1) / 10;
          var randomString = "";
          switch (axiom) {
              case 'B':
                  randomString = this.chooseRuleForBranch(randomWeight, axiomProductions);
                  break;
              case 'F':
                  randomString = this.chooseRuleForFilaments(randomWeight, axiomProductions);
                  break;
              case 'S':
                  randomString = this.chooseRuleForStolons(randomWeight, axiomProductions);
                  break;
              default:
                  console.log("Axiom not found");
                  break;
          }
          return randomString;
      }
  
      setOcclusionPenalty(occlusion) {
          this.occlusionPenalty = occlusion;
          this.calculateAlgaePenalty();
      }
  
      //Calculates penalty factors related with plant growth
      //Influence factors: depth + occlusion
      calculateAlgaePenalty() {
          var lastDepthScore = 0;
          for (var [key, value] of this.dpScore) {
              if (this.y < key) {
                  lastDepthScore = value;
              }
              else {
                  break;
              }
          }
          this.scale = 1 - (lastDepthScore + this.occlusionPenalty);
      }
  
      showNormals(hasNormals) {
          this.hasNormals = hasNormals;
      }
  
      buildParameter(axiom) {
          var myInt = parseInt(axiom);
          if (typeof myInt == 'number' || axiom == ".") {
              return axiom;
          }
          else {
              console.log("Is NOT a digit or decimal : ", this.axiom)
          }
      }
  
      clamp(num, min, max) {
          return num <= min ? min : num >= max ? max : num;
      }
  
      // Get the angle to be applied to rotation character
      getAngleFromAxiom(totalDigits) {
          var newAngle = this.clamp(parseInt(totalDigits), 0, 90);
          return (totalDigits != "") ? (newAngle * Math.PI / 180.0) : this.defaultAngle;
      }
  
      // Get the scale to be applied to the algae component
      getScaleFromAxiom(totalDigits) {
          var newScale = this.clamp(parseFloat(totalDigits), 0.1, 1);
          return (totalDigits == "") ? 1 : newScale;
  
      }
  
      display() {
          this.scene.pushMatrix();
          this.scene.translate(this.x, this.y, this.z);
          this.scene.scale(this.scale, this.scale, this.scale);
  
          var totalDigits = "";
          for (var i = 0; i < this.axiom.length; ++i) {
              switch (this.axiom[i]) {
                  case "[": // push                    
                      this.scene.pushMatrix();
                      break;
                  case "]":// pop                    
                      this.scene.popMatrix();
                      break;
                  case "+": // turn left 
                      this.scene.rotate(this.getAngleFromAxiom(totalDigits), 0, 0, 1);
                      totalDigits = "";
                      break;
                  case "-": // turn right                    
                      this.scene.rotate(-this.getAngleFromAxiom(totalDigits), 0, 0, 1);
                      totalDigits = "";
                      break;
                  case "\\": //rotate positive on xx axis
                      this.scene.rotate(this.getAngleFromAxiom(totalDigits), 1, 0, 0);
                      totalDigits = "";
                      break;
                  case "/": //rotate negative on xx axis
                      this.scene.rotate(-this.getAngleFromAxiom(totalDigits), 1, 0, 0);
                      totalDigits = "";
                      break;
                  case "<": //rotate positive on yy axis
                      this.scene.rotate(this.getAngleFromAxiom(totalDigits), 0, 1, 0);
                      totalDigits = "";
                      break;
                  case ">": //rotate negative on yy axis
                      this.scene.rotate(-this.getAngleFromAxiom(totalDigits), 0, 1, 0);
                      totalDigits = "";
                      break;
                  default:
                      {
                          var component = this.grammar[this.axiom[i]];
                          if (component) {
                              component.enableNormals(this.hasNormals);
                              this.scene.scale(this.getScaleFromAxiom(totalDigits), this.getScaleFromAxiom(totalDigits), this.getScaleFromAxiom(totalDigits));
                              component.display();
                              this.scene.translate(0, component.getHeight(), 0);
                              totalDigits = "";
                          }
                          else {
                              totalDigits += this.buildParameter(this.axiom[i]);
                          }
                      }
                      break;
              }
          }
          this.scene.popMatrix();
      }
  
      // Set the animation of the plant based on the delta time between scene updates
      // When the animation is not produces on shaders
      update(deltaTime, speed) {
          this.time += deltaTime * speed;
          var mov = (Math.sin(this.time * (Math.PI / 2) / 1000)) / 5;
          this.x = this.x0 + mov;;
          this.y = this.y0 + mov;
          mov = (Math.sin(this.time * (Math.PI + this.movz / 2) / 1000)) / 5;
          this.z = this.z0 + mov;
      }
  }