# DRAW3D

Compatible with:
DOS Maximite CMM MM150 MM170 MM+ MMX Picromite ArmiteL4 Armite F4 ArmiteH7 Picomite CMM2

Syntax:
DRAW3D CAMERA n, z_viewplane[,x_camera [,y_camera] [,PAN_X] [,PAN_Y]
DRAW3D CLOSE n [,n1 [,n2...]]
DRAW3D CLOSE ALL
DRAW3D CREATE nv, nf, camera, vertices(), fc(), faces(), colours() , edge() ,fill()
DRAW3D DIAGNOSE objectno, x, y, z
DRAW3D HIDE n [,n1 [,n2...]]
DRAW3D HIDE ALL
DRAW3D RESET n [,n1 [,n2...]}
DRAW3D ROTATE q(), n [,n1 [,n2...]}
DRAW3D SHOW n, x, y, z [,nocull]
DRAW3D WRITE n, x, y, z [,nocull]
DRAW3D(xmin n)
DRAW3D(xmax n)
DRAW3D(ymin n)
DRAW3D(ymax n)
DRAW3D(x n)
DRAW3D(y n)
DRAW3D(z n)

Description:

You can define up to 128 3D objects numbered 1 to 128.
Each object is described in terms of how many vertices it has, how many faces it has, which vertices make up each face and the colours of the edges and infill of each face. Objects can have a maximum of 64 vertices and 64 faces.
The vertices are specified as x,y,z coordinates referenced to the object centre at 0,0,0

In addition for each object you will define the "camera" that is used to view the object. The 3D engine supports up to 6 camera positions numbered 1 to 6

All cameras look along their Z axis and before you display a 3D object the associated camera must be initialised by defining the x,y position of the camera and its viewplane. In camera terms the viewplane can be thought of as the focal length of the camera lens. So the bigger the value of the viewplane the more the camera "magnifies" the image.

DRAW3D CREATE nv, nf, camera, vertices(), fc(), faces(), colours() , edge() ,fill()
DRAW3D CREATE is the command that creates a 3D object and all the information needed for the object is included in the parameter list.
nv: number of vertices (e.g. 8 for a cube)
nf: number of faces (e.g. 6 for a cube)
camera: number of the camera to use when displaying the object (1 to 6)
vertices(): This is a 3 by nv array that holds the x,y,z coordinates of the 3D object. For example the vertex definition for our cube with side length 2 with Option Base 0 centred on 0,0,0 could be: vertices(2,7) = (-1,1,-1, 1,1,-1, 1,-1,-1, -1,-1,-1, -1,1,1, 1,1,1, 1,-1,1, -1,-1,1). Note that the negative values represent the vertices closest to the camera.
fc(): is a count of how many vertices are needed to define each face, so in our example for the cube which has 6 faces it would be fc(7)=(4,4,4,4,4,4)
faces(): This is a very important array and defines the vertices that make up each face of the 3D object. There is one critical thing in setting up this array. The vertices must be listed in a clockwise order for each face as though you were looking at that face from in front of it. It doesn't matter which order the faces are listed as long as they match the correct vertex count in fc() and it doesn't matter which vertex you start on for each face. In our example this array could be: faces(23)=(0,1,2,3, 1,5,6,2, 0,4,5,1, 5,4,7,6, 2,6,7,3, 0,3,7,4)
colours(): This is an array that holds a simple list of all the colours you want to use to colour the 3D object. So if we want a different colour for each face and another one for all the edges we could set this array as follow: colours(6)=(rgb(blue), rgb(green), rgb(yellow), rgb(cyan), rgb(red), rgb(magenta), rgb(yellow))
edge(): This arrays specifies which of our colours to use for each edge of the 3D object. We will set them all to the array index in colours() holding the value yellow edge(5)=(6,6,6,6,6,6)
fill(): This array specifies which colour to use for each face of the 3D object. We will set them each to a different colour by specifying the array index into colours() fill(5)=(0,1,2,3,4,5)

DRAW3D ROTATE q(), n [,n1 [,n2...]}
q() is a matrix (quaternion) that defines the required rotation. We use quaternions because they don't suffer from gimbal lock and are computationally fairly efficient but the math is completely hidden by the firmware.
The n values are the 3D object IDs assigned when the object was created.
From the perspective of the MMBasic user a quaternion is simply a 5 element floating point array and it is loaded using one of two methods
MATH Q_EULER yaw, pitch, roll, q()
MATH Q_CREATE theta, x, y, z, q()
All objects specified in the ROTATE command are rotated by the same amount. Nothing happens on the screen but internally the firmware stores the rotated coordinates as well as the original ones.
It is very important to note that the rotate command acts on the original object as defined in the CREATE command. Rotate commands are not cumulative. This ensures that rounding errors can not affect the accuracy.
However, there is a command that can override this

DRAW3D RESET n [,n1 [,n2...]}
This command takes the current rotated version of the object(s) and copies it into the initialisation data. Whilst this isn't recommended for iterative rotations it is very useful in establishing multiple views of the same object.

DRAW3D CAMERA n, z_viewplane[,x_camera [,y_camera] [,PAN_X] [,PAN_Y]
This says position the camera at 3D coordinates x, y, 0 and any 3D objects will project onto an imaginary screen "viewplane" units in front of the camera along the z axis and orthogonal to it. In our world the camera is always at a z position of zero and objects will always be positioned with a positive value of z. In addition the camera will always point directly along the z axis. In future these constraints may change but for efficiency of calculation they make a lot of sense. Up to 6 camera positions may be defined (1 <= n <= 6) and an object will always be displayed using the camera that was defined when it was created. The camera must be configured using the DRAW3D CAMERA command before any object using it can be displayed.
The camera number n and the viewplane z distance are mandatory, all other parameters are optional and all default to 0
A practical example makes this clearer. Suppose we position a number of objects in the 3D world with their lower extermities at x, 0, z. In other words they are all sitting on the ground. To look at them we may want the camera somwhere above the ground so we are looking down on them. If the viewport is centred on the camera (the default) then all the objects will appear in the bottom half of the screen. Now this may be exactly what we want but if not, the firmware allows you to pan the viewport up and down and/or left and right relative to the camera. So in our example we could pan the viewport down to better frame the image on the screen. This does not change the perspective of the image, that is locked in by the relative positions of the object and the camera. It merely allows us to frame the image better given our limited screen resolution.

DRAW3D SHOW n, x, y, z [,nocull]
This says that we want to position the centre of the object at coordinates x, y, z in our virtual 3D world. The camera specified for our object is at a position Xc, Yc, 0. This command projects the object 'n' onto the imaginary screen at "viewpoint" from the camera. The mechanism of projection interprets the relative position of the object in 3 dimensions and does full perspective compensation taking into account the relative positions of each vertex in three dimensional space relative to the viewplane and the x,y coordinates of the camera. As it displays the object it calculates the screen coordinates of the minimum rectangle into which the rendered object fits. This allows a subsequent SHOW command (but not WRITE command) to erase the previous render and draw the object onto a clean screen.
Set nocull to 1 to disable calculation of normals and hidden face culling and relies on Painter's algorithm for the display. Omit or set to 0 for normal culling

Unlike sprites, 3D objects do not store the background image when the object is written or restore it when the object moves. It is recommended that 3D objects are written onto a blank page and are blitted or page copied (with transparency) onto the background image. Alternatively, putting 3D objects onto page 1 in 12-bit mode with the background on page 0 will work very well.

DRAW3D WRITE n, x, y, z [,nocull]
DRAW3D WRITE and DRAW3D SHOW. The only difference is that, assuming the object was already displayed, the SHOW command will clear a rectangle on the current write page sufficient to remove the existing object image before displaying it whereas DRAW3D WRITE just overwrites whatever in on the write page with the 2D representation of the object.
The syntax for both commands is the same.

DRAW3D HIDE n [,n1 [,n2...]]
DRAW3D HIDE ALL
DRAW3D HIDE hides one or more 3D objects that have been rendered using SHOW by clearing the screen in the area occupied by the object.
DRAW3D HIDE ALL does the same for all 3D objects

DRAW3D CLOSE n [,n1 [,n2...]]
DRAW3D CLOSE ALL
DRAW3D CLOSE both hides any 3D objects that have been rendered using SHOW and deletes the object in memory freeing up both the memory used and the object "slot"
DRAW3D CLOSE ALL does the same for all objects

DRAW3D DIAGNOSE objectno, x, y, z
After you have created the object and set a camera position you can use the command DRAW3D DIAGNOSE.
This calculates the position of the 3D object and then lists the faces in depth order with an analysis of whether they would be hidden or not based on their surface normal.
This should make it much easier to test the object data to confirm that the vertex ordering for each of the faces is correct.

option explicit
option default float
mode 1,8
dim integer viewplane = 500
const camera = 1
dim q(4)
dim yaw=rad(1),pitch=rad(2),roll=rad(0.5)
dim integer nv=9, nf=9 ' cube has 9 vertices and 9 faces
'array to hold vertices
dim v(2,nv-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, 0,0,0)
math scale v(),100,v()
' array to hold number of vertices for each face
dim integer fc(nf-1) =(4,4,4,4,4,3,3,3,3)
dim integer cindex(9)=(rgb(red),rgb(blue),rgb(green),rgb(magenta),rgb(yellow),rgb(cyan),rgb(white),rgb(brown),rgb(gray),0)
dim integer fcol(nf-1)=(9,9,9,9,9,9,9,9,9)
dim integer bcol(nf-1)=(0,1,2,3,4,5,6,7,8)
'array to hold vertices for each face
dim integer fv(math(sum fc())-1)=(1,5,6,2, 1,0,4,5, 0,3,7,4, 5,4,7,6, 2,6,7,3, 0,1,8, 1,2,8, 3,8,2 , 3,0,8)
draw3d create 1, nv, nf, camera, v(), fc(), fv(),cindex(),fcol(),bcol()
dim integer c
page write 1
cls
'draw3d diagnose 1,0,0,1000
gui cursor on 1,0,mm.vres\2
box 0,0,mm.hres-1,mm.vres-1
do
for c=-399 to 399
gui cursor c+400,MM.Vres\2-c*600/800
draw3d camera 1,viewplane,c,c*600/800
math q_create roll,1,1,1,q()
draw3d show 1,0,0,1000
math q_euler yaw,pitch,roll,q()
draw3d rotate q(),1
inc yaw,rad(1)
inc pitch,rad(2)
inc roll,rad(0.5)
page copy 1 to 0
pause 20
next
for c=399 to -399 step -1
gui cursor c+400,MM.Vres\2-c*600/800
draw3d camera 1,viewplane,c,c*600/800
math q_create roll,1,1,1,q()
draw3d show 1,0,0,1000
math q_euler yaw,pitch,roll,q()
draw3d rotate q(),1
inc yaw,rad(1)
inc pitch,rad(2)
inc roll,rad(0.5)
page copy 1 to 0
pause 20
next
loop
draw3d close all

Functions:

DRAW3D(xmin n)
'returns the leftmost x coordinate of a box bounding the render of 3d object n on the screen

DRAW3D(xmax n)
'returns the rightmost x coordinate of a box bounding the render of 3d object n on the screen

DRAW3D(ymin n)
'returns the upper y coordinate of a box bounding the render of 3d object n on the screen

DRAW3D(ymax n)
'returns the lower y coordinate of a box bounding the render of 3d object n on the screen

DRAW3D(x n)
'returns the x coordinate in the world current used to display 3D object n

DRAW3D(y n)
'returns the y coordinate in the world current used to display 3D object n

DRAW3D(z n)
'returns the z coordinate in the world current used to display 3D object n

Last edited: 04 December, 2020