======= 2nd line FLI - another approach to twisters, x-rotators and waving carpets =======
by Bitbreaker/Oxyron^Arsenic^Nuance
With FLI we can force another $d018 value per line. However the forced DMA consumes additional 40 cycles, so not much else can be done when displaying FLI. When doing a FLI only every 2nd line we have another 63 cycles (PAL that is) available for doing a lot of fun things during display, like changing sprite registers, background colors, or even do things like updates in data being displayed.
The difference to common FLI is, that you don't use $d018 to flip in different screens for different colors for your bitmap, but use the FLI to change not only the screen but also the charset-pointer. That way you are able to display for e.g. 32 char wide chunks of graphics resembled by 8 half-filled charsets and 4 different screens. That gives us 32 individual chunks per bank. It is possible to extend the number of chunks by using more banks of course, but it is also possible to use other banks for interlacing 2 charsets together to get even smoother gradients. That is what i do in my examples. Feel free to squeeze in even more data by choosing another width and interleaving of screen and charset data.
So a bank could look like:
$0000-$03ff charset 1 ;chunk 0-3
$0400-$07ff screen 1
$0800-$0bff charset 2 ;chunk 4-7
$0c00-$0fff screen 2
$1000-$13ff charset 3 ;chunk 8-11
$1400-$17ff screen 3
$1800-$1bff charset 4 ;chunk 12-15
$1c00-$1fff screen 4
$2000-$23ff charset 5 ;chunk 16-19
$2400-$27ff free
$2800-$2bff charset 6 ;chunk 20-23
$2c00-$2fff free
$3000-$33ff charset 7 ;chunk 24-27
$3400-$37ff free
$3800-$3bff charset 8 ;chunk 28-31
$3c00-$3fff free
====== Twister ======
{{ :base:twister.png?200|}}
Imagine the basic shape behind a (untwisted) twister, do 32 animations substeps to make it rotate by one face and save 2 lines of that shape at that very certain step. With those line fragments gained by that process you can now reassemble any twisted form of the original shape, right?
Now squeeze those fragments into 8 half-filled charsets, from which each $0100 bytes resemble a 32 char wide chunk (see mapping above). Also fill the chars repeatedly in y-direction.
When we now select charset 0 via $d018 and also select a screen as source that contains the chars $00..$1f repeatedly in every line, we will display the first line fragment, whenever we switch $d018 for e.g. to $10.
So that is what we would have on the screen for a single line fragment:
{{:base:screen.png|}}
And what it looks like with charset enabled:
{{:base:charset.png|}}
Here's some c-code that will spit out the fakeshaded segments of a 8-sided shape as two interlaced charsets with 8 dithersteps:
#include
#include
#include
#define PI atan2 (0.0, -1.0)
#define RADIUS 64.0
#define DITHERSTEPS 8
#define SHADE (DITHERSTEPS * 3 + 1)
#define DEGREE (2.0 * PI / 360.0)
//our nicely interlaced dither patterns
static const uint8_t dither_patterns[9][8][4] = {
{
{0, 0, 0, 0}, {0, 0, 0, 0},
{0, 0, 0, 0}, {0, 0, 0, 0},
{0, 0, 0, 0}, {0, 0, 0, 0},
{0, 0, 0, 0}, {0, 0, 0, 0},
},
{
{1, 0, 1, 0}, {0, 0, 0, 0},
{0, 0, 0, 0}, {0, 0, 0, 0},
{0, 0, 0, 0}, {0, 1, 0, 1},
{0, 0, 0, 0}, {0, 0, 0, 0},
},
{
{1, 0, 1, 0}, {0, 0, 0, 0},
{0, 1, 0, 1}, {0, 0, 0, 0},
{0, 0, 0, 0}, {1, 0, 1, 0},
{0, 0, 0, 0}, {0, 1, 0, 1},
},
{
{1, 1, 1, 1}, {0, 0, 0, 0},
{0, 1, 0, 1}, {0, 0, 0, 0},
{0, 0, 0, 0}, {1, 1, 1, 1},
{0, 0, 0, 0}, {1, 0, 1, 0},
},
{
{1, 1, 1, 1}, {0, 0, 0, 0},
{0, 0, 0, 0}, {1, 1, 1, 1},
{1, 1, 1, 1}, {0, 0, 0, 0},
{0, 0, 0, 0}, {1, 1, 1, 1},
},
{
{1, 1, 1, 1}, {1, 0, 1, 0},
{1, 1, 1, 1}, {0, 0, 0, 0},
{0, 1, 0, 1}, {1, 1, 1, 1},
{0, 0, 0, 0}, {1, 1, 1, 1},
},
{
{1, 1, 1, 1}, {1, 0, 1, 0},
{1, 1, 1, 1}, {0, 1, 0, 1},
{1, 0, 1, 0}, {1, 1, 1, 1},
{0, 1, 0, 1}, {1, 1, 1, 1},
},
{
{1, 1, 1, 1}, {1, 1, 1, 1},
{1, 1, 1, 1}, {0, 1, 0, 1},
{1, 1, 1, 1}, {1, 1, 1, 1},
{1, 0, 1, 0}, {1, 1, 1, 1},
},
{
{1, 1, 1, 1}, {1, 1, 1, 1},
{1, 1, 1, 1}, {1, 1, 1, 1},
{1, 1, 1, 1}, {1, 1, 1, 1},
{1, 1, 1, 1}, {1, 1, 1, 1},
}
};
void plot_pixel(uint8_t *charset, int x, int line, int luma) {
int col1;
int col2;
int dith;
int pos;
int shift;
int yy;
int pix_pos;
uint8_t byte;
if(luma > 24) luma = 24;
if(luma < 0) luma = 0;
if(x >= 128) return;
col1 = ((luma / DITHERSTEPS)) & 3;
col2 = (col1 + 1) & 3;
dith = luma % DITHERSTEPS;
pos = (line / 4 * 0x800) + (line & 0x3) * 32 * 8 + ((x * 2) & 0xf8);
pix_pos = x & 3;
shift = (3 - pix_pos) * 2;
for(yy = 0; yy < 4; yy++) {
/* first frame */
byte = charset[pos + yy] & (0xff ^ (3 << shift));
if(dither_patterns[dith][yy * 2 + 0][pix_pos]) {
byte |= (col2 << shift);
} else {
byte |= (col1 << shift);
}
charset[pos + yy + 0] = byte;
charset[pos + yy + 4] = byte;
/* second frame */
byte = charset[0x4000 + pos + yy] & (0xff ^ (3 << shift));
if(dither_patterns[dith][yy * 2 + 1][pix_pos]) {
byte |= (col2 << shift);
} else {
byte |= (col1 << shift);
}
charset[0x4000 + pos + yy + 0] = byte;
charset[0x4000 + pos + yy + 4] = byte;
}
}
void main() {
int a;
double deg;
int x1, x2, x3, x4, x5;
int z1, z2, z3, z4, z5;
int x, line;
int luma_dist;
int x_dist;
uint8_t charset[32768] = { 0 };
FILE* fw;
int xpos[256];
int c;
int pos;
int b;
int offset = 4;
double zmin = cos(0 - 112.5 * DEGREE + 0 * 45 * DEGREE);
double zmax = 1;
double scale = zmax - zmin;
line = 0;
//32 steps
for (a = 0; a < 32; a++) {
deg = DEGREE * 45 * a / (32);
xpos[line] = sin(deg - 112.5 * DEGREE + 0 * 45 * DEGREE) * RADIUS * 2 + RADIUS * 2 + 8 * offset - 128;
//max. 5 visible edges on front side (8 sided shape)
x1 = sin (deg - 112.5 * DEGREE + 0 * 45 * DEGREE) * RADIUS + RADIUS;
z1 = (cos(deg - 112.5 * DEGREE + 0 * 45 * DEGREE) - zmin) / scale * SHADE;
x2 = sin (deg - 112.5 * DEGREE + 1 * 45 * DEGREE) * RADIUS + RADIUS;
z2 = (cos(deg - 112.5 * DEGREE + 1 * 45 * DEGREE) - zmin) / scale * SHADE;
x3 = sin (deg - 112.5 * DEGREE + 2 * 45 * DEGREE) * RADIUS + RADIUS;
z3 = (cos(deg - 112.5 * DEGREE + 2 * 45 * DEGREE) - zmin) / scale * SHADE;
x4 = sin (deg - 112.5 * DEGREE + 3 * 45 * DEGREE) * RADIUS + RADIUS;
z4 = (cos(deg - 112.5 * DEGREE + 3 * 45 * DEGREE) - zmin) / scale * SHADE;
x5 = sin (deg - 112.5 * DEGREE + 4 * 45 * DEGREE) * RADIUS + RADIUS;
z5 = (cos(deg - 112.5 * DEGREE + 4 * 45 * DEGREE) - zmin) / scale * SHADE;
for(x = 0; x < x1; x++) {
plot_pixel(charset, x, line, 0);
}
for(x = x1; x < x2; x++) {
plot_pixel(charset, x, line, z1 + (z2 - z1) * (x1 - x) / (x2 - x1));
}
for(x = x2; x < x3; x++) {
plot_pixel(charset, x, line, z2 + (z3 - z2) * (x2 - x) / (x3 - x2));
}
for(x = x3; x < x4; x++) {
plot_pixel(charset, x, line, z3 + (z4 - z3) * (x3 - x) / (x4 - x3));
}
for(x = x4; x < x5; x++) {
plot_pixel(charset, x, line, z4 + (z5 - z4) * (x4 - x) / (x5 - x4));
}
for(x = x5; x < RADIUS * 2; x++) {
plot_pixel(charset, x, line, 0);
}
line++;
}
//generate screens + sprite-pointers
for (a = 0; a < 8; a++) {
pos = 0x400 + a * 0x800;
for (b = 0; b < 0x3f0; b++) {
charset[pos + b] = 0xff;
charset[0x4000 + pos + b] = 0xff;
}
for (b = 0; b < 25; b++) {
for (c = 0; c < 32; c++) {
if(a > 4) x = 0xfe;
else x = c + a * 32;
charset[pos + offset + b * 40 + c] = x;
charset[0x4000 + pos + offset + b * 40 + c] = x;
}
}
for (b = 0x3f8; b < 0x400; b+=2) {
charset[pos + b] = 0xf0;
charset[0x4000 + pos + b] = 0xf0;
charset[pos + b + 1] = 0xf1;
charset[0x4000 + pos + b + 1] = 0xf1;
}
}
//generate sprite data for left and right cover sprite
for(a = 0; a < 63; a += 3) {
charset[0x3c00 + a + 0] = charset[0x7c00 + a + 0] = 0xff;
charset[0x3c00 + a + 1] = charset[0x7c00 + a + 1] = 0xea;
charset[0x3c00 + a + 2] = charset[0x7c00 + a + 2] = 0xa9;
charset[0x3c40 + a + 0] = charset[0x7c40 + a + 0] = 0x7f;
charset[0x3c40 + a + 1] = charset[0x7c40 + a + 1] = 0xff;
charset[0x3c40 + a + 2] = charset[0x7c40 + a + 2] = 0xff;
}
//write out stuff
fw = fopen("5col.data", "wb");
c = 0x00;
fwrite(&c,1,1,fw);
c = 0x40;
fwrite(&c,1,1,fw);
fwrite(&charset[0],1,32768,fw);
fclose(fw);
}
And now comes the 6502 part to reassemble twisted shapes with that data. For that we just draw virtual lines where the y-position is the y-position on the screen and the x-position is the line segment to display. So a cheap bresenham will help out. The steeper our slope is, the straighter the shape appears, the flater our slopw is, the more it will appear twisted. Also we make the twister appear more colorful by using all 4 charset-colors for the twister's shape only and not wasting one color for the area outside of the twister. For covering the FLI-bug and the area outside of the shape we use expanded sprites for which we update the x and y-positions every second line (yes, no stretcher needed here, can be done even cheaper). For making the outline smoother we set the x-position of the sprites with an accuracy of 1 pixel. Also we use multicolor mode for the sprites. That enables us to use one color for covering the area, another (black) for doing a stylish outline and even one more for casting a fake shadow, that follows the outline of the twister for free. Now this thingy looks pretty colorful!
That's really all? Yes! You can now feel free to fade around the twister by changing the charset-, sprite- and border-colors at any fashion.
Here's the code:
*= $3000
y1 = $40
y2 = $41
x1 = $42
x2 = $43
err = $44
clk = $46
dy = $47
pos = $48
step = $49
fade = $52
offset = $51
jmp start
irq1
dec $d019
ldy #$00
loop0
;do fli
sta18 lda #$00
sta $d018
sta11 lda #$1b
sta $d011
;prepare next values
lda tab11,y
sta sta11+1
lda tab18,y
sta sta18+1
;wait for right moment
bit $ea
;set x-positions of cover sprite 1
lda xpos,y
;even too lazy to add an offset to the table, as we have enough cycles available
adc #$66
sta $d000
adc #$c0
eor #$ff
;wait a bit, so that is cocky, right? :-)
nop
nop
;advance y-position of sprites
ldx ypos,y
stx $d001
stx $d003
;set x-positions of cover sprite 2
iny
sta $d002
;enough cycles left to enjoy the luxury of a loop
cpy #100
bcc loop0
;all lines done, display something sane
col1 lda #$01
sta $d020
lda #$f0
sta $d018
lda #$50
sta $d011
;interlace between both banks
lda $dd00
and #$03
eor #$02
sta $dd00
;even do a $d016 shift
lda $d016
eor #$01
sta $d016
;our fancy rasterline
nop
nop
nop
nop
nop
nop
nop
nop
col2 lda #$01
sta $d020
inc clk
;update tables and colors
jsr update
jsr colors
col3 lda #$01
sta $d020
;jsr $1003
;return from irq
pla
tay
pla
tax
pla
rti
start
sei
;sync and turn off screen
lda $d011
bpl *-3
lda $d011
bmi *-3
lda #$0b
sta $d011
lda $d011
bpl *-3
lda $d011
bmi *-3
;set up $d011 table
ldx #$00
loop2
txa
asl
ora #$01
and #$07
ora #$10
sta tab11,x
inx
bne loop2
;set up colors
lda #$09
ldx #$00
loop3
sta $d800,x
sta $d900,x
sta $da00,x
sta $db00,x
dex
bne loop3
;init values
lda #$00
sta x1
lda #$00
sta y1
lda #$00
sta x2
lda #99
sta y2
ldx #$d0
stx clk
ldx #$00
stx pos
stx step
stx fade
stx pos
stx offset
;copy 2nd bank
lda #$34
sta $01
ldx #$3f
ldy #$00
-
src lda $8000,y
dst sta $c000,y
dey
bne -
inc src+2
inc dst+2
dex
bne -
inc $01
;fade to white
ldy #$00
-
ldx #$04
jsr wait
lda fadec,y
sta $d020
iny
cpy #$07
bne -
;create display tables for the first time
jsr update
;vsync
lda $d011
bpl *-3
lda $d011
bmi *-3
;copy last bytes now to not distroy any still active irq-pointers @ $fffe
ldy #$00
-
lda $bf00,y
sta $ff00,y
dey
bne -
;now use irq @ vector $0314, but we could also just use the vector @ $fffe/f as long as we have no needed data there
sei
lda #$37
sta $01
lda #$7f
sta $dc0d
lda $dc0d
lda #$0b
sta $d011
lda #$30
sta $d012
lda #irq1
sta $0315
lda #$01
sta $d01a
;setup sprites and colors and things
lda #$01
sta $d025
sta $d026
sta $d027
sta $d028
sta $d021
sta $d022
sta $d023
sta $d020
lda #$03
sta $d015
lda #$03
sta $d017
sta $d01d
sta $d01c
lda #$32
sta $d001
sta $d003
lda #$18
sta $d000
lda #$28
sta $d002
lda #$02
sta $d010
ldx #$f0
stx $7ff8
stx $7ff9
lda #$02
sta $dd00
lda #$18
sta $d016
cli
jmp *
fadec
!byte $00,$09,$08,$0a,$0f,$07,$01
wait
lda $d011
bmi *-3
lda $d011
bpl *-3
lda #$30
cmp $d012
bne *-3
dex
bne wait
rts
update
inc offset
inc offset
lda offset
cmp #64
bcc *+6
lda #$00
sta offset
lda clk
and #$01
bne step0o
;decide what to update (upper x pos, lower x pos, move xpos to left/right)
lda step
cmp #$00
beq step0
cmp #$01
beq step1
cmp #$02
beq step2
cmp #$03
beq step3
lda #$00
sta step
jmp update
step0
inc x2
lda x2
cmp #99
bne step0o
inc step
step0o
jmp drawline
step1
inc x1
dec x2
lda x2
bne step1o
inc step
step1o
jmp drawline
step2
dec x1
inc x2
lda x2
cmp #99
bne step2o
inc step
step2o
jmp drawline
step3
dec x2
lda x2
bne step3o
inc step
step3o
;jmp drawline
drawline
;setup bresenham (dx/dy, inx/dex)
lda y2
sta toy+1
sec
sbc y1
sta ty2+1
lsr
sta err
ldx #$e8
lda x2
sec
sbc x1
bcs ov2
eor #$ff
adc #$01
ldx #$ca
ov2
stx incx2
sta tx2+1
lda x1
clc
adc offset
tax
;bresenham to calc slope
ldy y1
loopy
lda mytab18,x
sta tab18,y
lda myxpos,x
sta xpos,y
lda err
sec
tx2 sbc #$00
bcs +
ty2 adc #$00
incx2 inx
+
sta err
iny
toy cpy #$00
bne loopy
rts
colors
;all the color fadings
lda clk
cmp #$e0
bcs *+3
rts
and #$03
bne ++
lda fade
cmp #$28
bne +
lda #$00
sta fade
beq *+2
+
inc fade
tax
lda fade1,x
sta $d021
lda fade2,x
sta $d022
lda fade3,x
sta $d023
lda fade00,x
sta $d025
sta col1+1
lda fade0b,x
sta $d027
sta $d028
lda fade0c,x
sta col3+1
sta $d026
lda fade0f,x
sta col2+1
++
rts
;fading tables
fade0f
!byte $01,$01,$01,$01
!byte $01,$01,$01,$0f
!byte $0f,$0f,$0f,$0f
!byte $0f,$0f,$0f,$0f
!byte $0f,$0f,$0f,$0f
!byte $0f,$0f,$0f,$0f
!byte $0f,$0f,$0f,$0f
!byte $0f,$0f,$0f,$0f
!byte $0f,$01,$01,$01
!byte $01,$01,$01,$01
fade00
!byte $01,$01,$01,$01
!byte $0f,$0c,$0b,$00
!byte $00,$00,$00,$00
!byte $00,$00,$00,$00
!byte $00,$00,$00,$00
!byte $00,$00,$00,$00
!byte $00,$00,$00,$00
!byte $00,$00,$00,$00
!byte $00,$0b,$0c,$0f
!byte $01,$01,$01,$01
fade0c
!byte $01,$01,$01,$01
!byte $01,$01,$0f,$0c
!byte $0c,$0c,$0c,$0c
!byte $0c,$0c,$0c,$0c
!byte $0c,$0c,$0c,$0c
!byte $0c,$0c,$0c,$0c
!byte $0c,$0c,$0c,$0c
!byte $0c,$0c,$0c,$0c
!byte $0c,$0f,$01,$01
!byte $01,$01,$01,$01
fade0b
!byte $01,$01,$01,$01
!byte $01,$0f,$0c,$0b
!byte $0b,$0b,$0b,$0b
!byte $0b,$0b,$0b,$0b
!byte $0b,$0b,$0b,$0b
!byte $0b,$0b,$0b,$0b
!byte $0b,$0b,$0b,$0b
!byte $0b,$0b,$0b,$0b
!byte $0b,$0c,$0f,$01
!byte $01,$01,$01,$01
fade1
!byte $01,$01,$01,$01
!byte $01,$0d,$0f,$05
!byte $05,$0f,$0d,$01
!byte $01,$07,$0f,$0a
!byte $0a,$0f,$07,$01
!byte $01,$0d,$03,$0e
!byte $0e,$03,$0d,$01
!byte $01,$07,$0f,$0a
!byte $0a,$0f,$07,$01
!byte $01,$01,$01,$01
fade2
!byte $01,$01,$01,$01
!byte $01,$01,$0d,$0f
!byte $0f,$0d,$01,$01
!byte $01,$01,$07,$0f
!byte $0f,$07,$01,$01
!byte $01,$01,$0d,$03
!byte $03,$0d,$01,$01
!byte $01,$01,$07,$0f
!byte $0f,$07,$01,$01
!byte $01,$01,$01,$01
fade3
!byte $01,$01,$01,$01
!byte $01,$01,$01,$0d
!byte $0d,$01,$01,$01
!byte $01,$01,$01,$07
!byte $07,$01,$01,$01
!byte $01,$01,$01,$0d
!byte $0d,$01,$01,$01
!byte $01,$01,$01,$07
!byte $07,$01,$01,$01
!byte $01,$01,$01,$01
;sprite y-positions
ypos
!byte $32,$32,$32,$32
!byte $32,$32,$32,$32
!byte $32,$32,$32,$32
!byte $32,$32,$32,$32
!byte $32,$32,$32,$32
!byte $32
!byte $5c,$5c,$5c,$5c
!byte $5c,$5c,$5c,$5c
!byte $5c,$5c,$5c,$5c
!byte $5c,$5c,$5c,$5c
!byte $5c,$5c,$5c,$5c
!byte $5c
!byte $86,$86,$86,$86
!byte $86,$86,$86,$86
!byte $86,$86,$86,$86
!byte $86,$86,$86,$86
!byte $86,$86,$86,$86
!byte $86
!byte $b0,$b0,$b0,$b0
!byte $b0,$b0,$b0,$b0
!byte $b0,$b0,$b0,$b0
!byte $b0,$b0,$b0,$b0
!byte $b0,$b0,$b0,$b0
!byte $b0
!byte $da,$da,$da,$da
!byte $da,$da,$da,$da
!byte $da,$da,$da,$da
!byte $da,$da,$da,$da
!byte $da,$da,$da,$da
!byte $da
;corresponding $d018 values for each fragment
mytab18
!byte $10,$30,$50,$70
!byte $12,$32,$52,$72
!byte $14,$34,$54,$74
!byte $16,$36,$56,$76
!byte $18,$38,$58,$78
!byte $1a,$3a,$5a,$7a
!byte $1c,$3c,$5c,$7c
!byte $1e,$3e,$5e,$7e
!byte $10,$30,$50,$70
!byte $12,$32,$52,$72
!byte $14,$34,$54,$74
!byte $16,$36,$56,$76
!byte $18,$38,$58,$78
!byte $1a,$3a,$5a,$7a
!byte $1c,$3c,$5c,$7c
!byte $1e,$3e,$5e,$7e
!byte $10,$30,$50,$70
!byte $12,$32,$52,$72
!byte $14,$34,$54,$74
!byte $16,$36,$56,$76
!byte $18,$38,$58,$78
!byte $1a,$3a,$5a,$7a
!byte $1c,$3c,$5c,$7c
!byte $1e,$3e,$5e,$7e
!byte $10,$30,$50,$70
!byte $12,$32,$52,$72
!byte $14,$34,$54,$74
!byte $16,$36,$56,$76
!byte $18,$38,$58,$78
!byte $1a,$3a,$5a,$7a
!byte $1c,$3c,$5c,$7c
!byte $1e,$3e,$5e,$7e
!byte $10,$30,$50,$70
!byte $12,$32,$52,$72
!byte $14,$34,$54,$74
!byte $16,$36,$56,$76
!byte $18,$38,$58,$78
!byte $1a,$3a,$5a,$7a
!byte $1c,$3c,$5c,$7c
!byte $1e,$3e,$5e,$7e
!byte $10,$30,$50,$70
!byte $12,$32,$52,$72
!byte $14,$34,$54,$74
!byte $16,$36,$56,$76
!byte $18,$38,$58,$78
!byte $1a,$3a,$5a,$7a
!byte $1c,$3c,$5c,$7c
!byte $1e,$3e,$5e,$7e
!byte $10,$30,$50,$70
!byte $12,$32,$52,$72
!byte $14,$34,$54,$74
!byte $16,$36,$56,$76
!byte $18,$38,$58,$78
!byte $1a,$3a,$5a,$7a
!byte $1c,$3c,$5c,$7c
!byte $1e,$3e,$5e,$7e
!byte $10,$30,$50,$70
!byte $12,$32,$52,$72
!byte $14,$34,$54,$74
!byte $16,$36,$56,$76
!byte $18,$38,$58,$78
!byte $1a,$3a,$5a,$7a
!byte $1c,$3c,$5c,$7c
!byte $1e,$3e,$5e,$7e
;xpos table for sprite (TODO: should also be generated)
myxpos
!byte $aa,$a9,$a8,$a7,$a6,$a5
!byte $a4,$a4,$a3,$a2,$a2,$a1
!byte $a1,$a1,$a1,$a1,$a0,$a1
!byte $a1,$a1,$a1,$a1,$a2,$a2
!byte $a3,$a4,$a4,$a5,$a6,$a7
!byte $a8,$a9
!byte $aa,$a9,$a8,$a7,$a6,$a5
!byte $a4,$a4,$a3,$a2,$a2,$a1
!byte $a1,$a1,$a1,$a1,$a0,$a1
!byte $a1,$a1,$a1,$a1,$a2,$a2
!byte $a3,$a4,$a4,$a5,$a6,$a7
!byte $a8,$a9
!byte $aa,$a9,$a8,$a7,$a6,$a5
!byte $a4,$a4,$a3,$a2,$a2,$a1
!byte $a1,$a1,$a1,$a1,$a0,$a1
!byte $a1,$a1,$a1,$a1,$a2,$a2
!byte $a3,$a4,$a4,$a5,$a6,$a7
!byte $a8,$a9
!byte $aa,$a9,$a8,$a7,$a6,$a5
!byte $a4,$a4,$a3,$a2,$a2,$a1
!byte $a1,$a1,$a1,$a1,$a0,$a1
!byte $a1,$a1,$a1,$a1,$a2,$a2
!byte $a3,$a4,$a4,$a5,$a6,$a7
!byte $a8,$a9
!byte $aa,$a9,$a8,$a7,$a6,$a5
!byte $a4,$a4,$a3,$a2,$a2,$a1
!byte $a1,$a1,$a1,$a1,$a0,$a1
!byte $a1,$a1,$a1,$a1,$a2,$a2
!byte $a3,$a4,$a4,$a5,$a6,$a7
!byte $a8,$a9
!byte $aa,$a9,$a8,$a7,$a6,$a5
!byte $a4,$a4,$a3,$a2,$a2,$a1
!byte $a1,$a1,$a1,$a1,$a0,$a1
!byte $a1,$a1,$a1,$a1,$a2,$a2
!byte $a3,$a4,$a4,$a5,$a6,$a7
!byte $a8,$a9
!byte $aa,$a9,$a8,$a7,$a6,$a5
!byte $a4,$a4,$a3,$a2,$a2,$a1
!byte $a1,$a1,$a1,$a1,$a0,$a1
!byte $a1,$a1,$a1,$a1,$a2,$a2
!byte $a3,$a4,$a4,$a5,$a6,$a7
!byte $a8,$a9
!byte $aa,$a9,$a8,$a7,$a6,$a5
!byte $a4,$a4,$a3,$a2,$a2,$a1
!byte $a1,$a1,$a1,$a1,$a0,$a1
!byte $a1,$a1,$a1,$a1,$a2,$a2
!byte $a3,$a4,$a4,$a5,$a6,$a7
!byte $a8,$a9
;the final tables to be displayed
*= $3d00
xpos
*= $3e00
tab18
*= $3f00
tab11
;include generated data
* = $4000
!bin "5col.data",$4000,$0002
* = $8000
!bin "5col.data",$4000,$4002
Now try this with ECM-mode and hires, you'll need to optimize the chunks and reuse chars from the charsets to save space as we can only use the first $200 bytes of each charset.
====== X-rotating cube ======
{{ :base:stmp2.png?200|}}
Now, having all that knowledge from doing a twister with 2nd line FLI, one can do of course also other shapes with that. How's about a x-rotating cube? So let's take the following quad:
______________
|\ 128 px /|
\ /
\ /
\________/
| 64 px |
This resembles a face that would go from the maximum width you wish the cube to have, to the minimum width the cube should have (usually 2*32 pixel less than the maximum, so each line gets one pixel smaller on both sides). Now rasterize it the same way as you would do for the twister, so you end up with 32 different lines ranging from 128 pixel (multicolor) to 64 pixel width.
Cube frontview
________v1
/ \
/ \
/ \
/______________\v2 ____ also, change $d021/2/3/4 here
\ /
\ /
\ /
\________/
v3
Depending on the slope, you can now again reassemble the lines to form 2 perspective faces watched from the front. So all we need to calculate are the slopes v1->v2 and v2->v3, build our display tables from that and we have a nice rotating cube. The rotated values for v1/v2/v3 can be easily calculated by a lookup into a sine-table.
To make the shape of the cube more obvious we change the colors for the charset whenever the last line for each face is displayed and we end up with a cube that has different colors on each face. Now we can get fancy again and add an y-offset to all slopes to make the cube stomp and add some fading to fake some moving light. Only a good fake is a good make!
And here comes some code:
!cpu 6510
*= $3000
y1 = $40
y2 = $41
x1 = $42
x2 = $43
err = $44
clk = $46
dy = $47
pos = $48
stpos = $49
offs = $4a
col = $4b
shift = $4c
dest = $50
fcnt_l = $52
fcnt_h = $53
c_offs = $60
jmp start
irq1
dec $d019
lda #$06
ldx $d012
inx
cpx $d012
bne *-3
ldy #$0a
dey
bne *-1
inx
cpx $d012
nop
beq time1
nop
bit $ea
time1
ldy #$09
dey
bne *-1
nop
nop
inx
cpx $d012
nop
beq time2
bit $ea
time2
ldy #$0a
dey
bne *-1
inx
cpx $d012
bne time3
time3
ldy #$00
bit $ea
ldx #$18
stx $d011
sta $d020
loop0
poi18 lda $1000,y
sta $d018
lda tab11,y
sta $d011
stx $ea ;-> sets FLI-bug color
split cpy #$00
bcc nosplit
colu2 lda #$06
sta $d022
colu3 lda #$06
sta $d023
colu1 lda #$06
sta $d021
jmp in1
nosplit
nop
nop
nop
nop
nop
nop
nop
nop
lda $1000
in1
lda $1000
lda $1000
lda $1000
lda $1000
lda $1000
lda $1000
lda $1000
bit $ea
nop
nop
iny
cpy #100
bcc loop0
lda #$f0
sta $d018
lda #$00
sta $d020
lda #$50
sta $d011
lda $dd00
eor #$02
sta $dd00
colo2 lda #$06
sta $d022
colo3 lda #$06
sta $d023
colo1 lda #$06
sta $d021
;jsr $1003
inc clk
lda clk
and #$01
bne +
jsr update
jsr cols
inc fcnt_l
bne +
inc fcnt_h
+
pla
tay
pla
tax
pla
rti
start
;copy 2nd bank
sei
lda #$34
sta $01
ldx #$40
ldy #$00
-
sr01 lda bank2,y
tg01 sta $c000,y
dey
bne -
inc sr01+2
inc tg01+2
dex
bne -
lda #$37
sta $01
cli
;generate values for $d011
ldx #$00
loop2
txa
sec ;a = a * 2 + 1
rol
and #$07
ora #$18
sta tab11,x
inx
bne loop2
;populate tables for the first time
jsr update
;set colram
lda #$0e
ldx #$00
loop3
sta $d800,x
sta $d900,x
sta $da00,x
sta $db00,x
dex
bne loop3
;init variables
ldx #$00
txy
stx fcnt_l
stx fcnt_h
stx clk
stx pos
stx stpos
stx col
stx shift
stx c_offs
loopdl
;create all slopes needed
stx pos
jsr doline
ldx pos
inx
cpx #$10
bne loopdl
ldx #$00
stx pos
;vsync
lda $d011
bpl *-3
lda $d011
bmi *-3
lda #$02
sta $dd00
lda #$18
sta $d016
;set colors for the first time
jsr cols
;set up irq
sei
lda #$37
sta $01
lda #$7f
sta $dc0d
lda #$1b
sta $d011
lda #$2c
sta $d012
lda #irq1
sta $0315
lda #$01
sta $d019
sta $d01a
cli
;fade in
;wait 4 frames
-
lda fcnt_l
and #$03
bne -
lda fcnt_l
;wait until next frame starts
--
cmp fcnt_l
beq --
inc c_offs
lda c_offs
cmp #$08
bne -
jmp *
cols
;set up colors
ldy shift
lda col ;which colors to choose (face 1..4)
clc
adc spos,y ;add x-movment depending on counter shift
tay
;colors of first face shown
lda coltab+0,y ;fetch index into fadings tab
adc c_offs ;add colorfade offset
tax ;use as index
lda fadings,x ;fetch corresponding color
sta colu3+1 ;set color
lda coltab+1,y ;same for all other colors
adc c_offs
tax
lda fadings,x
sta colu2+1
lda coltab+2,y
adc c_offs
tax
lda fadings,x
sta colu1+1
;add offset to shift and wrap around in case -> we advance to the colors of next face
tya
clc
adc #$08
and #$1f
tay
;same for other face shown
lda coltab+0,y
adc c_offs
tax
lda fadings,x
sta colo3+1
lda coltab+1,y
adc c_offs
tax
lda fadings,x
sta colo2+1
lda coltab+2,y
adc c_offs
tax
lda fadings,x
sta colo1+1
;advance counter
inc shift
lda shift
cmp #$3c
bne *+6
lda #$00
sta shift
rts
spos
!byte $00,$00,$00,$00,$00,$00
!byte $00,$00,$00,$00,$00,$00
!byte $00,$00,$00,$00,$00,$00
!byte $00,$00,$00,$00,$00,$00
!byte $00,$00,$00,$00,$00,$00
!byte $01,$01,$02,$03,$04,$04
!byte $04,$05,$05,$05,$05,$05
!byte $05,$05,$05,$05,$05,$05
!byte $05,$05,$05,$04,$04,$04
!byte $03,$02,$01,$01,$00,$00
update
;walk through slopes
inc pos
lda pos
and #$0f
sta pos
cmp #$0f
bne +
sta stpos
jmp ++
+
cmp #$00
bne ++
;advance to colors of next face
lda col
clc
adc #$08
and #$1f
sta col
++
;calc destination pointer
jsr calcdest
;set up start of $d018 table
lda dest
sta poi18+1
lda dest+1
sta poi18+2
;and pick fitting splittab entry
ldx pos
lda splittab,x
sta split+1
rts
calcdest
;dest = pos * $80 + slopes
;-> dest = (pos * $100 + (2 * slopes) / 2)
lda pos
clc
adc #>(slopes) * 2
lsr
sta dest+1
arr #$00 ;moves carry to MSB of lowbyte, aka lda #$00 + ror
sta dest
rts
doline
;calc location to store slope data
jsr calcdest
;calculate offset for stomping/marching
lda sine+$08,x
asl
sta offs
lda #$7f
sec
sbc offs
sta offs
;slope for face 1
lda sine+$28,x
asl
clc
adc offs
sta y1
lda sine+$38,x
asl
clc
adc offs
sta y2
lda sine+$18,x
lsr
sta x1
lda sine+$28,x
lsr
sta x2
;blank all area above face 1
ldy #$00
lda #$9c
cl1
sta (dest),y
iny
cpy y2
bne cl1
jsr drawline
ldx pos
;slope for face 2
lda sine+$38,x
asl
clc
adc offs
sta y1
sta splittab,x
lda sine+$08,x
asl
clc
adc offs
sta y2
lda sine+$28,x
lsr
sta x1
lda sine+$38,x
lsr
sta x2
jsr drawline
rts
drawline
;calc dy/dx and decide if we increment or decrement x
lda y2
sta toy+1
sec
sbc y1
sta dy
ldx #$e8
lda x2
sta tox+1
sec
sbc x1
bcs ov2
eor #$ff
adc #$01
ldx #$ca
ov2
stx incx1
stx incx2
ldx x1
ldy y1
cmp dy
bcc steep
;flat slope
sta tx1+1
lsr
sta err
lda dy
sta ty1+1
loopx
lda mytab,x
sta (dest),y
lda err
sec
ty1 sbc #$00
bcs ov3
tx1 adc #$00
incy1 iny
ov3
sta err
incx1 inx
tox cpx #$00
bne loopx
rts
;we have a steep slope
steep
sta tx2+1
lda dy
sta ty2+1
lsr
sta err
loopy
lda mytab,x
sta (dest),y
lda err
sec
tx2 sbc #$00
bcs ov4
ty2 adc #$00
incx2 inx
ov4
sta err
incy2 iny
toy cpy #$00
bne loopy
rts
splittab
!byte $00,$00,$00,$00
!byte $00,$00,$00,$00
!byte $00,$00,$00,$00
!byte $00,$00,$00,$00
coltab
!byte $40,$e0,$30,$70
!byte $30,$e0,$40,$60
!byte $a0,$f0,$70,$10
!byte $70,$f0,$a0,$80
!byte $b0,$c0,$f0,$10
!byte $f0,$c0,$b0,$00
!byte $50,$f0,$d0,$10
!byte $d0,$f0,$50,$b0
;sine, two times, so we don't have to cope with wrap arounds
sine
!byte $20,$23,$26,$29,$2c,$2f
!byte $31,$34,$36,$38,$3a,$3c
!byte $3d,$3e,$3f,$3f,$3f,$3f
!byte $3f,$3e,$3d,$3c,$3a,$38
!byte $36,$34,$31,$2f,$2c,$29
!byte $26,$23,$20,$1c,$19,$16
!byte $13,$10,$0e,$0b,$09,$07
!byte $05,$03,$02,$01,$00,$00
!byte $00,$00,$00,$01,$02,$03
!byte $05,$07,$09,$0b,$0e,$10
!byte $13,$16,$19,$1c
!byte $20,$23,$26,$29,$2c,$2f
!byte $31,$34,$36,$38,$3a,$3c
!byte $3d,$3e,$3f,$3f,$3f,$3f
!byte $3f,$3e,$3d,$3c,$3a,$38
!byte $36,$34,$31,$2f,$2c,$29
!byte $26,$23,$20,$1c,$19,$16
!byte $13,$10,$0e,$0b,$09,$07
!byte $05,$03,$02,$01,$00,$00
!byte $00,$00,$00,$01,$02,$03
!byte $05,$07,$09,$0b,$0e,$10
!byte $13,$16,$19,$1c
;color fadings tabs for each specific color
fadings
!byte $06,$00,$00,$00,$00,$00,$00,$00
!byte $00,$00,$00,$00,$00,$00,$00,$06
!byte $06,$0b,$0c,$0f,$01,$01,$01,$01
!byte $01,$01,$01,$01,$0f,$0c,$0b,$06
!byte $06,$02,$02,$02,$02,$02,$02,$02
!byte $02,$02,$02,$02,$02,$02,$02,$06
!byte $06,$06,$04,$0e,$03,$03,$03,$03
!byte $03,$03,$03,$03,$0e,$04,$06,$06
!byte $06,$06,$04,$04,$04,$04,$04,$04
!byte $04,$04,$04,$04,$04,$04,$06,$06
!byte $06,$0b,$0c,$05,$05,$05,$05,$05
!byte $05,$05,$05,$05,$05,$0c,$0b,$06
!byte $06,$06,$06,$06,$06,$06,$06,$06
!byte $06,$06,$06,$06,$06,$06,$06,$06
!byte $06,$09,$08,$0a,$0f,$07,$07,$07
!byte $07,$07,$07,$0f,$0a,$08,$09,$06
!byte $06,$09,$08,$08,$08,$08,$08,$08
!byte $08,$08,$08,$08,$08,$08,$09,$06
!byte $06,$09,$09,$09,$09,$09,$09,$09
!byte $09,$09,$09,$09,$09,$09,$09,$06
!byte $06,$09,$08,$0a,$0a,$0a,$0a,$0a
!byte $0a,$0a,$0a,$0a,$0a,$08,$09,$06
!byte $06,$0b,$0b,$0b,$0b,$0b,$0b,$0b
!byte $0b,$0b,$0b,$0b,$0b,$0b,$0b,$06
!byte $06,$0b,$0c,$0c,$0c,$0c,$0c,$0c
!byte $0c,$0c,$0c,$0c,$0c,$0c,$0b,$06
!byte $06,$06,$0c,$05,$03,$0d,$0d,$0d
!byte $0d,$0d,$0d,$03,$05,$0c,$06,$06
!byte $06,$06,$04,$0e,$0e,$0e,$0e,$0e
!byte $0e,$0e,$0e,$0e,$0e,$04,$06,$06
!byte $06,$0b,$0c,$0f,$0f,$0f,$0f,$0f
!byte $0f,$0f,$0f,$0f,$0f,$0c,$0b,$06
;d018 values depending for each fragment
mytab
!byte $10,$30,$50,$70
!byte $12,$32,$52,$72
!byte $14,$34,$54,$74
!byte $16,$36,$56,$76
!byte $18,$38,$58,$78
!byte $1a,$3a,$5a,$7a
!byte $1c,$3c,$5c,$7c
!byte $1e,$3e,$5e,$7e
!align 255,0
tab11
* = $3800
slopes
* = $4000
!bin "stomp.data",$4000,2
* = $7400
!fill $0100, $ff
* = $8000
bank2
!bin "stomp.data",$3ff8,$4002
* = $b400
!fill $0100, $ff
====== Waving chessboard carpet ======
{{ :base:chessboard.png?200|}}
At this kind of effect we concentrate on updating the multicolor registers each second line, while avoiding ugly gray dots. Also we use the free cycles during display to clear the z-table that is generated each frame.
Here, the displayed chunk is determinded by the z-position of each line. Calculations start with the deepest z-position of the carpet, the y-position is thereby fetched from a sine-table. Now if a closer line is at the same y-position it will automatically cover lines below, as the value in the final table is simply overwritten.
Each chunk has also certain colors bonded to it. Also the bitmap is created that way, that we can get a continuous gradient by continuously interleaving 2 colors. This way the gradient $01 $0d $03 $0e $04 $02 $0b $00 fits into the whole chessboard. The dark areas are just faded from $06 to $00 with the remaining 2 colors. The FLI bug can be successfully colored black by placing a ''ldy #$xx'' (opcode $a0) after the ''sta $d011.''
The chessboard-texture to be rasterized:
{{:base:wobble_texture.png|}}
Timing is nifty in the inner loop to avoid gray dots, same goes for timing when entering the loop.
;...
;code for stable irq
;...
ldy ztab
;takes 3 instead of 2 cycles a ldx #$00 would need. This fixes timing as only a single cycle
;needs to be wasted what can't be done with a single mnemonic
ldx zero
loop0
;now it is time to switch colors in the offscreen area to avoid gray dots
sta $d022
stx $d021
lda mytab,y
sta $d018
.x lda tab11
;possibility to blank first frames, to avoid glitches
first ora #$40
sta $d011
;-> flibug col = 0
col3 ldy #$00
sty $d023
;-> first line is black, always, else unwanted colors appear in first line
;then $d023 is $06 for all other lines
lda #$06
sta col3+1
nop
nop
ldy #$ff
ldx .x+1
bit $ea
;clear ztab so that update finds a free table when being called, saves cycles outside of irq
sty ztab,x
inx
;store index for d011-tab as x will be destroyed in 3, 2, 1, ...
stx .x+1
;compare beforehand, carry will stay untouched until branch
cpx #100
ldy ztab,x
;preload values for $d021/22 and waste some cycles for perfect timing (destroys x)
ldx tabd021,y
nop
nop
nop
nop
lda tabd022,y
bcc loop0
Yet no full code-examples here, but as you now know about the ideas behind, it should be possible for you to write your own routines for that, right? :-)