{
const {
imageMesh,
channelMesh,
outputMesh,
convs,
sums,
min,
max,
outputBuf
} = outputs
const offscreen = new OffscreenCanvas(32, 32)
const octx = offscreen.getContext('2d')
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = 256
const ctx = canvas.getContext('2d', { willReadFrequently: true })
const outputIm = ctx.createImageData(32, 32)
for (let i = 0; i < outputBuf.length; i += 4) {
for (let j = 0; j < 4; j++) {
outputIm.data[i + j] = outputBuf[i + j] * 255
}
}
octx.putImageData(outputIm, 0, 0)
const clearConnectors = () => {
for (let i = tubeGroup.children.length - 1; i >= 0; i--) {
const child = tubeGroup.children[i]
child.geometry.dispose()
child.material.dispose()
tubeGroup.remove(child)
}
for (let i = sumGroup.children.length - 1; i >= 0; i--) {
const child = sumGroup.children[i]
child.geometry.dispose()
child.material.dispose()
sumGroup.remove(child)
}
}
const drawImages = (kx, ky) => {
ctx.clearRect(0, 0, 256 * 2, 256)
ctx.lineWidth = 1
ctx.drawImage(image, 0, 0, 256, 256)
ctx.strokeStyle = `rgba(0, 0, 0, 0.5)`
for (let x = 0; x < image.width; x++) {
ctx.beginPath()
ctx.moveTo(x * 256 / 32, 0)
ctx.lineTo(x * 256 / 32, 256)
ctx.stroke()
}
for (let y = 0; y < image.height; y++) {
ctx.beginPath()
ctx.moveTo(0, y * 256 / 32)
ctx.lineTo(256, y * 256 / 32)
ctx.stroke()
}
ctx.drawImage(offscreen, 256, 0, 256, 256)
for (let x = 0; x < image.width; x++) {
ctx.beginPath()
ctx.moveTo(x * 256 / 32 + 256, 0)
ctx.lineTo(x * 256 / 32 + 256, 256)
ctx.stroke()
}
for (let y = 0; y < image.height; y++) {
ctx.beginPath()
ctx.moveTo(0 + 256, y * 256 / 32)
ctx.lineTo(256 + 256, y * 256 / 32)
ctx.stroke()
}
}
drawImages()
// reusable
const matrix = new THREE.Matrix4()
const vec3 = new THREE.Vector3()
const color = new THREE.Color()
// for matrix decomposition
const pos = new THREE.Vector3()
const quat = new THREE.Quaternion()
const scale = new THREE.Vector3()
let prev = {
index: null,
inputPxColor: new THREE.Color(),
outputPxColor: new THREE.Color(),
}
ctx.canvas.addEventListener('pointermove', e => {
const rect = ctx.canvas.getBoundingClientRect()
const x = Math.floor((e.clientX - rect.left) / 256 * 32)
const y = Math.floor((e.clientY - rect.top) / 256 * 32)
if (x >= 32 || y >= 32) {
return;
}
const index = x + y * 32;
if (index === prev.index) {
return;
}
clearConnectors()
if (prev.index !== null) {
imageMesh.setColorAt(prev.index, prev.inputPxColor)
outputMesh.setColorAt(prev.index, prev.outputPxColor)
}
prev.index = index
// only works when input image is same size as output image
imageMesh.getColorAt(index, prev.inputPxColor)
outputMesh.getColorAt(index, prev.outputPxColor)
const imagePos = new THREE.Vector3()
imageMesh.getMatrixAt(index, matrix)
matrix.decompose(imagePos, quat, scale)
imagePos.add(imageGroup.position)
const outputPos = new THREE.Vector3()
outputMesh.getMatrixAt(index, matrix)
matrix.decompose(outputPos, quat, scale)
outputPos.add(outputGroup.position)
// we could definitely optimize the creation of geoms/mats below,
// but this is performant enough for a demo
const sumPositions = []
const sumGeom = new THREE.BoxGeometry(kernel_dims[0], kernel_dims[1], boxSize)
const rgb = outputBuf.slice(index * 4, index * 4 + 3)
rgb.forEach((c, i) => {
const pos = new THREE.Vector3(
util.lerp(0, 3 - 1, i, ...x_range) + padded_image_dims[1] / 2,
padded_image_dims[1] / 2,
15
).add(depthGroup.position)
const rgb = [0, 0, 0]
rgb[i] = c
const sumMat = new THREE.MeshBasicMaterial({ color: color.setRGB(...rgb) })
const sum = new THREE.Mesh(sumGeom, sumMat)
sum.position.copy(pos)
sumPositions.push(sum.position.clone())
sumGroup.add(sum)
const pts = [
pos,
new THREE.Vector3(pos.x, pos.y, pos.z + 1),
util.pointAt(pos, outputPos, 0.5),
new THREE.Vector3(outputPos.x, outputPos.y, outputPos.z - 1),
outputPos
]
const curve = new THREE.CatmullRomCurve3(pts, false, 'centripetal', 0.5)
const geom = new THREE.TubeBufferGeometry(curve, 48, 0.2, 8, false)
const mat = new THREE.MeshBasicMaterial({ color })
tubeGroup.add(new THREE.Mesh(geom, mat))
})
let kernelIndices = []
for (let wx = x; wx < x + kernel_dims[0]; wx++) {
for (let wy = y; wy < y + kernel_dims[1]; wy++) {
const i = (wx * 3) + (wy * 3 * padded_image_dims[0])
kernelIndices.push(i + 0, i + 1, i + 2)
}
}
for (let i = 0; i < kernelIndices.length; i++) {
channelMesh.getColorAt(kernelIndices[i], color)
channelMesh.getMatrixAt(kernelIndices[i], matrix)
matrix.decompose(pos, quat, scale)
pos.add(depthGroup.position)
const sumPos = sumPositions[i % 3]
// could just create these once and cache them, since
// they have the same curves/positions, just offset...
{
const pts = [
imagePos,
new THREE.Vector3(imagePos.x, imagePos.y, imagePos.z + 1),
util.pointAt(imagePos, pos, 0.5),
new THREE.Vector3(pos.x, pos.y, pos.z - 2),
pos, // end
]
const curve = new THREE.CatmullRomCurve3(pts, false, 'centripetal', 0.5)
const geom = new THREE.TubeBufferGeometry(curve, 48, 0.05, 8, false)
const mat = new THREE.MeshBasicMaterial({ color })
tubeGroup.add(new THREE.Mesh(geom, mat))
}
{
const pts = [
pos,
new THREE.Vector3(pos.x, pos.y, pos.z + 1),
util.pointAt(pos, sumPos, 0.5),
new THREE.Vector3(sumPos.x, sumPos.y, sumPos.z - 1),
sumPos
]
const curve = new THREE.CatmullRomCurve3(pts, false, 'centripetal', 0.5)
const geom = new THREE.TubeBufferGeometry(curve, 48, 0.05, 8, false)
const mat = new THREE.MeshBasicMaterial({ color })
tubeGroup.add(new THREE.Mesh(geom, mat))
}
}
imageMesh.setColorAt(index, color.setRGB(1, 1, 1))
imageMesh.instanceColor.needsUpdate = true
outputMesh.setColorAt(index, color)
outputMesh.instanceColor.needsUpdate = true
channelMesh.instanceColor.needsUpdate = true;
drawImages(x, y)
const d = ctx.getImageData(x / 32 * 256, y / 32 * 256, 1, 1).data;
ctx.fillStyle = `rgb(${d[0]}, ${d[1]}, ${d[2]})`
ctx.fillRect(512, 0, 256, 256)
ctx.fillStyle = '#fff'
ctx.fillRect(x / 32 * 256, y / 32 * 256, 256 / 32, 256 / 32)
})
ctx.canvas.addEventListener('pointerout', () => {
clearConnectors()
drawImages()
imageMesh.setColorAt(prev.index, prev.inputPxColor)
imageMesh.instanceColor.needsUpdate = true
outputMesh.setColorAt(prev.index, prev.outputPxColor)
outputMesh.instanceColor.needsUpdate = true
})
return ctx.canvas
}