Technical details of my small programs
Contents
Introduction
Articles documenting my tiny graphics programs,
best read in order (for Linux) to see progression.
Most release names are taken from the Star Control
world, one of my favorite adventure game !
Sources can be found here.
Guides
Articles
RNG
A pseudorandom
number generator might be useful early on, check out this thread about
tiny PRNG, another good source is this, i used
this one early on in my Linux programs, on modern x86 (~2012)
RDRAND
instruction is also available, good enough random numbers can also
be sourced from RAM etc. sometimes.
LCG
(MCG especially) are probably the smallest ones, they just require
an addition and multiply if the moduli is a power of two, there is
also Xorshift.
A naive and primitive one i used on Halite is just using a 8
bits register and an addition (can hardly call that a RNG but it
was good enough for me) : r = (r + 159) & 255
Linux (x86, framebuffer)
2020
2021
- Truespace - 256b
- Flagship - 256b
- Campers - 256b
- QuasiSpace - 128b
- Calculating Space - 256b
- Apollonian - 256b
2022
2023
TIC-80
2021
2022
2023
RISC OS / RPI /
Acorn Archimedes (ARM)
2023
DOS (x86, MCGA / VGA)
2024
p5js/tweet
Introduction
JavaScript / p5js code that i
published on my Twitter account, no definite size goal but
generally less than 256 characters.
Code of this category is not very well size optimized, they
are more to show interesting short algorithms that i discovered on
my own or intro effect that i replicated, made them with
accessibility in mind so not much arcane code stuff and it use as
few as possible p5js stuff and lib stuff (software rendering) to be
ported / understood easily.
Rotating
Ortho. Cube (2023, ~230 characters)
no polygons, all integers orthographic
cube render
w=128;f=0;setup=_=>createCanvas(w,w);draw=_=>{background(0);loadPixels();f+=.1;s=sin(f);c=cos(f);for(i=h=64;i--;)for(y=h;y--;)for(x=h;x--;){dx=32-x;dy=32-y;pixels[h+(dx*c-dy*s)+(((h+dx*s+dy*c)/2+i)<<7)<<2]=255-i*4};updatePixels()}
The idea is to show off the algorithm of many intros (i
believe) which render pseudo 3d iso / ortho objects such as
cubes in
very small code, these objects can be built easily without going
the usual way by iterating on an area with center 0,0 and applying
a transform to the x,y; this produce a shape which can be extruded
down (with an additional loop), this method also works with any
complex shapes eg. a labyrinth or a text as long as you generate
them and extrude them.
using logical operators
The shading can be done by using the extrusion loop value and
more fancy shading such as per face shading is also easily possible
by either doing it as a single pass or on a second pass by
rendering the cube into a buffer and using the buffer to isolate
face from pixel color / horizontal position. (or a
combination)
fancy texturing / shading ! (logical operators,
also note the lighting fakery)
composition, stacking and scrolling, almost
like the cool 256b intro Pixel Town by
Digimind,
note here that the shading is only on the side faces, could also be
applied on the upper face which would appear as a cheap
roof
The rendering of multiple objects is also easy with a second
pass, it can be pretty fast also since it is just a bitmap that is
copied over. :)
multiple objects
2d map made of
tiny patterns rendered with isometric projection and with
highly extruded ortho objects, wave is just a shading trick,
still
fairly small
The best way to map the cubes to the screen is to use this
coordinates transform : x = (x - y) and y = (x + y) / 2
To go full 3D from this is not difficult, it will still stay
tiny and fast, just adds 2D rotation to the coordinates, this will
be equivalent to a forward mapping affine renderer (you might have
to downscale the result to remove sampling holes, this can be done
quite easily with scaling; a right arithmetic
shift) and for perspective just add 3D rotation (with pitch
component), texture mapping can also be added easily with the x and
y coordinates and you will get a simple quad forward mapping 3D
renderer at this point, 4-point quadrilaterals can also be made by
adjusting the loop endpoints which would end up being comparable to
a Sega Saturn / 3DO style rasterizer although they also go further
with algorithms to fill the sampling holes, it will be a bit tricky
compared to a triangles based rasterizer but it might do the job in
size limited context or if you don't care about optimal
performances.
Black filled circle (2024, ~122 characters)
x=y=4e4;setup=_=>createCanvas(512,512);draw=_=>{if(x>0)x-=224;for(i=1e4;i--;)point(256+((x+=y>>8)>>8),256+((y-=x>>8)>>8))}
A way to draw a filled circle with the simplest arithmetic
operations (no multiplication, only addition, subtraction and bit
shifting), this use an
integer variant of Minsky circle algorithm (HAKMEM 149) with
adjustment outside the loop to fill the circle (make it
"unstable"), downscale is used to cover the gaps, the circle fill
is noisy if there is no downscale.
This method
is quite tailored to early x86/ARM CPU instructions
because
the core
Minsky circle algorithm and even the downscale can be implemented
in few instructions with movsx
trick (this explain
the shift by 8), shift and
conditional is great on early ARM due to the single instruction
add/sub + barrel shifter (shift) and conditional instructions so
this can be implemented in few instructions on these
CPU.
It is of
course inefficient (slow) with
the major downside that multiple pixels will be revisited if not
cared for.
Can also be
done without the conditional by replacing the conditional code with
:
y -= y >> 8
The cool
thing is that a variable thickness circle (outward or inward; just
a matter of sign) can be done with the exact same code by just
moving the line above in the loop (and a spiral can even be done if
the shift is small !) :
let x = 6e4
let y = 0
for (let i = 0; i < 1e4; i += 1) {
x += y >> 8
y -= x >> 8
y -= y >> 16 // perturb the orbit by very small
amount, the shift adjust the thickness, downscaling may be needed
to fill the gaps
//...
}
Note that the
usual way of drawing a filled circle (see below) is already quite
small by looping over a rectangular portion of the screen and
checking if the current point is inside or outside the circle (or
doing it by spans) but it require multiplication.
Here is a
more conventional way (but still tiny !) to draw a filled circle,
color (c) is based on distance from center so not uniform,
s2 is half radius and sl is a shift value that
control the circle radius (shortcut to
avoid more operations; only works
with power of two radius), circle radius can of course be
controlled in a finer way by replacing the shift with a division or
a mul and the two loops could be broken into a single one
:
for (let y = 0; y < s; y += 1)
{
for (let x = 0; x < s; x += 1) {
let cx = x - s2
let cy = y - s2
let c = (cx * cx + cy * cy) >> sl
let i = (x + (y << 9)) << 2 //
512x512 canvas (= explain the shifts)
// full white there means that the point lie
outside the circle radius (so just needs a conditional + constant
for an uniform color circle)
// could be done with single conditional
instruction like a cmov on x86 CPU
pixels[i + 0] = c
pixels[i + 1] = c
pixels[i + 2] = c
}
}
back to top