; Slightly busted metaballs ; ; by Mikael Kalms (mikael@kalms.org) on 2001-12-01 ; ; For EFNet #asm compo 5: make a dos demo/game/effect in max 1kB ; ; ; This piece of code was supposed to show a set of balls which blob together ; when getting close to each other, often referred to as 'metaballs'. ; Marching tetrahedrons was used to polygonize the scalar field. Unfortunately ; the implementation is broken somewhere; the end result is not quite the expected. ; I don't have time to track the error down, so this will have to do as-is. ; ; Marching tetrahedrons was chosen over marching cubes because mtetras has ; fewer 'unique' cases that need to be specified by hand, smaller code ; implementation, and I was curious as to how the end result would look. ; ; Assembles/links with TASM 4/TLINK 7. b EQU byte ptr w EQU word ptr d EQU dword ptr FIELDFUNC EQU 1 ; Set to 1 for 1/dist^2, and to 2 for dist^2 LOCALS @@ .MODEL TINY .CODE .386 ORG 100h ;--------------------------------------------------------------------- ; Init; set greyscale palette start: mov ax,ds add ah,10h mov fs,ax mov ax,13h int 10h mov dx,3c8h xor ax,ax out dx,al inc dx @@col: out dx,al out dx,al out dx,al inc al jne @@col ;--------------------------------------------------------------------- ; Frame loop @@frame: call UpdateMetaBallPositions call ClearBackbuffer ; call Meta_calcFieldValues ; Fallthrough call Meta_generatePolys call VSync ; call CopyBackbufferToScreen ; Fallthrough mov ah,1 int 16h jz @@frame ;--------------------------------------------------------------------- ; Exit sequence mov ax,3 int 10h ret ;--------------------------------------------------------------------- ; Wait for vertical retrace VSync: mov dx,03dah @@v1: in al,dx test al,8 jz @@v1 @@v2: in al,dx test al,8 jnz @@v2 ; ret ; Fallthrough ;--------------------------------------------------------------------- ; Copy the rendering buffer to graphics memory CopyBackbufferToScreen: push es push ds push 0a000h pop es push fs pop ds xor si,si xor di,di mov cx,16000 rep movsd pop ds pop es ret ;--------------------------------------------------------------------- ; Subpixeled flat-triangle rasterizer ; inputs: ; [si+0] w triangle colour ; [si+2] w vertex 0 x (as 12.4 fixed point) ; [si+4] w vertex 0 y (as 12.4 fixed point) ; [si+6] w vertex 1 x (as 12.4 fixed point) ; [si+8] w vertex 1 y (as 12.4 fixed point) ; [si+10] w vertex 2 x (as 12.4 fixed point) ; [si+12] w vertex 2 y (as 12.4 fixed point) DrawTri: push es pusha mov di,OFFSET TriEdges ; Reset spantable to all 0's xor ax,ax mov cx,200*2 rep stosw xor ecx,ecx mov bx,[si+1*2] ; Draw triangle edges into spantable mov dx,[si+2*2] mov ax,[si+4*2] mov cx,[si+5*2] call @@drawEdge mov bx,[si+4*2] mov dx,[si+5*2] mov ax,[si+7*2] mov cx,[si+8*2] call @@drawEdge mov bx,[si+7*2] mov dx,[si+8*2] mov ax,[si+1*2] mov cx,[si+2*2] call @@drawEdge mov bp,320 mov dl,200 ; Go through all entries in spantable, mov dh,[si] ; and render any lines which are more mov si,OFFSET TriEdges ; than 0 pixels wide push fs pop es xor di,di @@span: lodsw ; Fetch span from spantable xchg ax,cx lodsw test ax,ax ; Clip span against screen X borders jns @@nClipSpanLeft xor ax,ax @@nClipSpanLeft: cmp cx,bp jle @@nClipSpanRight mov cx,bp @@nClipSpanRight: sub cx,ax ; Skip spans with width <= 0 jle @@skipSpan push di ; Render pixels add di,ax mov al,dh rep stosb pop di @@skipSpan: add di,bp dec dl jnz @@span popa pop es ret ;-------------------------------------------------------------------- ; Draw triangle-edge into spantable ; inputs: ; bx x0 12.4 fixed point ; dx y0 12.4 fixed point ; ax x1 12.4 fixed point ; cx y1 12.4 fixed point @@drawEdge: mov di,OFFSET TriEdges-2 cmp dx,cx ; Check if edge goes downward jl @@drawEdge_left ; If it does, it's on right side je @@drawEdge_skip inc di ; Left-side edge inc di xchg ax,bx xchg cx,dx @@drawEdge_left: sub ax,bx sub cx,dx shl eax,16 shl ebx,16 push dx ; Calculate dx/dy gradient cdq ; along edge idiv ecx xchg eax,ebx pop dx push dx add cx,dx ; Subpixel pre-step edge not dl and edx,0fh imul edx,ebx add eax,edx sar eax,4 pop dx sar cx,4 sar dx,4 sub cx,dx je @@drawEdge_skip mov bp,dx shl bp,2 add di,bp @@drawEdge_row: ; Draw all edge's rows to spantable test dh,dh ; Skip row if (unsigned) y > 255 jne @@drawEdge_nClipY push ax ; Write edge to spantable mov ax,[di] mov [di],eax pop ax @@drawEdge_nClipY: add di,4 add eax,ebx ; Step down edge X counter inc dx loop @@drawEdge_row @@drawEdge_skip: ret ;-------------------------------------------------------------------- ; Calculate new positions for the metaballs ; UpdateMetaBallPositions: mov si,OFFSET start+46 mov di,OFFSET MetaBallPositions+2 mov cx,[di-2] fld t fadd tstep fst t imul cx,3 @@ball: ; Use our code as random input values, fld st ; and calculate new XYZ positions for fiadd w [si] ; the balls lodsw fmul scaledown1 fsincos fstp st fimul w [si] lodsw fmul scaledown2 fstp d [di] add di,4 loop @@ball ret t dd 0 tstep dd 0.1 scaledown1 dd 0.1 scaledown2 dd 0.0002 ;-------------------------------------------------------------------- ; Clear our rendering-buffer ; ClearBackbuffer: push es push fs pop es xor di,di xor eax,eax mov cx,16000 rep stosd pop es ; ret ; Fallthrough ;-------------------------------------------------------------------- ; Calculate field strength ; ; Iterates through the 3d grid, and at each intersection point in the ; grid, evaluates at that position in space the strength of the force ; field generated by the balls. ; ; The force function is the sum of the force created by each ball on ; point and the force created by each ball is either 1/distance^2 ; or distance^2 (depending on which field function you choose). ; Meta_calcFieldValues: push ax push ax push ax mov bx,sp mov bp,-8 mov di,OFFSET MetaFieldStrength mov [bx+4],bp ; Iterate through all field positions, @@z: mov [bx+2],bp ; evaluating the force function @@y: mov [bx],bp ; at each position @@x: mov si,OFFSET MetaBallPositions fldz lodsw xchg cx,ax @@ball: fld d [si] ; Calculate distance^2 between the fisub w [bx] ; intersection point and the ball fmul st,st ; center fld d [si+4] fisub w [bx+2] fmul st,st fld d [si+8] fisub w [bx+4] fmul st,st faddp st(1),st faddp st(1),st fadd fudgeFactor IF FIELDFUNC EQ 1 fld1 ; if you chose fieldfunction 1, fdivrp st(1),st ; convert distance^2 to 1/distance^2 ENDIF faddp st(1),st ; Add field strength to sum add si,12 loop @@ball fsub @@isoSurfaceThreshold ; Subtract threshold value fimul @@isoSurfaceScaler ; .. and scale up a bit, fistp d [di] ; we're going integer now mov edx,32767 ; Clamp field strength to mov eax,[di] ; -32767 .. +32767 cmp eax,edx jle @@nClampMax mov eax,edx @@nClampMax: neg edx cmp eax,edx jge @@nClampMin mov ax,dx @@nClampMin: stosw mov al,[bx] ; Store grid xyz position too stosb mov al,[bx+2] stosb mov al,[bx+4] stosb mov ax,8 inc w [bx] cmp ax,[bx] jg @@x inc w [bx+2] cmp ax,[bx+2] jg @@y inc w [bx+4] cmp ax,[bx+4] jg @@z add sp,6 ret IF FIELDFUNC EQ 1 @@isoSurfaceThreshold dd 0.3 @@isoSurfaceScaler dw 250 ELSEIF FIELDFUNC EQ 2 @@isoSurfaceThreshold dd 300.0 @@isoSurfaceScaler dw 128 ENDIF ;-------------------------------------------------------------------- ; Marching tetrahedrons ; ; This routine will iterate through the 3d grid, and build a polygonized ; representation of the area in space where field function = 0. ; ; For a thorough description of the marching cubes algorithm (which is ; very similar to the marching tetrahedrons algorithm, and more commonly ; known/used), visit www.exaflop.org and search around a bit. ; Meta_generatePolys: mov si,OFFSET MetaFieldStrength+14*5*16*16+14*5*16+14*5 xor ax,ax mov cl,15 ; Iterate through all cubes in @@z: mov ch,15 ; 3d-grid, performing mtetra @@y: push cx ; polygonization independently mov cl,15 ; inside each cube @@x: mov ch,8 ; First do early-out test: check mov di,OFFSET @@indexToOffset ; if all vertices are inside @@grabBit: ; or if all vertices are outside mov bx,[di] ; the field function. If so, no inc di ; polygons should be generated inc di ; in the current cube mov al,[si+bx+1] add ax,ax dec ch jnz @@grabBit inc ah cmp ah,1 jbe @@emptyCube push cx ; There will be triangles generated mov bx,OFFSET MetaTetraConfigs ; inside this cube; iterate over mov ch,[bx] ; the 6 tetrahedrons that make inc bx ; up the cube, and process each separately @@tetra: mov cl,4 ; Calculate in/out codes for each of @@tetraVertex: ; the current tetra's 4 vertices movzx bp,b [bx] inc bx mov bp,[@@indexToOffset+bp] mov dl,[si+bp+1] add dx,dx dec cl jnz @@tetraVertex push bx ; Depending on the in/out configuration, mov bl,dh ; choose one out of 15 pre-made and bx,0fh ; polygonization cases movzx bp,b [@@generateTriCases+bx] add bp,OFFSET @@generateTriCase0 pop bx mov cl,[bp] ; No polygons inside tetra? if so, skip test cl,cl jz @@skipTetra inc bp @@tetraTri: ; Emit 1 or 2 triangles inside current tetra push cx mov di,OFFSET MetaTempTri mov cl,3 ; Calculate 3 vertices for triangle @@tetraTriVertex: xor ax,ax mov al,[bp] inc bp mov dx,ax shr ax,2 and dl,3 call @@emitInterpolatedVertex dec cl jnz @@tetraTriVertex call @@finalizeTri ; Draw triangle pop cx dec cl ; Next triangle in tetra jnz @@tetraTri @@skipTetra: dec ch ; Next tetra in cube jnz @@tetra pop cx @@emptyCube: sub si,5 ; Next cube in grid dec cl jnz @@x sub si,5 pop cx dec ch jnz @@y sub si,5*16 dec cx jnz @@z ret @@indexToOffset dw 0,5,5*16,5*16+5,5*16*16,5*16*16+5,5*16*16+5*16,5*16*16+5*16+5 @@generateTriCases db @@generateTriCase0-@@generateTriCase0 db @@generateTriCase1-@@generateTriCase0 db @@generateTriCase2-@@generateTriCase0 db @@generateTriCase3-@@generateTriCase0 db @@generateTriCase4-@@generateTriCase0 db @@generateTriCase5-@@generateTriCase0 db @@generateTriCase6-@@generateTriCase0 db @@generateTriCase7-@@generateTriCase0 db @@generateTriCase8-@@generateTriCase0 db @@generateTriCase9-@@generateTriCase0 db @@generateTriCase10-@@generateTriCase0 db @@generateTriCase11-@@generateTriCase0 db @@generateTriCase12-@@generateTriCase0 db @@generateTriCase13-@@generateTriCase0 db @@generateTriCase14-@@generateTriCase0 db @@generateTriCase15-@@generateTriCase0 @@generateTriCase0: ; Triangle configurations inside each tetra, @@generateTriCase15: ; depending on the in/out vertex test db 0 @@generateTriCase1: @@generateTriCase14: db 1,0*4+3,1*4+3,2*4+3 @@generateTriCase2: @@generateTriCase13: db 1,0*4+2,1*4+2,3*4+2 @@generateTriCase3: @@generateTriCase12: db 2 db 1*4+2,1*4+3,0*4+3 db 1*4+2,0*4+3,0*4+2 @@generateTriCase4: @@generateTriCase11: db 1,0*4+1,2*4+1,3*4+1 @@generateTriCase5: @@generateTriCase10: db 2 db 0*4+1,0*4+3,2*4+3 db 0*4+1,2*4+3,2*4+1 @@generateTriCase6: @@generateTriCase9: db 2 db 1*4+0,1*4+3,2*4+3 db 1*4+0,2*4+3,2*4+0 @@generateTriCase7: @@generateTriCase8: db 1,1*4+0,2*4+0,3*4+0 ;----------------------------------------------------------------- ; Given two vertex-indices into the grid, interpolates XYZ positions ; between the two vertices (using the force function values as weights) ; to approximately calculate where the force function = 0 ; Then emits the XYZ for that position ; ; in ax vertex-index 0 ; dx vertex-index 1 ; di vertexbuffer ; out writes to [di++] @@emitInterpolatedVertex: pusha add ax,bx add bx,dx movzx bx,b [bx-4] xchg ax,bx movzx bx,b [bx-4] mov bp,[@@indexToOffset+bx] xchg ax,bx mov bx,[@@indexToOffset+bx] add bx,si add bp,si mov ax,[bx] ; calculate interpolation factor: mov cx,[bp] ; t = (force1 / (force1 - force2)) inc bx inc bx inc bp inc bp test ax,ax jns @@okayish xchg ax,cx xchg bx,bp @@okayish: neg cx add cx,ax cwd mov dl,ah mov ah,al div cx ; result is 0.8 fixed point xchg ax,si mov cl,3 ; Interpolate XYZ @@emitInterpolatedVertex_component: movsx dx,b [bx] inc bx movsx ax,b [bp] inc bp sub ax,dx push dx imul si pop dx add ah,dl stosw dec cl jnz @@emitInterpolatedVertex_component popa add di,3*2 ret ;----------------------------------------------------------------- ; Calculate polygon normal (via cross product), project the vertices, ; then draw the triangle with normal's Z component used as colour ; @@finalizeTri: pusha mov si,OFFSET MetaTempTri mov di,si pusha mov cx,3 @@projVtx: mov ax,2 ; Calculate triangle normal mov bx,4 call @@calcCrossComponent fmul st,st xor bx,bx call @@calcCrossComponent fmul st,st mov bx,2 call @@calcCrossComponent fld st fmul st,st faddp st(2),st fxch st(2) faddp st(1),st fadd fudgeFactor fsqrt fchs fdivp st(1),st ; .. and normalize Z component fimul @@normalScale lodsw ; Fetch vertex xchg bx,ax lodsw xchg bp,ax lodsw xchg ax,bp xchg ax,bx fistp w [di] inc di inc di add bp,300 SHL 4 ; .. and project mov dx,ax sar dx,4 shl ax,12 idiv bp add ax,160 SHL 4 stosw xchg ax,bx mov dx,ax sar dx,4 shl ax,12 idiv bp add ax,100 SHL 4 stosw loop @@projVtx popa neg w [si] ; Clamp tri colour to 0 jns @@nBack neg w [si] @@nBack: call DrawTri ; Draw tri mov eax,[si+4*2] ; Flip tri orientation xchg eax,[si+7*2] mov [si+4*2],eax call DrawTri ; Draw tri again (this takes ; care of tris that don't ; have proper clockwise orientation) popa ret @@calcCrossComponent: ; Calculate one component of cross product fild w [si+bx] fisub w [si+bx+3*2] fild w [si+bx] fisub w [si+bx+6*2] xchg ax,bx fild w [si+bx] fisub w [si+bx+3*2] fild w [si+bx] fisub w [si+bx+6*2] fmulp st(3),st fmulp st(1),st fsubp st(1),st ret @@normalScale dw 60 fudgeFactor dd 0.001 MetaTetraConfigs db 6 ; Cube subdivision into tetrahedrons db 7*2,6*2,5*2,3*2 db 4*2,5*2,3*2,6*2 db 4*2,3*2,2*2,6*2 db 1*2,4*2,5*2,3*2 db 1*2,3*2,2*2,4*2 db 0*2,4*2,1*2,2*2 MetaBallPositions dw 5 dd 5*3 dup (?) TriEdges dw 256*2 dup (?) MetaFieldStrength db 5*16*16*16 dup (?) MetaTempTri dw 3*3 dup (?) END start