xxxxxxxxxx
118
/*
* Draw a logo using a LOGO / TURTLE type language with commands encoded into a bitfield
* the three characters + space are all encoded into a single 32-bit value as a bitfield
* the commands are embedded as 3-bit, commands move and draw in four directions + bottom left diagonal
* there is also one command which move right and assign a color
* it is used to skip to next char by using background color.
*
* The trick is to start from somewhere which has the maximum efficiency in term of number of commands (or type of commands)
* so here i start from somewhere which only require the 'move right but don't draw' instruction to draw
* the whole text, there is still some commands left to do more stuff if required
*
* 8 possible commands in total can be encoded that way and 11 commands can be fetched for every 32-bit values
*
* This one is tinier than the first one by using a single instruction for direction change, drawing and skip command
* it also add state change command to allow line length to be changed
*
* This one widen the lines but know the drawing direction so is able to draw blocky characters
*
* This code can be converted to assembly language for a very efficient way codesize wise to draw a text / logo
* in assembly the function table is turned into some code next to a label which assign registers (could also be swapping)
* and then jump again to draw code, the initial jump (fetch instruction & call) could be done as 2/3 instructions (logic OP, direct short jump)
*/
// no issue on Z on this one due to the introduction of two command to
// draw right and change line length (one block or two)
// there is also a small one line position fix for the last char (dunno if there is better way yet)
let defaultLineLength = 64
let blockLength = 64/3
let lineLength = defaultLineLength
let lineWeight = 64 / 3
let defaultBrightness = 255
// a series of commands as 32-bit values to draw whatever
let stack = [
1579028180
]
let index = 0
let e = 1
let lastG = 0
let last = 1
let dummyf = () => { return { x: 0, y: 0 } }
// the jump table where each entry would basically be a label + simple register allocation for x,y with ASM code for example
let f = null
function computeStartPosition() {
// compute start drawing position given as a precomputed index (a single instruction on x86)
let x = width / 2 - lineLength * 5 / 2 + lineLength
let y = height / 2 + lineLength / 2
// fix the pixels to the grid
x = Math.floor(x)
y = Math.floor(y)
index = (x + y * width) * 4
//
}
function initializeJumpTable() {
f = [
() => { return 1 }, // move right (positioned here to act as a don't draw but move right command)
() => { index -= width * 4; lineLength = blockLength; e = width; return 1 }, // change line length and move right
() => { e = width; return -1 }, // move left
() => { e = -1; return width }, // move down
() => { e = 1; return -width }, // move up
() => { e = 1; return width },
() => { lineLength = blockLength * 2; e = width; return 1 }, // change line length and move right
() => { e = width; return 1 } // move right
]
}
function setup() {
createCanvas(512, 512);
background(0);
computeStartPosition()
initializeJumpTable()
drawOnce()
}
function drawOnce() {
loadPixels()
let r = -1
while (v = stack.pop()) {
for (let l = 0; l < 11; l += 1) {
let g = v & 7 // get the virtual 'instruction'
lineLength = defaultLineLength // reset line length for each commands (save space)
last = abs(r) > 1 ? -Math.sign(r) : Math.sign(r)
r = f[g]() // basically a jump table: jmp g
// abuse saturation so that the first command is a 'draw with color 0' command
let c = g << 8
// draw segment based on the instructions result
for (let i = 0; i < lineLength; i += 1) {
for (let j = 0; j < lineWeight; j += 1) {
let index2 = index - j * 4 + width * 4 * j
pixels[index2 + 0] = c
pixels[index2 + 1] = c
pixels[index2 + 2] = c
}
index += r * 4
}
v >>= 3 // move to the next instruction
}
}
updatePixels()
}
function draw() {
}