Compatible with:

DOS Maximite CMM MM150 MM170 MM+ MMX Picromite ArmiteL4
Armite F4 ArmiteH7 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)**

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
D**RAW3D 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