REVDANCATT'S GENUARY 2023

05 - Debug view

/* global preloadImagesTmr fxhash fxrand */

//
//  fxhash - Genuary 05 Debug view
//
//
//  HELLO!! Code is copyright revdancatt (that's me), so no sneaky using it for your
//  NFT projects.
//  But please feel free to unpick it, and ask me questions. A quick note, this is written
//  as an artist, which is a slightly different (and more storytelling way) of writing
//  code, than if this was an engineering project. I've tried to keep it somewhat readable
//  rather than doing clever shortcuts, that are cool, but harder for people to understand.
//
//  You can find me at...
//  https://twitter.com/revdancatt
//  https://instagram.com/revdancatt
//  https://youtube.com/revdancatt
//

const ratio = 1
// const startTime = new Date().getTime() // so we can figure out how long since the scene started
let drawn = false
let highRes = false // display high or low res
const features = {}
let resizeTmr = null
const aniFrame = null

window.$fxhashFeatures = {}

//  Work out what all our features are
const makeFeatures = () => {
  // We are going to have a random number of lines from 5 to 9 on each side plus 1
  const lineCount = (Math.floor(fxrand() * 4) + 5) * 2 + 1
  // The lines run from -1 to 1, so we need to work out the step size
  features.step = 2 / (lineCount - 1)
  // Now make the lines
  features.lines = []
  for (let i = 0; i < lineCount; i++) {
    const thisLine = {
      p1: {
        x: -1 + (features.step * i),
        y: -2
      },
      p2: {
        x: -1 + (features.step * i),
        y: 2
      }
    }
    features.lines.push(thisLine)
  }
  // I want to bunch some of the ends up to bring them closer together
  const scaleFactor = fxrand() * 0.33 + 0.33
  for (let i = 0; i < features.lines.length; i++) features.lines[i].p2.x *= scaleFactor

  // Now we want to put blocks between the lines. So we'll need to create a step value
  // and then loop through the lines, ignoring the last one, and creating a block
  // between each line
  const maxBlocks = 25
  const blockSize = (features.lines[0].p2.y - features.lines[0].p1.y) / maxBlocks
  features.blocks = []
  for (let i = 0; i < features.lines.length - 1; i++) {
    // Now loop through the number of blocks we have
    for (let b = 0; b < maxBlocks; b++) {
      // There is a chance to place a block here
      const newBlock = {
        firstLine: i,
        secondLine: i + 1,
        top: b * blockSize,
        bottom: (b + 1) * blockSize,
        type: 'dot'
      }
      if (fxrand() < 0.3) newBlock.type = 'grrrrrrid'
      if (fxrand() < 0.3) newBlock.type = 'block'
      if (fxrand() < 0.3) newBlock.type = 'lines'
      features.blocks.push(newBlock)
    }
  }
  // Work out by what we're going to rotate the canvas by
  features.rotation = fxrand() * 360

  features.colours = {
    background: '#191919',
    lines: '#ffffff',
    block: 'cyan',
    dot: 'magenta',
    blockLines: '#CCCCCC',
    grrrrrrid: 'yellow'
  }
  if (fxrand() < 0.333) {
    features.colours.background = '#ffffff'
    features.colours.lines = '#191919'
    features.colours.block = 'red'
    features.colours.dot = 'magenta'
    features.colours.blockLines = 'black'
    features.colours.grrrrrrid = 'blue'
  }
}

//  Call the above make features, so we'll have the window.$fxhashFeatures available
//  for fxhash
makeFeatures()

console.log(features)
console.table(window.$fxhashFeatures)

const init = async () => {
  //  I should add a timer to this, but really how often to people who aren't
  //  the developer resize stuff all the time. Stick it in a digital frame and
  //  have done with it!
  window.addEventListener('resize', async () => {
    //  If we do resize though, work out the new size...
    clearTimeout(resizeTmr)
    resizeTmr = setTimeout(async () => {
      await layoutCanvas()
    }, 100)
  })

  //  Now layout the canvas
  await layoutCanvas()
}

const layoutCanvas = async () => {
  const wWidth = window.innerWidth
  const wHeight = window.innerHeight
  let cWidth = wWidth
  let cHeight = cWidth * ratio
  if (cHeight > wHeight) {
    cHeight = wHeight
    cWidth = wHeight / ratio
  }
  const canvas = document.getElementById('target')
  if (highRes) {
    canvas.height = 8192
    canvas.width = 8192 / ratio
  } else {
    canvas.width = Math.min((8192 / 2), cWidth * 2)
    canvas.height = Math.min((8192 / ratio / 2), cHeight * 2)
    //  Minimum size to be half of the high rez cersion
    if (Math.min(canvas.width, canvas.height) < 8192 / 2) {
      if (canvas.width < canvas.height) {
        canvas.height = 8192 / 2
        canvas.width = 8192 / 2 / ratio
      } else {
        canvas.width = 8192 / 2
        canvas.height = 8192 / 2 / ratio
      }
    }
  }

  canvas.style.position = 'absolute'
  canvas.style.width = `${cWidth}px`
  canvas.style.height = `${cHeight}px`
  canvas.style.left = `${(wWidth - cWidth) / 2}px`
  canvas.style.top = `${(wHeight - cHeight) / 2}px`

  //  And draw it!!
  drawCanvas()
}

const scaleBlock = (block, scale) => {
  // translate all the corners by the middle
  // (We could do this all in a handy transformation matrix, but this way is somehow more readable)
  block.topLeft.x -= block.middle.x
  block.topLeft.y -= block.middle.y
  block.bottomLeft.x -= block.middle.x
  block.bottomLeft.y -= block.middle.y
  block.topRight.x -= block.middle.x
  block.topRight.y -= block.middle.y
  block.bottomRight.x -= block.middle.x
  block.bottomRight.y -= block.middle.y

  // Now we multiply all the points by the scale
  block.topLeft.x *= scale
  block.topLeft.y *= scale
  block.bottomLeft.x *= scale
  block.bottomLeft.y *= scale
  block.topRight.x *= scale
  block.topRight.y *= scale
  block.bottomRight.x *= scale
  block.bottomRight.y *= scale

  // Finally we now shift them all back
  block.topLeft.x += block.middle.x
  block.topLeft.y += block.middle.y
  block.bottomLeft.x += block.middle.x
  block.bottomLeft.y += block.middle.y
  block.topRight.x += block.middle.x
  block.topRight.y += block.middle.y
  block.bottomRight.x += block.middle.x
  block.bottomRight.y += block.middle.y

  return block
}

const drawCanvas = async () => {
  // Clear the animation frame
  if (aniFrame) window.cancelAnimationFrame(aniFrame)
  //  Let the preloader know that we've hit this function at least once
  drawn = true

  const canvas = document.getElementById('target')
  const ctx = canvas.getContext('2d')
  const w = canvas.width
  const h = canvas.height

  // fill the background in black
  ctx.fillStyle = features.colours.background
  ctx.fillRect(0, 0, w, h)
  // Now draw the lines
  ctx.strokeStyle = features.colours.lines
  ctx.lineWidth = w / 500

  // save the canvas state
  ctx.save()
  // translate the canvas to the center
  ctx.translate(w / 2, h / 2)
  ctx.rotate((features.rotation * Math.PI) / 180)
  // Loop through the lines
  for (let i = 0; i < features.lines.length; i++) {
    const thisLine = features.lines[i]
    // Draw the line
    ctx.beginPath()
    ctx.moveTo(thisLine.p1.x * w, thisLine.p1.y * h)
    ctx.lineTo(thisLine.p2.x * w, thisLine.p2.y * h)
    ctx.stroke()
  }

  // Now loop thru the blocks
  for (let i = 0; i < features.blocks.length; i++) {
    const thisBlock = features.blocks[i]
    const firstLine = features.lines[thisBlock.firstLine]
    const secondLine = features.lines[thisBlock.secondLine]
    // Now we need to work out the four corners of the block
    // The top left corner is block top distance between firstLine.p1 and firstLine.p2
    let corners = {
      topLeft: {
        x: firstLine.p1.x + (firstLine.p2.x - firstLine.p1.x) * (thisBlock.top / (firstLine.p2.y - firstLine.p1.y)),
        y: firstLine.p1.y + (firstLine.p2.y - firstLine.p1.y) * (thisBlock.top / (firstLine.p2.y - firstLine.p1.y))
      },
      bottomLeft: {
        x: firstLine.p1.x + (firstLine.p2.x - firstLine.p1.x) * (thisBlock.bottom / (firstLine.p2.y - firstLine.p1.y)),
        y: firstLine.p1.y + (firstLine.p2.y - firstLine.p1.y) * (thisBlock.bottom / (firstLine.p2.y - firstLine.p1.y))
      },
      topRight: {
        x: secondLine.p1.x + (secondLine.p2.x - secondLine.p1.x) * (thisBlock.top / (secondLine.p2.y - secondLine.p1.y)),
        y: secondLine.p1.y + (secondLine.p2.y - secondLine.p1.y) * (thisBlock.top / (secondLine.p2.y - secondLine.p1.y))
      },
      bottomRight: {
        x: secondLine.p1.x + (secondLine.p2.x - secondLine.p1.x) * (thisBlock.bottom / (secondLine.p2.y - secondLine.p1.y)),
        y: secondLine.p1.y + (secondLine.p2.y - secondLine.p1.y) * (thisBlock.bottom / (secondLine.p2.y - secondLine.p1.y))
      }
    }

    // Work out the middle point of the corners
    corners.middle = {
      x: corners.topLeft.x + ((corners.bottomRight.x - corners.topLeft.x) / 2),
      y: corners.topLeft.y + ((corners.bottomRight.y - corners.topLeft.y) / 2)
    }

    // Now we need to scale the block, but only if it's not lines
    if (thisBlock.type !== 'lines') corners = scaleBlock(corners, 0.8)

    // Now draw the block
    if (thisBlock.type === 'block') {
      ctx.strokeStyle = features.colours.block
      ctx.beginPath()
      ctx.moveTo(corners.topLeft.x * w, corners.topLeft.y * h)
      ctx.lineTo(corners.topRight.x * w, corners.topRight.y * h)
      ctx.lineTo(corners.bottomRight.x * w, corners.bottomRight.y * h)
      ctx.lineTo(corners.bottomLeft.x * w, corners.bottomLeft.y * h)
      ctx.lineTo(corners.topLeft.x * w, corners.topLeft.y * h)
      ctx.stroke()
    }

    // If we are drawing a grrrrrrid, then we need to break down the block into 10 by 10 points
    if (thisBlock.type === 'grrrrrrid') {
      ctx.fillStyle = features.colours.grrrrrrid
      const points = 10
      for (let y = 0; y <= points; y++) {
        const yPercent = y / points
        for (let x = 0; x <= points; x++) {
          const xPercent = x / points
          const topMiddlePoint = {
            x: corners.topLeft.x + (corners.topRight.x - corners.topLeft.x) * xPercent,
            y: corners.topLeft.y + (corners.topRight.y - corners.topLeft.y) * xPercent
          }
          const bottomMiddlePoint = {
            x: corners.bottomLeft.x + (corners.bottomRight.x - corners.bottomLeft.x) * xPercent,
            y: corners.bottomLeft.y + (corners.bottomRight.y - corners.bottomLeft.y) * xPercent
          }
          const thisPoint = {
            x: topMiddlePoint.x + (bottomMiddlePoint.x - topMiddlePoint.x) * yPercent,
            y: topMiddlePoint.y + (bottomMiddlePoint.y - topMiddlePoint.y) * yPercent
          }
          // Draw a yellow dot at this point
          ctx.beginPath()
          ctx.arc(thisPoint.x * w, thisPoint.y * h, w / 500, 0, 2 * Math.PI)
          ctx.fill()
        }
      }
    }

    // If we are drawing lines, then we'll draw 50 lines between the corners
    if (thisBlock.type === 'lines') {
      ctx.strokeStyle = features.colours.blockLines
      ctx.lineWidth = w / 2000
      ctx.beginPath()
      const lines = 50
      for (let l = 0; l < lines; l++) {
        const linePercent = l / lines
        // work out the start point along the left side
        const leftPoint = {
          x: corners.topLeft.x + (corners.bottomLeft.x - corners.topLeft.x) * linePercent,
          y: corners.topLeft.y + (corners.bottomLeft.y - corners.topLeft.y) * linePercent
        }
        // work out the end point along the right side
        const rightPoint = {
          x: corners.topRight.x + (corners.bottomRight.x - corners.topRight.x) * linePercent,
          y: corners.topRight.y + (corners.bottomRight.y - corners.topRight.y) * linePercent
        }
        // Draw a line between the two points
        ctx.moveTo(leftPoint.x * w, leftPoint.y * h)
        ctx.lineTo(rightPoint.x * w, rightPoint.y * h)
      }
      ctx.stroke()
    }

    // Draw a red dot
    ctx.fillStyle = features.colours.dot
    ctx.beginPath()
    ctx.arc(corners.middle.x * w, corners.middle.y * h, w / 250, 0, 2 * Math.PI)
    ctx.fill()
  }
  // Call the draw function again
  // aniFrame = window.requestAnimationFrame(drawCanvas)
}

const autoDownloadCanvas = async (showHash = false) => {
  const element = document.createElement('a')
  element.setAttribute('download', `Genuary_05_Debug_View_${fxhash}`)
  element.style.display = 'none'
  document.body.appendChild(element)
  let imageBlob = null
  imageBlob = await new Promise(resolve => document.getElementById('target').toBlob(resolve, 'image/png'))
  element.setAttribute('href', window.URL.createObjectURL(imageBlob, {
    type: 'image/png'
  }))
  element.click()
  document.body.removeChild(element)
}

//  KEY PRESSED OF DOOM
document.addEventListener('keypress', async (e) => {
  e = e || window.event
  // Save
  if (e.key === 's') autoDownloadCanvas()

  //   Toggle highres mode
  if (e.key === 'h') {
    highRes = !highRes
    console.log('Highres mode is now', highRes)
    await layoutCanvas()
  }
})

//  This preloads the images so we can get access to them
// eslint-disable-next-line no-unused-vars
const preloadImages = () => {
  if (!drawn) {
    clearInterval(preloadImagesTmr)
    init()
  }
}

Project files


Home
Changelog
Page created in: 1ms