;Azure asked me to write a doc on this 512 bytes
;intro called 'Midnight Blue' that won 512 compo at tig98.
;So, let's start :)
;This intro perform that boring mapped tunnel that all of us
;have seen and seen hundreds of times :-)
;Now, where is the fun?
;Try to put all the 'normal' tunnel code in 512 bytes and
;you will experience several headache :-)
;For who doenst know how this kind of effects works let me
;say that all the theory is very simple.
;In few words tunnel is calculated by intersecting rays with tunnel
;surface (that is a infinte cylinder equation).
;It's a kind of raytracing :-)
;But we'll not calc EVERY pixel on the screen in this way, 'cause
;it would be too slow, very slow, at least on 68k series.
;We'll perform calculation only on a 32*32 screen (1024 rays!) and then
;we'll interpolate this texture value on a 8x8 pixels wide grid.
;In this manner you can view on your screen a full 256x256 pixels
;tunnel with no-noticeable loss.
;What do we need to realize a such tunnel effect?
;1) one palette :-)
;2) sin/cos table
;3) one texture
;4) a simple raytracer
;5) a grid expander (to zoom 32x32 screen on a 256x256 one)
;6) some code that moves and rotates camera along this damn tunnel :)
;So, let's star with code:
Init: Lea Base,a0 ALL the bss stuff is referred to this pointer
;First of all we'll generate some texture.
;I opted for a simple black to blue texture..
;so dont blame me for this foolish colors :)
move.l a0,a1 ;save bss pointer
moveq #-1,d7 ;just 256 color :-)
moveq #0,d0 ;initializes to black (we work on RGB values)
.pal move.l d0,(a1)+ ;store color
add.l #$00010102,d0 ;spread black to blue
subq.b #1,d7
bcc.s .pal ;loop just 256 times
bsr.w SetPalette ;loads palette
;Now we have to generate a sin/cos table.
;We could just make only a sin table and then extract
;cos values by adding pi/4 period offset on sin pointer..
;This way should be also more shorter than the double sin/cos
;stuff but I cant produce a good routine that calcs only
;a sin table, I'm a lamer,you know :-)
;So I used an idea already explained on http://come.to/amiga in one Azure's doc.
;In a brief summary this sin/cos work simply applicating infinitesimal
;rotation to a vector.
;Vector starts at (1,0) coords..so x will be cos value and
;y will be the sin one.
;Applicating an infinetisimal rotation isnt so simple because
;you can experiment some vey bad accuracy.
;To fix this problem i've just done some improvements on the
;classical infinitesimal rotation matrix.
steps equ 2048 ;sin/cos table steps
factor equ steps*10000/2/31415 ;fixed point factor
move.l a1,a2 ;saves sin table pointer
move.l #factor,d5 ;this piece of code
move.w #steps,d0 ;generates a sin/cos table
lsr.w #3,d7 ;using an infinitesimal
mulu.w d5,d0 ;rotation matrix
moveq #0,d2 ;u can find all the math stuff..
;on http://come.to/amiga
.scloop move.l d2,d3 ;look at azure's doc with my final
divs.l d5,d3 ;error correction chapter
move.w d3,(a1)+ ;btw, this isn't the shortest
sub.l d3,d0 ;way i know to generate a sin table
move.l d0,d1 ;but afaik probably
divs.l d5,d1 ;it's the shortest way
move.w d1,(a1)+ ;to make a sin/cos table
add.l d1,d2
dbra d7,.scloop
;* This is V.R.T.G ************************************
;* Very Random Textures Generator :-D *****************
;Now, how to generate a good texture to map on tunnel?
;There are several way to accomplish this work..like
;fractal and procedural generator..but we'll
;use a 'dumb generator' :-)
;First we fill 256x256 texture with random values.
;Then we'll 'blur' this texture by calculating the average
;value of every texel, using the top,bottom,left and right texels
;that lies near our average one.
;This blur pass will be applicated several time..and in this
;way we will have the initial random generated mess smoothed..but
;now we havent something that could be called as a 'good texture'.
;So..we have to BREAK the 'monotony' of texture by applicating
;a non linear filter.
;This non-linear filter just take one texel, multiply this for 4, and the if value
;it's greater than 127 we'll subtract 127.
;In this way we'll break the smoothness and texture will be more interesting.
;Now let's apply all this passes just a few times and will obtain a
;so called 'decent texture' :-)
;Another problem to solve it's how can we make out texture tileable.
;This is not a big problem, all that we have to do it's think our
;texture space like a finite but unlimitated (ie. no boundaries)
;space.
move.l a1,a0 ;saves pointer
moveq #4,d0
.random ror.l d0,d0 ;tanx to Azure for this..
addq.l #7,d0 ;smart (and chip) rnd generator
move.b d0,(a0)+ ;fill texture with random values
dbra d7,.random
moveq #6-1,d4 ;filter passes
.start moveq #6-1,d6 ;blur passes
move.l a1,a0 ;saves texture pointer
.loopf move.b (a0),d0 ;load one texel
lsl.b #2,d0 ;*4
bge.s .ok ;it's greater than 127?
not.b d0 ;yeah..so make it 'reasonable' :)
.ok move.b d0,(a0)+ ;store texel
dbra d7,.loopf
.loopbb move.l a1,a0 ;some pointers sutff
move.l a1,a5
move.l #256,d0
move.l d0,d1
neg.w d1
.loopb moveq #0,d2 ;loads 4 texels
move.b -1(a0),d2 ;left
add.b (a5,d0.l),d2 ;add bottom
moveq #0,d3
move.b 1(a0),d3 ;add right
add.b (a5,d1.l),d3 ;add top
add.w d2,d3 ;mix alltogether
lsr.w #2,d3 ;perform average
move.b d3,(a0)+ ;and store smoothed texel
addq.w #1,d0 ;increase bottom and top pointers
addq.w #1,d1
dbra d7,.loopb
dbra d6,.loopbb
dbra d4,.start
move.l a1,a5 *saves texture pointer
move.l a0,a1 *saves grid pointer
lea (33*33*8)(a1),a4 *saves temp pointer
move.l a4,a0
lea (32*4)(a0),a0 *chunky pointer
rts ;this rts can removed
;but who care? :)
;This is dumb camera code..no words on it :-)
VBlank: ; Called every vblank after Init has finished.
addq.w #1,param4(a4) ;here..
move.w param4(a4),d0 ;just...
movem.w (a2,d0.w*4),d0/d1 ;some...
asr.w #1,d0 ;lissajeous..
asr.w #2,d1 ;trick..
move.w d0,param1(a4) ;to move..
move.w d1,param2(a4) ;and rotate camera
move.w (a2,d1.w*4,2048*2*4+384),d0
lsl.w #3,d0
move.w d0,param3(a4)
rts
Main: ; Called once when Init has finished.
; Registers are as left by Init.
; If it terminates, the demo will exit.
* A5 --> Texture Pointer
* A4 --> TempData Pointer
* A2 --> Sin/Cos Pointer
* A1 --> Grid Pointer
* A0 --> Chunky Pointer
param1 equ 0
param2 equ 2
param3 equ 4
param4 equ 6
;This it's the main part.
;Simply we call first tracer and then grid expander.
;Camera movements are managed in verical blanck routine.
Main2 bsr.b Tracer ;call ray tracer
bsr.w Grid ;call grid expander
bsr.w Update256x256
bra.b Main2
rts
;Here the most interesting part of this intro.
;Imagine that observer (and you) is located on our universe
;axis origin (0,0,0).
;Imagine also that our screen is located in front of us.
;Screen plane is parallel to plane composed by X and Y axis.
;Now, our screen is 32x32 wide and so we have to trace 1024
;rays, from observer to EVERY pixel on this projecting screen.
;For every ray we have to calc his intersection with tunnel
;and than find out what (u,v) texture coords assign at this intersection
;point.
;Mind that intersection points is in a 3 dimension space, while texture
;points lies on a 2 dimension space, so there isnt a general rule
;(ie. a |R^3-->|R^2 function) to map the texture.
;Calc ray intersection with tunnel is very simple.
;Our ray start ever from (0,0,0) so the general parametric equations
;to describe that ray are:
;x = a*t
;y = b*t
;z = c*t
;where a,b,c are ray coefficients and t it's the only real parameter.
;Tunnel is described by the classical infinite tunnel equation.
;Let z axis be the tunnel axis, so the equation is:
;x^2+y^2 = R^2
;where R it's cylinder radius.
;NOw, just substitutes ray equations into cilinder one and u'll obtain:
;t = +/- (R/sqrt(a^2+b^2))
;We'll discard negative solutions 'cause those intersection points
;are behind the observer.
;Now, with t calculated, just substitute t in ray equation to
;find out intersection point.
;Very simple and linear.
;But how to rotate/move camera and how to choice a good function
;to map texture on tunnel?
;We'll see that move tunnel problem it's inherent to texture mapping
;function problem.
;So we'll first accomplish to rotate camera stuff.
;In this intro camera can roll on x and y axis by simply rotate
;intersection rays!
;This is a dumb way to perform camera rotation, in fact there are
;more fast way to do the same thing..but remind, we need short code :)
;Now we have to solve mapping problem.
;There isnt a canonic way to solve this problem, you can come
;out with several different solutions, all good, all nice :-)
;But it's ok that we have to display a nice-to-see tunnel, so
;we are looking for the best-look function.
;So, the more used function it's like
; f(x,y,z) -> (u,v)
; u = atan (y/x)
; v = z
;but atan function cant be performed in few bytes and we have so few
;space :-(
;Ergo, i tried several different solution and I found a good one
;that has also very very short code :-)
;just :
; u = x^2 (or u = y^2)
; v = z
;this solution it's good when x is a 'big' number..where x is less than
;one it's became a very lame mapping :-)
;So we have just solved camera along z traslations, in fact we can
;just add a traslation value to v mapping coordinate and we can
;magically traslate our camera..nice? :-)
;let's start with some code:
Tracer movem.l d0-a6,-(sp)
moveq #32/2,d7 ;y loop counter
moveq #-32/2,d1 ;we start at the upper left screen corner
.y moveq #-32/2,d0 ;to trace our rays
.x moveq #26,d2 ;screen distance by observer (ie. focal lenght!)
;bigger value make it zoomed,
;lower make it seems look trought to a
;fish eye lens :-)
move.w param1(a4),d3 ;rotates ray on X axis
and.w #$7ff,d3 ;make angle fit into table range
movem.w (a2,d3.w*4),d3/d4 ; get sin(a) and cos(a)
move.l d3,d5
move.l d4,d6
muls.w d2,d3 ;z*sin
muls.w d1,d4 ;y*cos
muls.w d1,d5 ;y*sin
muls.w d2,d6 ;z*cos
add.l d5,d6 ;Z
sub.l d3,d4 ;Y
move.l d4,-(sp) ;saves Y value
move.w param2(a4),d3 ;rotate ray on Y axis
and.w #$7ff,d3
movem.w (a2,d3.w*4),d3/d5 ;get sin(b) and cos(b)
move.l d3,d4
move.l d5,d2
muls.w d0,d3 ;x*sin
muls.l d6,d4 ;z*sin
muls.w d0,d2 ;x*cos
muls.l d6,d5 ;z*cos
moveq #11,d6
asr.l d6,d4
asr.l d6,d5
add.l d2,d4 ;X
sub.l d3,d5 ;Z
move.l (sp)+,d6 ;gets Y value and..
move.l d5,-(sp) ;saves the Z ones
move.l d6,d5 ;now
move.l d4,d6 ;we
asr.l #6,d5 ;just
asr.l #6,d4 ;calc
muls.w d5,d5 ;a^2
muls.w d4,d4 ;b^2
add.l d4,d5 ;and a^2+b^2
addq.l #1,d5 ;to avoid division by zero :-)
;Now, how do sqrt?
;I used this routine coded by an unknow archimedes coder
;and then adapted on 6502 and 680x0 by Graham.
;HOw it works? find it by yourselves :-)
;btw..just think that an integer number can be written
;like a product of several factor all equal to 4 and one
;other real factor.. :-)
moveq #1,d3 ;thank goes to
ror.l #2,d3 ;Graham for this
moveq #32,d2 ;fast and short sqrter
.l2n
move.l d3,d4
rol.l d2,d4
add.w d3,d3
cmp.l d4,d5
bcs.b .no
addq.w #1,d3
sub.l d4,d5
.no
subq.w #2,d2
bgt.b .l2n
;Now we have just to perform last calculation..
.ok move.l (sp)+,d4 ;i know that this isn't the academic
asl.l #5,d4 ;way to make tunnels, but that's the
asl.l #5,d6 ;only way i found that eliminates
divs.w d3,d6 ;arctan calculation and short both
divs.w d3,d4 ;(my atan routine is 100 bytes long....)
add.w param3(a4),d4 ;Z axis camera traslation
move.w d4,(a1)+ ;just write (u,v)
move.w d6,(a1)+
addq.l #1,d0 ;next column
cmp.w d7,d0
ble.w .x
addq.l #1,d1 ;next row
cmp.w d7,d1
ble.w .y
movem.l (sp)+,d0-a6
rts
;Here come routine that put all the stuff on screen.
:it's very simple..just think to dispose all (u,v) calculated
;with tracer on a grid made by 8x8 pixel square.
;At every knot you will find a differente (u,v) value.
;Now we have to interpolate this values from knot to knot for
;every little square.
;I dont bother you with interpolation formulas that are just dumb.
;so..take a look to the routine..and think that can be
;done more shorter (and more faster) with some simply math tricks
;that i just found after the compo :-)
*A5-> texture pointer*
*A1-> 33x33 (u,v) grid pointer*
*A0-> chunky buffer*
;I know that actually this routine it's slow
;but this is a short code compo, that isn't? :)
Lattice movem.l d0-a6,-(sp)
moveq #32-1,d7 ;y loop counter
moveq #0,d0
.scanli swap d7
move.w #32-1,d7 ;x loop counter
.square move.l a0,a6
move.l (33*4)(a1),a4 ;(u4,v4)
move.l (a1)+,d1 ;(u1,v1)
move.l (33*4)(a1),a3 ;(u3,v3)
move.l (a1),d2 ;(u2,v2)
sub.l d1,a4 ;(u4-u1,v4-v1)
sub.l d2,a3 ;(u3-u2,v3-v2)
lsl.l #3,d1 ;instead divide by 8 all the increments..
lsl.l #3,d2 ;we just multiply by 8 all the offset :)
;in this way we have not accuracy loss.
moveq #8-1,d6
.Yspan move.l d1,d3 ;(uL,vL)
move.l d2,d4 ;(uR,vR)
swap d6
sub.l d3,d4 ;(uR-uL,vR-vL)
addq.w #8,d6
asr.l #3,d4
lsl.w #3,d4
asr.w #3,d4
.Xspan move.w d3,d0 ;need explanations
rol.l #8,d3 ;this loop? :)
move.b d3,d0
ror.l #8,d3
move.b (a5,d0.l),(a6)+ ;just do it :-)
add.l d4,d3 ;(u+du,v+dv)
subq.w #1,d6
bne.s .Xspan
lea 256-8(a6),a6 ;next span
add.l a4,d1 ;(uL+duL,vL+dvL)
add.l a3,d2 ;(ur+duR,vR+dvR)
swap d6
dbra d6,.Yspan
addq.l #8,a0 ;next nice little square
dbra d7,.square
addq.l #4,a1
lea (256*7)(a0),a0
swap d7
dbra d7,.scanli
movem.l (sp)+,d0-a6
rts
;And that's all Folks! :-)
printt "Length of your code"
Code_End: printv Code_End-Code_Start
;; ******************** BSS area ********************
section BSS_Area,bss
; declare BSS vars here. and only here
Base
Palette ds.l 256
Sin ds.l 2048*4
Texture ds.b 256*256
Grid ds.l 33*33*2
TempData ds.l 32
Chunky ds.b 256*256
;All code by nAo/darkAge (when I wrote this i was ramjam :-) )
;except where is differently specificated.