Programming by Example

 

A BB4W Compendium

freeman69@gmx.com

IDIC BBC_Owl2 M&P

Point Of Light

In previous programs we've created graphics by using lines to join points, where the points (or 'nodes') are rotated prior to drawing. Basic 3D wireframe and solid graphics can be drawn using similar techniques. Wireframe uses lines, whereas solid graphics are based on filled triangles.

 

When using triangles, it's possible to simulate a point of light by using brighter shades of colour when a triangle (or plane) faces the light to a greater degree. Conversely, triangles facing away from the light are drawn using darker shades. Brightness can be determined by finding the angle between the 'vector pointing to a distant light source' and 'the normal of a plane'. (The normal is perpendicular to the plane).

 

  10 MODE 9:OFF

  20 ORIGIN 640,512

  30 *REFRESH OFF

  40 DIM nd{(18) xt,yt,zt,xr,yr,zr,x2,y2}:REM Nodes

  50 DIM link{(25) type,n1,n2,n3}:REM Links (colours & triangles)

  60 DIM td{(1) np,lp,nc,lc}:REM Net pointers

  70 FOR a=0 TO 18

  80   READ nd{(a)}.xt,nd{(a)}.yt,nd{(a)}.zt

  90 NEXT

 100 FOR a=0 TO 25

 110   READ link{(a)}.type,link{(a)}.n1,link{(a)}.n2,link{(a)}.n3

 120 NEXT

 130 FOR a=0 TO 1

 140   READ td{(a)}.np,td{(a)}.lp,td{(a)}.nc,td{(a)}.lc

 150 NEXT

 160 REM Create a unit vector pointing towards a light source

 170 lightx=100:lighty=100:lightz=10

 180 lm=SQR(lightx^2+lighty^2+lightz^2)

 190 lightx/=lm:lighty/=lm:lightz/=lm

 200

 210 TIME=0:za=0:xa=0:ya=0

 220 REPEAT

 230   T=TIME:CLS

 240   net=(TIME DIV 800)MOD 2

 250   PROCdraw(net,0,0,-40,za,xa,ya)

 260   za-=2.6:xa+=1:ya+=3.4

 270   *REFRESH

 280   WAIT 4-(TIME-T)

 290 UNTIL 0

 300 END

 310

 320 DEF PROCdraw(net,x,y,z,za,xa,ya)

 330 LOCAL f,abort,col,a,n1,n2,n3,v1x,v1y,v1z,v2x,v2y,v2z,nx,ny,nz,nm,v

 340 f=1280:abort=FALSE:col=7:GCOL 0,15

 350 FOR a=td{(net)}.np TO td{(net)}.np+td{(net)}.nc-1

 360   PROCrotzxy(a,x,y,z,za,xa,ya):REM Rotate node & add offset

 370   IF nd{(a)}.zr>-1 THEN

 380     abort=TRUE:REM Node behind viewer

 390   ELSE

 400     nd{(a)}.x2=f*nd{(a)}.xr/-nd{(a)}.zr:REM 3D to 2D

 410     nd{(a)}.y2=f*nd{(a)}.yr/-nd{(a)}.zr

 420   ENDIF

 430 NEXT

 440 IF abort ENDPROC

 450 FOR a=td{(net)}.lp TO td{(net)}.lp+td{(net)}.lc-1

 460   CASE link{(a)}.type OF

 470     WHEN 1 : col=link{(a)}.n1

 480     WHEN 7

 490       n1=link{(a)}.n1+td{(net)}.np

 500       n2=link{(a)}.n2+td{(net)}.np

 510       n3=link{(a)}.n3+td{(net)}.np

 520       REM Calc two vectors from 3 co-ords (triangle): b-a and c-a

 530       v1x=nd{(n2)}.xr-nd{(n1)}.xr

 540       v1y=nd{(n2)}.yr-nd{(n1)}.yr

 550       v1z=nd{(n2)}.zr-nd{(n1)}.zr

 560       v2x=nd{(n3)}.xr-nd{(n1)}.xr

 570       v2y=nd{(n3)}.yr-nd{(n1)}.yr

 580       v2z=nd{(n3)}.zr-nd{(n1)}.zr

 590       REM Find the 'normal' of a triangle, (cross product of two vectors)

 600       nx=v1y*v2z-v1z*v2y

 610       ny=v1z*v2x-v1x*v2z

 620       nz=v1x*v2y-v1y*v2x

 630       REM Vector towards viewer

 640       v1x=0-nd{(n1)}.xr

 650       v1y=0-nd{(n1)}.yr

 660       v1z=0-nd{(n1)}.zr

 670       REM Triangle faces viewer (normal points towards viewer)?

 680       IF (nx*v1x+ny*v1y+nz*v1z)>0 THEN

 690         nm=SQR(nx^2+ny^2+nz^2)

 700         REM Calc triangle shade from: A.B / |A||B|

 710         v=(nx*lightx+ny*lighty+nz*lightz)/nm

 720         if v<-1 v=0 else if v>1 v=255 else v=255-acs(v)/pi*255

 730         COLOUR 15,v*(col AND 1),v*((col AND 2)/2),v*((col AND 4)/4)

 740         MOVE nd{(n1)}.x2,nd{(n1)}.y2

 750         MOVE nd{(n2)}.x2,nd{(n2)}.y2

 760         PLOT 85,nd{(n3)}.x2,nd{(n3)}.y2

 770       ENDIF

 780   ENDCASE

 790 NEXT

 800 ENDPROC

 810

 820 REM Rotate a node about 3 axes and add any offset

 830 DEF PROCrotzxy(n,x,y,z,za,xa,ya)

 840 LOCAL ca,sa,x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4

 850 x1=nd{(n)}.xt:y1=nd{(n)}.yt:z1=nd{(n)}.zt

 860 z2=z1:ca=COS(RAD(za)):sa=SIN(RAD(za)):x2=x1*ca+y1*sa:y2=y1*ca-x1*sa

 870 x3=x2:ca=COS(RAD(xa)):sa=SIN(RAD(xa)):y3=y2*ca+z2*sa:z3=z2*ca-y2*sa

 880 y4=y3:ca=COS(RAD(ya)):sa=SIN(RAD(ya)):z4=z3*ca+x3*sa:x4=x3*ca-z3*sa

 890 nd{(n)}.xr=x4+x:nd{(n)}.yr=y4+y:nd{(n)}.zr=z4+z

 900 ENDPROC

 910

 920 REM Ship K nodes (x,y,z)

 930 DATA 0,4,-8,0,-4,-8,8,0,-4,-8,0,-4,0,0,8,6,4,-8

 940 DATA 6,-4,-8,-6,-4,-8,-6,4,-8,8,0,8,-8,0,8,8,0,-8,-8,0,-8

 950 REM Ship S

 960 DATA -3,0,8,3,0,8,8,0,-8,-8,0,-8,0,4,-4,0,-4,-4

 970 REM Ship K links (type,n1,n2,n3: type 1=colour, 7=triangle)

 980 DATA 1,1,0,0,7,7,12,10,7,12,8,10,7,11,6,9,7,9,5,11,1,7,0,0

 990 DATA 7,0,4,2,7,0,3,4,7,4,3,1,7,1,2,4,7,2,1,0,7,1,3,0

1000 DATA 1,1,0,0,7,7,10,12,7,12,10,8,7,9,6,11,7,9,11,5

1010 REM Ship S

1020 DATA 1,6,0,0,7,4,2,5,7,4,5,3,7,4,0,1,7,4,3,0,7,2,4,1

1030 DATA 7,5,1,0,7,5,2,1,7,5,0,3

1040 REM Ship pointers

1050 DATA 0,0,13,17

1060 DATA 13,17,6,9

POL

Point Of Light: Code explained...

 

  40 DIM nd{(18) xt,yt,zt,xr,yr,zr,x2,y2}:REM Nodes

  50 DIM link{(25) type,n1,n2,n3}:REM Links (colours & triangles)

  60 DIM td{(1) np,lp,nc,lc}:REM Net pointers

 

'xt', 'yt' & 'zt' hold the template nodes.

'xr', 'yr' & 'zr' will hold the transformed (rotated) template nodes.

'x2', 'y2' will hold the 2D co-ordinates of 3D rotated nodes projected onto the flat plane of the window.

 

The 'link' array holds the instructions for drawing the 3D shape. This program uses two types of instruction: 'type'=1 for changing the current colour and 'type'=7 to draw a triangle. The nodes (corners of the triangle) are pointed to by 'n1', 'n2' & 'n3'

 

'td' holds information on two separate 'nets'. A 'net' is the umbrella term for the template of a 3D object. This demonstration switches between 2 different net designs every 8 seconds.

 

 160 REM Create a unit vector pointing towards a light source

 170 lightx=100:lighty=100:lightz=10

 180 lm=SQR(lightx^2+lighty^2+lightz^2)

 190 lightx/=lm:lighty/=lm:lightz/=lm

 

To calculate the shade of each triangle, we need a light source and a vector pointing towards it. By making the vector a unit vector, we simplify the calculation slightly. (A unit vector has a magnitude of 1.)

 

 320 DEF PROCdraw(net,x,y,z,za,xa,ya)

 330 LOCAL f,abort,col,a,n1,n2,n3,v1x,v1y,v1z,v2x,v2y,v2z,nx,ny,nz,nm,v

 340 f=1280:abort=FALSE:col=7:GCOL 0,15

 350 FOR a=td{(net)}.np TO td{(net)}.np+td{(net)}.nc-1

 360   PROCrotzxy(a,x,y,z,za,xa,ya):REM Rotate node & add offset

 370   IF nd{(a)}.zr>-1 THEN

 380     abort=TRUE:REM Node behind viewer

 390   ELSE

 400     nd{(a)}.x2=f*nd{(a)}.xr/-nd{(a)}.zr:REM 3D to 2D

 410     nd{(a)}.y2=f*nd{(a)}.yr/-nd{(a)}.zr

 420   ENDIF

 430 NEXT

 440 IF abort ENDPROC

 

When drawing a net, every node must be rotated so the object possesses the orientation we require. In 2D we only rotate about the Z-axis i.e. in the XY plane. In 3D there are 3 axes to rotate around. The order of rotation affects the final orientation.

 

This routine simplifies drawing by aborting if any of the nodes fall on the wrong side of the window i.e. behind the viewer. Otherwise each node is converted to 2 dimensions by projecting it onto the window.

 

 450 FOR a=td{(net)}.lp TO td{(net)}.lp+td{(net)}.lc-1

 

The next stage involves reading the drawing instructions (or links). These instructions control the current colour and indicate which nodes are to be used to draw each triangle.

The design of the 3D net is extremely important. Nodes must be listed in the correct order i.e. anticlockwise, to face the viewer. With complex shapes, the order in which the triangles are drawn also makes a significant difference.

 

 460   CASE link{(a)}.type OF

 470     WHEN 1 : col=link{(a)}.n1

 480     WHEN 7

 490       n1=link{(a)}.n1+td{(net)}.np

 500       n2=link{(a)}.n2+td{(net)}.np

 510       n3=link{(a)}.n3+td{(net)}.np

 

'n1', 'n2' & 'n3' point to the specific row in the 'nd' (node) array.

 

 520       REM Calc two vectors from 3 co-ords (triangle): b-a and c-a

 530       v1x=nd{(n2)}.xr-nd{(n1)}.xr

 540       v1y=nd{(n2)}.yr-nd{(n1)}.yr

 550       v1z=nd{(n2)}.zr-nd{(n1)}.zr

 560       v2x=nd{(n3)}.xr-nd{(n1)}.xr

 570       v2y=nd{(n3)}.yr-nd{(n1)}.yr

 580       v2z=nd{(n3)}.zr-nd{(n1)}.zr

 590       REM Find the 'normal' of a triangle, (cross product of two vectors)

 600       nx=v1y*v2z-v1z*v2y

 610       ny=v1z*v2x-v1x*v2z

 620       nz=v1x*v2y-v1y*v2x

 

To calculate the shade of a triangle, we need to find the 'normal' of the plane, which involves finding the 'cross product' of two vectors within the plane.

 

 630       REM Vector towards viewer

 640       v1x=0-nd{(n1)}.xr

 650       v1y=0-nd{(n1)}.yr

 660       v1z=0-nd{(n1)}.zr

 670       REM Triangle faces viewer (normal points towards viewer)?

 680       IF (nx*v1x+ny*v1y+nz*v1z)>0 THEN

 

By finding the 'normal' of each triangle, we can determine whether the triangle is facing the viewer or not: Is there less than 90 degrees difference between the 'normal' and 'a vector from the triangle towards the viewer'?

Note that our routine could be made more efficient, preventing these calculations from being repeated for flat polygons comprised of multiple triangles.

 

 690         nm=SQR(nx^2+ny^2+nz^2)

 700         REM Calc triangle shade from: A.B / |A||B|

 710         v=(nx*lightx+ny*lighty+nz*lightz)/nm

 720         v=255-ABS(ACS(v))/PI*255

 730         COLOUR 15,v*(col AND 1),v*((col AND 2)/2),v*((col AND 4)/4)

 740         MOVE nd{(n1)}.x2,nd{(n1)}.y2

 750         MOVE nd{(n2)}.x2,nd{(n2)}.y2

 760         PLOT 85,nd{(n3)}.x2,nd{(n3)}.y2

 770       ENDIF

 780   ENDCASE

 790 NEXT

 800 ENDPROC

 

The angle of the triangle to the light, between zero and 180 degrees, will be converted to a value between zero and 255, representing the full range of shades of the basic palette.

 

 820 REM Rotate a node about 3 axes and add any offset

 830 DEF PROCrotzxy(n,x,y,z,za,xa,ya)

 840 LOCAL ca,sa,x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4

 850 x1=nd{(n)}.xt:y1=nd{(n)}.yt:z1=nd{(n)}.zt

 860 z2=z1:ca=COS(RAD(za)):sa=SIN(RAD(za)):x2=x1*ca+y1*sa:y2=y1*ca-x1*sa

 870 x3=x2:ca=COS(RAD(xa)):sa=SIN(RAD(xa)):y3=y2*ca+z2*sa:z3=z2*ca-y2*sa

 880 y4=y3:ca=COS(RAD(ya)):sa=SIN(RAD(ya)):z4=z3*ca+x3*sa:x4=x3*ca-z3*sa

 890 nd{(n)}.xr=x4+x:nd{(n)}.yr=y4+y:nd{(n)}.zr=z4+z

 900 ENDPROC

 

As mentioned previously, the order of rotation affects the final orientation of the 3D net. The direction of the axes affects the direction of rotation.

Rotation Arrow black large Arrow black large