04 - Intersections
/* global preloadImagesTmr fxhash fxrand */
//
// fxhash - Genuary 04 Intersections
//
//
// 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 = () => {
// For this project we will be setting the origin x to the middle of the
// canvas and the origin y to the bottom of the canvas
// We have three vanishing points, one to the right, one to the left and one above everything
features.rightVPoint = {
x: 1.5,
y: 1
}
features.leftVPoint = {
x: -1.5,
y: 1
}
features.topVPoint = {
x: 0,
y: -5
}
// Now we are going to have an array of "blocks" that we will draw
features.blocks = []
// And we need to decide how many we are going to have
// We're going to have a total volume of boxes
const maxVolume = 0.02
let currentVolume = 0
let escape = 0
// Now let's work out the size of each block
while (currentVolume < maxVolume && escape < 200) {
// create a starting point for the block in the x range of -0.4 to 0.4,
// and a y from 0.1 to 0.8
const newBlock = {
x: fxrand() * 0.8 - 0.4,
y: fxrand() * 0.8 + 0.1,
height: fxrand() * 0.15 + 0.05,
rightWidth: fxrand() * 0.15 + 0.05,
leftWidth: fxrand() * 0.15 + 0.05
}
if (fxrand() < 0.1) newBlock.height *= 0.1
if (fxrand() < 0.1) newBlock.rightWidth *= 0.1
if (fxrand() < 0.1) newBlock.leftWidth *= 0.1
if (fxrand() > 0.9) newBlock.height *= 2
if (fxrand() > 0.9) newBlock.rightWidth *= 2
if (fxrand() > 0.9) newBlock.leftWidth *= 2
const volume = newBlock.height * newBlock.rightWidth * newBlock.leftWidth
currentVolume += volume
features.blocks.push(newBlock)
escape++
}
}
// 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 vertProject = (p1, p2) => {
const m = (p1.y - p2.y) / (p1.x - p2.x)
const b = p2.y - m * p2.x
const x1 = (100 - b) / m
const x2 = (-100 - b) / m
const y1 = m * x1 + b
const y2 = m * x2 + b
return {
p1: {
x: x1,
y: y1
},
p2: {
x: x2,
y: y2
}
}
}
const horizProject = (p1, p2) => {
const m = (p1.y - p2.y) / (p1.x - p2.x)
const b = p2.y - m * p2.x
const y1 = m * 100 + b
const y2 = m * -100 + b
const x1 = (y1 - b) / m
const x2 = (y2 - b) / m
return {
p1: {
x: x1,
y: y1
},
p2: {
x: x2,
y: y2
}
}
}
// We need a function that gives us the intersection of two lines
const lineIntersect = (line1, line2) => {
const x11 = line1.p1.x
const y11 = line1.p1.y
const x21 = line1.p2.x
const y21 = line1.p2.y
const x12 = line2.p1.x
const y12 = line2.p1.y
const x22 = line2.p2.x
const y22 = line2.p2.y
const m1 = (y21 - y11) / (x21 - x11)
const b1 = y11 - m1 * x11
const m2 = (y22 - y12) / (x22 - x12)
const b2 = y12 - m2 * x12
const x = (b2 - b1) / (m1 - m2)
const y = m1 * x + b1
return { x, y }
}
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 in the background in white
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, w, h)
// Now loop throught the blocks
for (let i = 0; i < features.blocks.length; i++) {
const block = features.blocks[i]
const tvPoint = features.topVPoint
const rvPoint = features.rightVPoint
const lvPoint = features.leftVPoint
/*
//
// NOTE, SOME OF THIS INSTERSECTION STUFF IS SKETCHY, I'M DOING IT
// TWO DIFFERENT WAYS AND I SHOULD CONDENSE IT DOWN TO ONE
//
*/
// We want to work out the points of the block
// we'll start with the top point.
// We need to project a line from the x,y to the top vanishing point
// and then work how how far along that line the height is.
// Start by working out the slope of the line
const middleVerticalSlope = (tvPoint.y - block.y) / (tvPoint.x - block.x)
// Now project along the line by the height relative to the distance of 1
const topPoint = {
x: block.x + block.height / Math.sqrt(1 + middleVerticalSlope * middleVerticalSlope),
y: block.y + middleVerticalSlope * block.height / Math.sqrt(1 + middleVerticalSlope * middleVerticalSlope)
}
// If the topPoint x is > 0 invert the x and y
if (topPoint.x > 0) {
topPoint.x = block.x - (topPoint.x - block.x)
topPoint.y = block.y - (topPoint.y - block.y)
}
// Now we need to work out the right point
// We'll do this by projecting a line from the top point to the right vanishing point
// and then work how how far along that line the rightWidth is.
// Start by working out the slope of the line
const foundationRightSlope = (rvPoint.y - block.y) / (rvPoint.x - block.x)
// Now project along the line by the rightWidth relative to the distance of 1
const rightPointBottom = {
x: block.x + block.rightWidth / Math.sqrt(1 + foundationRightSlope * foundationRightSlope),
y: block.y + foundationRightSlope * block.rightWidth / Math.sqrt(1 + foundationRightSlope * foundationRightSlope)
}
// Now we need to work out the rightPointTop
// This is at the intersection of the line from the top point to the right vanishing point
// and the line from the rightPointBottom to the top vanishing point
// We'll do this by projecting a line from the top point to the right vanishing point
// and then work how how far along that line the rightWidth is.
// Start by working out the slope of the line
const rightSlope = (rvPoint.y - topPoint.y) / (rvPoint.x - topPoint.x)
// Now project along the line by the rightWidth relative to the distance of 1
const rightPointTop = {
x: topPoint.x + block.rightWidth / Math.sqrt(1 + rightSlope * rightSlope),
y: topPoint.y + rightSlope * block.rightWidth / Math.sqrt(1 + rightSlope * rightSlope)
}
// Do the above again for the left point
const foundationLeftSlope = (lvPoint.y - block.y) / (lvPoint.x - block.x)
const leftPointBottom = {
x: block.x - block.leftWidth / Math.sqrt(1 + foundationLeftSlope * foundationLeftSlope),
y: block.y - foundationLeftSlope * block.leftWidth / Math.sqrt(1 + foundationLeftSlope * foundationLeftSlope)
}
const leftSlope = (lvPoint.y - topPoint.y) / (lvPoint.x - topPoint.x)
const leftPointTop = {
x: topPoint.x - block.leftWidth / Math.sqrt(1 + leftSlope * leftSlope),
y: topPoint.y - leftSlope * block.leftWidth / Math.sqrt(1 + leftSlope * leftSlope)
}
const middleLine = vertProject(topPoint, block)
const rightLine = vertProject(rightPointTop, rightPointBottom)
const leftLine = vertProject(leftPointTop, leftPointBottom)
const topRightLine = horizProject(topPoint, rightPointTop)
const bottomRightLine = horizProject(rightPointBottom, block)
const topLeftLine = horizProject(topPoint, leftPointTop)
const bottomLeftLine = horizProject(leftPointBottom, block)
const rightBackTopLine = horizProject(rightPointTop, lvPoint)
const rightBackBottomLine = horizProject(rightPointBottom, lvPoint)
const leftBackTopLine = horizProject(leftPointTop, rvPoint)
const leftBackBottomLine = horizProject(leftPointBottom, rvPoint)
// The backTopPoint is the intersection of the rightBackTopLine and the leftBackTopLine, calculated it here
const backTopPoint = lineIntersect(leftBackTopLine, rightBackTopLine)
const backBottomPoint = lineIntersect(leftBackBottomLine, rightBackBottomLine)
block.fromPoints = [
block,
topPoint,
rightPointBottom,
rightPointTop,
leftPointBottom,
leftPointTop,
backTopPoint,
backBottomPoint
]
block.projectionLines = [
middleLine,
rightLine,
leftLine,
topRightLine,
bottomRightLine,
topLeftLine,
bottomLeftLine,
rightBackTopLine,
rightBackBottomLine,
leftBackTopLine,
leftBackBottomLine
]
block.topPoint = topPoint
block.rightPointBottom = rightPointBottom
block.rightPointTop = rightPointTop
block.leftPointBottom = leftPointBottom
block.leftPointTop = leftPointTop
block.backTopPoint = backTopPoint
block.backBottomPoint = backBottomPoint
}
// we need to translate the canvas so that the origin is in the middle
// of the canvas, and the y axis is pointing up
ctx.save()
ctx.translate(w / 2, h)
ctx.scale(1, -1)
// Draw the projection lines
ctx.lineWidth = w / 1000
ctx.strokeStyle = '#eeeeee'
for (let i = 0; i < features.blocks.length; i++) {
const block = features.blocks[i]
block.projectionLines.forEach(line => {
ctx.moveTo(line.p1.x * w, line.p1.y * h)
ctx.lineTo(line.p2.x * w, line.p2.y * h)
})
}
ctx.stroke()
// Draw the ticks
ctx.lineWidth = w / 500
ctx.strokeStyle = 'red'
ctx.beginPath()
for (let i = 0; i < features.blocks.length; i++) {
const block = features.blocks[i]
// Draw ticks on the fromPoints
block.fromPoints.forEach(fromPoint => {
ctx.moveTo(fromPoint.x * w - (w / 200), fromPoint.y * h - w / 200)
ctx.lineTo(fromPoint.x * w + (w / 200), fromPoint.y * h + w / 200)
ctx.moveTo(fromPoint.x * w - (w / 200), fromPoint.y * h + w / 200)
ctx.lineTo(fromPoint.x * w + (w / 200), fromPoint.y * h - w / 200)
})
}
ctx.stroke()
// Draw the boxes
ctx.lineWidth = w / 500
ctx.strokeStyle = 'black'
ctx.beginPath()
for (let i = 0; i < features.blocks.length; i++) {
const block = features.blocks[i]
ctx.moveTo(block.x * w, block.y * h)
ctx.lineTo(block.topPoint.x * w, block.topPoint.y * h)
ctx.moveTo(block.x * w, block.y * h)
ctx.lineTo(block.rightPointBottom.x * w, block.rightPointBottom.y * h)
ctx.moveTo(block.x * w, block.y * h)
ctx.lineTo(block.leftPointBottom.x * w, block.leftPointBottom.y * h)
ctx.moveTo(block.topPoint.x * w, block.topPoint.y * h)
ctx.lineTo(block.rightPointTop.x * w, block.rightPointTop.y * h)
ctx.moveTo(block.topPoint.x * w, block.topPoint.y * h)
ctx.lineTo(block.leftPointTop.x * w, block.leftPointTop.y * h)
ctx.moveTo(block.rightPointTop.x * w, block.rightPointTop.y * h)
ctx.lineTo(block.rightPointBottom.x * w, block.rightPointBottom.y * h)
ctx.moveTo(block.leftPointTop.x * w, block.leftPointTop.y * h)
ctx.lineTo(block.leftPointBottom.x * w, block.leftPointBottom.y * h)
ctx.moveTo(block.backBottomPoint.x * w, block.backBottomPoint.y * h)
ctx.lineTo(block.rightPointBottom.x * w, block.rightPointBottom.y * h)
ctx.moveTo(block.backBottomPoint.x * w, block.backBottomPoint.y * h)
ctx.lineTo(block.leftPointBottom.x * w, block.leftPointBottom.y * h)
}
ctx.stroke()
// restore the canvas
ctx.restore()
// Call the draw function again
// aniFrame = window.requestAnimationFrame(drawCanvas)
}
const autoDownloadCanvas = async (showHash = false) => {
const element = document.createElement('a')
element.setAttribute('download', `Genuary_04_Intersections_${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()
}
}
Home
Changelog
Page created in: 2ms