sebassdc
5/30/2018 - 11:22 AM

lindermayer.js

const degToRad = deg => (deg / 180) * Math.PI
const radToDeg = rad => rad * 180 / Math.PI;

class Turtle {
  constructor(ctx) {
    this.ctx = ctx
    // Set initial orientation and position
    this.orientation = 0;
    this.position = {x: 0, y: 0}
  }

  // Make a cartesian cordinate system
  _centerCoords() {
    this.ctx.translate(this.ctx.canvas.width / 2, this.ctx.canvas.height / 2);
    this.ctx.transform(1, 0, 0, -1, 0, 0);
  }

  _clearContext() {
    this.ctx.save();
    this.ctx.setTransform(1,0,0,1,0,0);
    this.ctx.clearRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height);
    this.ctx.restore();
  }

  goto(x, y){
    this.position = { x, y }
  }

  fd(length) {
    // Ctx stuff
    this.ctx.save()
    this._centerCoords()
    this.ctx.beginPath()
    
    // Calculate new position
    const { x, y } = this.position
    const newX = x + Math.sin(this.orientation) * length
    const newY = y + Math.cos(this.orientation) * length

    // The thing
    this.ctx.moveTo(x,y) // Move the turtle to the current position
    this.ctx.lineTo(newX, newY)

    //Store new position
    this.position = {
      x: newX,
      y: newY,
    }
    this.ctx.stroke(); // Draw
    this.ctx.restore(); // Get old context mode
  }
  bk(length) {
    // Ctx stuff
    this.ctx.save()
    this._centerCoords()
    this.ctx.beginPath()
    
    // Calculate new position
    const { x, y } = this.position
    const newX = x + Math.sin(this.orientation + degToRad(180)) * length
    const newY = y + Math.cos(this.orientation + degToRad(180)) * length

    // The thing
    console.log(newX, newY)
    this.ctx.moveTo(x,y) // Move the turtle to the current position
    this.ctx.lineTo(newX, newY)

    //Store new position
    this.position = {
      x: newX,
      y: newY,
    }
    this.ctx.stroke(); // Draw
    this.ctx.restore(); // Get old context mode
  }
  rt(angle) {
    this.orientation += degToRad(angle)
  }
  lt(angle) {
    this.orientation -= degToRad(angle)
  }
}

/// range(5) -> [0, 1, 2, 3, 4]
const range = n => [...Array(n).keys()]

const genSystem = ({
  inicio = '',
  variables = [],
  constantes = [],
  reglas = {},
}) => n =>
  range(n+1)
    .reduce(
      (acc, actu, index, arr) => [
        ...acc,
        acc[index]
          .split('')
          .map(e => (constantes.indexOf(e) === -1) ? reglas[e] : e)
          .join('')
      ],
      [inicio]
    )[n]

const genDrawer = ({turtle, angle, length, descriptor}) =>
  Object
    .keys(descriptor)
    .map(e => () =>
      turtle[descriptor[e]](
        descriptor[e] == 'rt' || descriptor[e] == 'lt'
          ? angle
          : length
      )
    )
    .reduce(
      (acc, actu, index, arr) => ({
        ...acc,
        [Object.keys(descriptor)[index]]: actu,
      }),
      {}
    )


const drawSystem = ({sys, n, ...rest}) => {
  const drawer = genDrawer(rest)
  sys(n)
    .split('')
    .forEach(e =>
      (drawer[e] || (()=>{}))()
    )
}

const genSystemDrawer = ({
  descriptor,
  sys,
  angle,
}) => turtle => iterStuff => {
  drawSystem({
    sys,
    turtle,
    descriptor,
    angle,
    ...iterStuff
  })
}

const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
const bob = new Turtle(ctx)

const standartDescriptor =  {
  'F': 'fd',
  '+': 'rt',
  '-': 'lt'
}

const kotchCurve90 = genSystemDrawer({
  angle: 90,
  sys: genSystem({
    variables: ['F'],
    constantes: ['+', '-'],
    reglas: {
      'F': 'F-F+F+F-F',
    },
    inicio: 'F'
  }),
  descriptor: standartDescriptor,
})
const kotchCurve60 = genSystemDrawer({
  angle: 60,
  sys: genSystem({
    variables: ['F'],
    constantes: ['+', '-'],
    reglas: {
      'F': 'F+F--F+F',
    },
    inicio: 'F'
  }),
  descriptor: standartDescriptor
})
const curve32Seg = genSystemDrawer({
  angle: 90,
  sys: genSystem({
    variables: ['F'],
    constantes: ['+', '-'],
    reglas: {
      'F': '-F+F-F-F+F+FF-F+F+FF+F-F-FF+FF-FF+F+F-FF-F-F+FF-F-F+F+F-F+'
    },
    inicio: 'F',
  }),
  descriptor: standartDescriptor,
})
const hilbertCurve = genSystemDrawer({
  angle: 90,
  sys: genSystem({
    variables: ['L', 'R'],
    constantes: ['+', '-', 'F'],
    reglas: {
      'L': '+RF-LFL-FR+',
      'R': '-LF+RFR+FL-'
    },
    inicio: 'L',
  }),
  descriptor: standartDescriptor,
})
const sierpinskiTriangle = genSystemDrawer({
  angle: 120,
  sys: genSystem({
    variables: ['F', 'G'],
    constantes: ['+', '-'],
    inicio: 'F-G-G',
    reglas: {
      'F': 'F-G+F+G-F',
      'G': 'GG'
    },
  }),
  descriptor: {
    'F': 'fd',
    'G': 'fd',
    '+': 'lt',
    '-': 'rt',
  },
})
const sierpinskiCurve = genSystemDrawer({
  angle: 60,
  sys: genSystem({
    variables: ['A', 'B'],
    constantes: ['+', '-'],
    inicio: 'A',
    reglas: {
      'A': 'B-A-B',
      'B': 'A+B+A'
    },
  }),
  descriptor: {
    'A': 'fd',
    'B': 'fd',
    '+': 'lt',
    '-': 'rt',
  },
})
const dragonCurve = genSystemDrawer({
  sys: genSystem({
    inicio: 'FX',
    variables: ['X', 'Y'],
    constantes: ['F', '+', '-'],
    reglas: {
      'X': 'X+YF+',
      'Y': '-FX-Y',
    }
  }),
  angle: 90,
  descriptor: standartDescriptor,
})

const peanoGosperCurve = genSystemDrawer({
  sys: genSystem({
    inicio: 'X',
    variables: ['X', 'Y'],
    constantes: ['F', '+', '-'],
    reglas: {
      'X': 'X+YF++YF-FX--FXFX-YF+',
      'Y': '-FX+YFYF++YF+FX--FX-Y',
    }
  }),
  angle: 60,
  descriptor: standartDescriptor,
})
const peanoCurve = genSystemDrawer({
  sys: genSystem({
    inicio: 'F',
    variables: ['F'],
    constantes: ['+', '-'],
    reglas: {
      'F': 'F+F-F-F-F+F+F+F-F',
    }
  }),
  angle: 90,
  descriptor: standartDescriptor,
})
const quadraticKochIsland = genSystemDrawer({
  sys: genSystem({
    inicio: 'F-F-F-F',
    variables: ['F'],
    constantes: ['+', '-'],
    reglas: {
      'F': 'F-F+F+FFF-F-F+F',
    }
  }),
  angle: 90,
  descriptor: standartDescriptor,
})
const squareCurve = genSystemDrawer({
  sys: genSystem({
    inicio: 'F+XF+F+XF',
    variables: ['X'],
    constantes: ['+', '-', 'F'],
    reglas: {
      'X': 'XF-F+F-XF+F+XF-F+F-X',
    }
  }),
  angle: 90,
  descriptor: standartDescriptor,
})

bob.goto(0, 300)
bob.rt(90)

squareCurve(bob)({
  n: 5,
  length: 4.7,
})