3d without openGL

User projects written in or related to FreeBASIC.
BasicCoder2
Posts: 3400
Joined: Jan 01, 2009 7:03

3d without openGL

For some unknown reason I have for the time being taken an interest again in doing some 3D stuff using only the FreeBasic's graphic commands.

I have been scouring the internet for a simple explanation of a rotation problem I encountered while playing with some simple code that I recently posted and so far I have come up with it being the need to rotate around an arbitrary axis. It appears that unlike the 2d world none of this 3d world math is simple. The explanations are given in mathematical notation that I am not familiar with so it would be a very big learning curve for me to take on.

I think I need to write the code to perform these actions:
Rotation around arbitrary axis:
1. Translate the P0 (x0, y0, z0) axis point to the origin of the coordinate system.
2. Perform appropriate rotations to make the axis of rotation coincident with z-coordinate axis.
3. Rotate about the z-axis by the angle θ.
4. Perform the inverse of the combined rotation transformation.
5. Perform the inverse of the translation.

Which I am assuming is illustrated here.
Last edited by BasicCoder2 on Oct 08, 2017 5:52, edited 2 times in total.
paul doe
Posts: 922
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: 3d without openGL

Hi BasicCoder2

I'm preparing something for you. When I finish it, I will post it here or on a separate thread, if you prefer. The code is relatively complicated (all 3D stuff is), but we can review it, step by step. Just not right now, as I'm too drunk. But as soon as I finish, I will let you know, ok? Until then, see ya.
dafhi
Posts: 1251
Joined: Jun 04, 2005 9:51

Re: 3d without openGL

i'll let paul doe do his thing and just say a few words. rotation about an arbitrary axis is not an easy thing. mathoma on youtube gives a great video about rodrigues rotation. vector arithmetic is rock-simple however, and vectors are the building blocks of "deeper" 3d, including rodrigues rotation

I also recommend mathoma's video on complex number multiplication
BasicCoder2
Posts: 3400
Joined: Jan 01, 2009 7:03

Re: 3d without openGL

This is where I am at so far.
The rocket seems to rotate around the x axis where ever it is due I think to the rotation routine dealing with that axis first?

Code: Select all

'some useful defines
Const Pi = 4 * Atn(1)
Dim Shared As Double TwoPi = 8 * Atn(1)
Dim Shared As Double RtoD = 180 / Pi   ' radians * RtoD = degrees
Dim Shared As Double DtoR = Pi / 180   ' degrees * DtoR = radians

const SCRW = 800
const SCRH = 600
screenres SCRW,SCRH,32
color rgb(0,0,0),rgb(255,255,255):cls

dim shared as integer midX,midY  'origin position(0,0,0) position on screen
midX = SCRW\2
midY = SCRH\2

const PointCount = 44
const LineCount  = 73

type POINT3D
as single x    'absolute position
as single y
as single z
as single rx   'position after rotation
as single ry
as single rz
as ulong  c    'color of pixel
end type

type GROUP
as POINT3D pt(0 to pointCount)  'set of 32 points
as single cx    'point of rotation in absolute space
as single cy
as single cz
as single ax   'rotation around axis cx,cy
as single ay
as single az
as ulong  c     'color of group's points
as single dx    'direction of movement along x and y coordinates
as single dy
as single dz
end type

type ALINE
as integer pt1
as integer pt2
as ulong   c
end type

dim shared as GROUP rocket  'group of dots called rocket

'initialize rocket points
for i as integer = 0 to pointCount
next i

dim shared as ALINE aLine1(0 to lineCount)
for i as integer = 0 to lineCount
read aLine1(i).pt1,aLine1(i).pt2  'read start and end points of line
aLine1(i).c = rgb(0,0,0)          'color the lines black
next i

'recolor axes lines
aLine1(70).c = rgb(255,0,0)
aLine1(71).c = rgb(0,255,0)
aLine1(72).c = rgb(0,0,255)

rocket.cx = 150
rocket.cy = 150
rocket.cz = 0

rocket.ax  = 0  'rotation around axes
rocket.ay  = 0
rocket.az  = 0

rocket.c  = rgb(200,100,25)  'color of group
rocket.dx = 1
rocket.dy = 0

sub drawAxes()
line (0,100)-(800,500),rgb(255,0,0) 'y axis
line (0,500)-(800,100),rgb(0,255,0) 'x axis
line (400,300)-(400,200),rgb(0,0,255) 'z axis
line (400,200)-(380,220),rgb(0,0,255) 'up arrow
line (400,200)-(420,220),rgb(0,0,255)
end sub

sub rotatePoints()

dim as single cosAngleX,sinAngleX,angleX
dim as single cosAngleY,sinAngleY,angleY
dim as single cosAngleZ,sinAngleZ,angleZ

angleX    = rocket.ax*DtoR
cosAngleX = cos(angleX)
sinAngleX = sin(angleX)

angleY    = rocket.ay*DtoR
cosAngleY = cos(angleY)
sinAngleY = sin(angleY)

angleZ    = rocket.az*DtoR
cosAngleZ = cos(angleZ)
sinAngleZ = sin(angleZ)

'=========================================

dim as single newX,newY,newZ,x,y,z

for i as integer = 0 to pointCount

x = rocket.pt(i).x
y = rocket.pt(i).y
z = rocket.pt(i).z

'***Rotation on the X-axis
NewY = y*cosAngleX - z*sinAngleX
NewZ = z*cosAngleX + y*sinAngleX
y = NewY
z = NewZ

'***Rotation on the Y-axis
NewZ = z*cosAngleY - x*sinAngleY
NewX = x*cosAngleY + z*sinAngleY
x = NewX

'***Rotation on the Z-axis
NewX = x*cosAngleZ - y*sinAngleZ
NewY = y*cosAngleZ + x*sinAngleZ

rocket.pt(i).rx = NewX
rocket.pt(i).ry = NewY
rocket.pt(i).rz = NewZ

rocket.pt(i).c = rocket.pt(i).c

next i

end sub

sub drawScene()
dim as single x,y,z,x1,y1,z1,x2,y2,z2
z = 0
rotatePoints()
screenlock
cls
drawAxes()

locate 2,2
print " use the arrow keys and the Z or X key to rotate"
print " use WASD keys to translate along x and y axes"
print " hit space key to reset position"

x = rocket.cx + midX   'screen coordinates
y = rocket.cy + midY

circle ( x - y + 300, (x + y)/2 - z - 50),5,rocket.c,,,0.4,f

for i as integer = 0 to pointCount  'draw each point in each group

'get x,y positions
x = rocket.pt(i).rx + rocket.cx + midX
y = rocket.pt(i).ry + rocket.cy + midY
z = rocket.pt(i).rz

'draw rotation point in isometric display
circle ( x - y + 300, (x + y)/2 - z - 50),2,rocket.pt(i).c,,,0.4,f

next i

'draw lines
for i as integer = 0 to lineCount  'number of lines
x1 = rocket.pt(aLine1(i).pt1).rx + rocket.cx + midX
y1 = rocket.pt(aLine1(i).pt1).ry + rocket.cy + midY
z1 = rocket.pt(aLine1(i).pt1).rz
x2 = rocket.pt(aLine1(i).pt2).rx + rocket.cx + midX
y2 = rocket.pt(aLine1(i).pt2).ry + rocket.cy + midY
z2 = rocket.pt(aLine1(i).pt2).rz

line ( x1 - y1 + 300, (x1 + y1)/2 - z1 - 50)-( x2 - y2 + 300, (x2 + y2)/2 - z2 - 50)  ,aLine1(i).c

next i

screenunlock
end sub

dim as string key
do

drawScene

rotatePoints()

if multikey(&H39) then  'space key to reset rocket variables
rocket.ax = 0
rocket.ay = 0
rocket.az = 0
rocket.cx = 150
rocket.cy = 150
rocket.cz = 0
while multikey(&H39):wend
end if

if multikey(&H1E) then
rocket.cx = rocket.cx - 1
end if

if multikey(&H20) then
rocket.cx = rocket.cx + 1
end if

if multikey(&H1F) then
rocket.cy = rocket.cy - 1
end if

if multikey(&H11) then
rocket.cy = rocket.cy + 1
end if

'rotate around x axis
if multikey(&H48) then
rocket.ax = rocket.ax + 1
if rocket.ax = 360 then rocket.ax = 0
end if
if multikey(&H50) then
rocket.ax = rocket.ax - 1
if rocket.ax < 0 then rocket.ax = 359
end if

'rotate around y axis
if multikey(&H4B) then
rocket.ay = rocket.ay + 1
if rocket.ay = 360 then rocket.ay = 0
end if
if multikey(&H4D) then
rocket.ay = rocket.ay - 1
if rocket.ay < 0 then rocket.ay = 359
end if

'rotate around z axis
if multikey(&H2C) then   'Z KEY
rocket.az = rocket.az + 1
if rocket.az = 360 then rocket.az = 0
end if
if multikey(&H2D) then   'X KEY
rocket.az = rocket.az - 1
if rocket.az < 0 then rocket.az = 359
end if

'=======================================

sleep 2
loop until multikey(&H01)

'=========================== 3D POINT DATA ===================================
' points for rocket cylinder
data   0,-24,-50,  10,-22,-50,  17,-17,-50,  22,-10,-50
data  24,  0,-50,  22, 10,-50,  17, 17,-50,  10, 22,-50
data   0, 24,-50, -10, 22,-50, -17, 17,-50, -22, 10,-50
data -24,  0,-50, -22,-10,-50, -17,-17,-50, -10,-22,-50

data   0,-24, 50,  10,-22, 50,  17,-17, 50,  22,-10, 50
data  24,  0, 50,  22, 10, 50,  17, 17, 50,  10, 22, 50
data   0, 24, 50, -10, 22, 50, -17, 17, 50, -22, 10, 50
data -24,  0, 50, -22,-10, 50, -17,-17, 50, -10,-22, 50

data 0,0,80  'nose cone point

'rocket fins
data -44,0,-50, -24,0,-50, -24,0,-30
data  44,0,-50,  24,0,-50,  24,0,-30

'points for relative axis
data 100,0,0, -100,0,0  'x axis
data 0,100,0, 0,-100,0  'y axis
data 0,0,100, 0,0,-100  'z axis

'=============================  LINE DATA ==================================
' 16 lines joining points
data  0,16, 1,17,  2,18,  3,19,  4,20,  5,21,  6,22,  7,23
data  8,24, 9,25, 10,26, 11,27, 12,28, 13,29, 14,30, 15,31

' lines to cone tip
data  32,16, 32,17, 32,18, 32,19, 32,20, 32,21, 32,22, 32,23
data  32,24, 32,25, 32,26, 32,27, 32,28, 32,29, 32,30, 32,31

' lines for fins
data  33,34, 34,35, 35,33
data  36,37, 37,38, 38,36

'lines to draw circumference of top and bottom circle
data 0 , 1,  1, 2,  2, 3,  3, 4,  4, 5,  5, 6,  6, 7,  7, 8,  8, 9,  9,10, 10,11, 11,12, 12,13, 13,14, 14,15, 15, 0
data 16,17, 17,18, 18,19, 19,20, 20,21, 21,22, 22,23, 23,24, 24,25, 25,26, 26,27, 27,28, 28,29, 29,30, 30,31, 31,16

'axis lines
data 39,40, 41,42, 43,44
dafhi
Posts: 1251
Joined: Jun 04, 2005 9:51

Re: 3d without openGL

i'm kinda busy, but check this out

Code: Select all

type p3d
as single x,y,z
End Type

operator *(l as single,r as p3d) as p3d: return type(r.x*l,r.y*l,r.z*l): end operator
operator *(l as p3d,r as single) as p3d: return type(l.x*r,l.y*r,l.z*r): end operator
operator *(l as p3d,r as p3d) as p3d: return type(l.x*r.x,l.y*r.y,l.z*r.z): end operator
operator -(l as p3d,r as p3d) as p3d: return type(l.x-r.x,l.y-r.y,l.z-r.z): end operator
operator +(l as p3d,r as p3d) as p3d: return type(l.x+r.x,l.y+r.y,l.z+r.z): end operator

type POINT3D
union
as p3d    ori
type
as single x    'absolute position
as single y
as single z
end type
end union
union
as p3d    ori_r
type
as single rx   'position after rotation
as single ry
as single rz
end type
end union
as ulong  c    'color of pixel
end type

var w = 800, midx = w/2
var h = 600, midy = h/2

screenres w,h

dim as point3d  p

p.ori += type(midx, midy)

pset (p.x, p.y)

sleep
fatman2021
Posts: 135
Joined: Dec 14, 2013 0:43

Re: 3d without openGL

BasicCoder2 wrote:This is where I am at so far.
The rocket seems to rotate around the x axis where ever it is due I think to the rotation routine dealing with that axis first?

Code: Select all

'some useful defines
Const Pi = 4 * Atn(1)
Dim Shared As Double TwoPi = 8 * Atn(1)
Dim Shared As Double RtoD = 180 / Pi   ' radians * RtoD = degrees
Dim Shared As Double DtoR = Pi / 180   ' degrees * DtoR = radians

const SCRW = 800
const SCRH = 600
screenres SCRW,SCRH,32
color rgb(0,0,0),rgb(255,255,255):cls

dim shared as integer midX,midY  'origin position(0,0,0) position on screen
midX = SCRW\2
midY = SCRH\2

const PointCount = 44
const LineCount  = 73

type POINT3D
as single x    'absolute position
as single y
as single z
as single rx   'position after rotation
as single ry
as single rz
as ulong  c    'color of pixel
end type

type GROUP
as POINT3D pt(0 to pointCount)  'set of 32 points
as single cx    'point of rotation in absolute space
as single cy
as single cz
as single ax   'rotation around axis cx,cy
as single ay
as single az
as ulong  c     'color of group's points
as single dx    'direction of movement along x and y coordinates
as single dy
as single dz
end type

type ALINE
as integer pt1
as integer pt2
as ulong   c
end type

dim shared as GROUP rocket  'group of dots called rocket

'initialize rocket points
for i as integer = 0 to pointCount
next i

dim shared as ALINE aLine1(0 to lineCount)
for i as integer = 0 to lineCount
read aLine1(i).pt1,aLine1(i).pt2  'read start and end points of line
aLine1(i).c = rgb(0,0,0)          'color the lines black
next i

'recolor axes lines
aLine1(70).c = rgb(255,0,0)
aLine1(71).c = rgb(0,255,0)
aLine1(72).c = rgb(0,0,255)

rocket.cx = 150
rocket.cy = 150
rocket.cz = 0

rocket.ax  = 0  'rotation around axes
rocket.ay  = 0
rocket.az  = 0

rocket.c  = rgb(200,100,25)  'color of group
rocket.dx = 1
rocket.dy = 0

sub drawAxes()
line (0,100)-(800,500),rgb(255,0,0) 'y axis
line (0,500)-(800,100),rgb(0,255,0) 'x axis
line (400,300)-(400,200),rgb(0,0,255) 'z axis
line (400,200)-(380,220),rgb(0,0,255) 'up arrow
line (400,200)-(420,220),rgb(0,0,255)
end sub

sub rotatePoints()

dim as single cosAngleX,sinAngleX,angleX
dim as single cosAngleY,sinAngleY,angleY
dim as single cosAngleZ,sinAngleZ,angleZ

angleX    = rocket.ax*DtoR
cosAngleX = cos(angleX)
sinAngleX = sin(angleX)

angleY    = rocket.ay*DtoR
cosAngleY = cos(angleY)
sinAngleY = sin(angleY)

angleZ    = rocket.az*DtoR
cosAngleZ = cos(angleZ)
sinAngleZ = sin(angleZ)

'=========================================

dim as single newX,newY,newZ,x,y,z

for i as integer = 0 to pointCount

x = rocket.pt(i).x
y = rocket.pt(i).y
z = rocket.pt(i).z

'***Rotation on the X-axis
NewY = y*cosAngleX - z*sinAngleX
NewZ = z*cosAngleX + y*sinAngleX
y = NewY
z = NewZ

'***Rotation on the Y-axis
NewZ = z*cosAngleY - x*sinAngleY
NewX = x*cosAngleY + z*sinAngleY
x = NewX

'***Rotation on the Z-axis
NewX = x*cosAngleZ - y*sinAngleZ
NewY = y*cosAngleZ + x*sinAngleZ

rocket.pt(i).rx = NewX
rocket.pt(i).ry = NewY
rocket.pt(i).rz = NewZ

rocket.pt(i).c = rocket.pt(i).c

next i

end sub

sub drawScene()
dim as single x,y,z,x1,y1,z1,x2,y2,z2
z = 0
rotatePoints()
screenlock
cls
drawAxes()

locate 2,2
print " use the arrow keys and the Z or X key to rotate"
print " use WASD keys to translate along x and y axes"
print " hit space key to reset position"

x = rocket.cx + midX   'screen coordinates
y = rocket.cy + midY

circle ( x - y + 300, (x + y)/2 - z - 50),5,rocket.c,,,0.4,f

for i as integer = 0 to pointCount  'draw each point in each group

'get x,y positions
x = rocket.pt(i).rx + rocket.cx + midX
y = rocket.pt(i).ry + rocket.cy + midY
z = rocket.pt(i).rz

'draw rotation point in isometric display
circle ( x - y + 300, (x + y)/2 - z - 50),2,rocket.pt(i).c,,,0.4,f

next i

'draw lines
for i as integer = 0 to lineCount  'number of lines
x1 = rocket.pt(aLine1(i).pt1).rx + rocket.cx + midX
y1 = rocket.pt(aLine1(i).pt1).ry + rocket.cy + midY
z1 = rocket.pt(aLine1(i).pt1).rz
x2 = rocket.pt(aLine1(i).pt2).rx + rocket.cx + midX
y2 = rocket.pt(aLine1(i).pt2).ry + rocket.cy + midY
z2 = rocket.pt(aLine1(i).pt2).rz

line ( x1 - y1 + 300, (x1 + y1)/2 - z1 - 50)-( x2 - y2 + 300, (x2 + y2)/2 - z2 - 50)  ,aLine1(i).c

next i

screenunlock
end sub

dim as string key
do

drawScene

rotatePoints()

if multikey(&H39) then  'space key to reset rocket variables
rocket.ax = 0
rocket.ay = 0
rocket.az = 0
rocket.cx = 150
rocket.cy = 150
rocket.cz = 0
while multikey(&H39):wend
end if

if multikey(&H1E) then
rocket.cx = rocket.cx - 1
end if

if multikey(&H20) then
rocket.cx = rocket.cx + 1
end if

if multikey(&H1F) then
rocket.cy = rocket.cy - 1
end if

if multikey(&H11) then
rocket.cy = rocket.cy + 1
end if

'rotate around x axis
if multikey(&H48) then
rocket.ax = rocket.ax + 1
if rocket.ax = 360 then rocket.ax = 0
end if
if multikey(&H50) then
rocket.ax = rocket.ax - 1
if rocket.ax < 0 then rocket.ax = 359
end if

'rotate around y axis
if multikey(&H4B) then
rocket.ay = rocket.ay + 1
if rocket.ay = 360 then rocket.ay = 0
end if
if multikey(&H4D) then
rocket.ay = rocket.ay - 1
if rocket.ay < 0 then rocket.ay = 359
end if

'rotate around z axis
if multikey(&H2C) then   'Z KEY
rocket.az = rocket.az + 1
if rocket.az = 360 then rocket.az = 0
end if
if multikey(&H2D) then   'X KEY
rocket.az = rocket.az - 1
if rocket.az < 0 then rocket.az = 359
end if

'=======================================

sleep 2
loop until multikey(&H01)

'=========================== 3D POINT DATA ===================================
' points for rocket cylinder
data   0,-24,-50,  10,-22,-50,  17,-17,-50,  22,-10,-50
data  24,  0,-50,  22, 10,-50,  17, 17,-50,  10, 22,-50
data   0, 24,-50, -10, 22,-50, -17, 17,-50, -22, 10,-50
data -24,  0,-50, -22,-10,-50, -17,-17,-50, -10,-22,-50

data   0,-24, 50,  10,-22, 50,  17,-17, 50,  22,-10, 50
data  24,  0, 50,  22, 10, 50,  17, 17, 50,  10, 22, 50
data   0, 24, 50, -10, 22, 50, -17, 17, 50, -22, 10, 50
data -24,  0, 50, -22,-10, 50, -17,-17, 50, -10,-22, 50

data 0,0,80  'nose cone point

'rocket fins
data -44,0,-50, -24,0,-50, -24,0,-30
data  44,0,-50,  24,0,-50,  24,0,-30

'points for relative axis
data 100,0,0, -100,0,0  'x axis
data 0,100,0, 0,-100,0  'y axis
data 0,0,100, 0,0,-100  'z axis

'=============================  LINE DATA ==================================
' 16 lines joining points
data  0,16, 1,17,  2,18,  3,19,  4,20,  5,21,  6,22,  7,23
data  8,24, 9,25, 10,26, 11,27, 12,28, 13,29, 14,30, 15,31

' lines to cone tip
data  32,16, 32,17, 32,18, 32,19, 32,20, 32,21, 32,22, 32,23
data  32,24, 32,25, 32,26, 32,27, 32,28, 32,29, 32,30, 32,31

' lines for fins
data  33,34, 34,35, 35,33
data  36,37, 37,38, 38,36

'lines to draw circumference of top and bottom circle
data 0 , 1,  1, 2,  2, 3,  3, 4,  4, 5,  5, 6,  6, 7,  7, 8,  8, 9,  9,10, 10,11, 11,12, 12,13, 13,14, 14,15, 15, 0
data 16,17, 17,18, 18,19, 19,20, 20,21, 21,22, 22,23, 23,24, 24,25, 25,26, 26,27, 27,28, 28,29, 29,30, 30,31, 31,16

'axis lines
data 39,40, 41,42, 43,44

Compiles and runs in DOSBOX until I press the X or Z keys 2 or 3 times. Then DOSBOX crashes(Segmentation fault (core dumped)).
BasicCoder2
Posts: 3400
Joined: Jan 01, 2009 7:03

Re: 3d without openGL

fatman2021 wrote:Compiles and runs in DOSBOX until I press the X or Z keys 2 or 3 times. Then DOSBOX crashes(Segmentation fault (core dumped)).

Sorry I can't help there as I know nothing about running code in DOSBOX.
BasicCoder2
Posts: 3400
Joined: Jan 01, 2009 7:03

Re: 3d without openGL

Slow progress but have been working my way through some 3D tutorials trying to resolve the 3D rotation about an arbitrary axis coding problem when all I have found is complex math only explanations I can't translate into actual FreeBasic code.

There are some good qbasic 3d tutorials but the code is in very old qbasic code that needs translating to FreeBasic.
http://www.petesqbsite.com/sections/tut ... hics.shtml

Code: Select all

'''Code supplement for the rotation article
'''This is supposed to show you how to use wireframes instead of points
'''The filled cube is far from perfect as it used PAINT so I had to
'''check a lower number for the z component of our projected coord.
'''Pls be sure to check the code out as I will be explaing it in detail
'''in the next issue of article. :*)

'''SetvideoSeg by Plasma357
'''Relsoft 2004
'''Rel.Betterwebber.com

'http://www.petesqbsite.com/sections/tutorials/tuts/relsoft3d/Chapter3/Chapter3.htm
'http://www.petesqbsite.com/sections/tutorials/tuts/relsoft3d/Chapter3/Files/3DWIRE.BAS

'Tweaked to compile with FreeBASIC by BasicCoder2

TYPE Point3d
x       AS SINGLE                   'Normal 3d coords
y       AS SINGLE
z       AS SINGLE
xr      AS SINGLE                   'Rotated  3d coords
yr      AS SINGLE
zr      AS SINGLE
scrx    AS INTEGER                  'Translated and projected
scry    AS INTEGER                  '2d Coords
END TYPE

TYPE PolyType
p1      AS INTEGER                  'vertex 1
p2      AS INTEGER
p3      AS INTEGER
clr     AS INTEGER                  'color
idx     AS INTEGER                  'index for later use
END TYPE

CONST LENS = 256                            'Z
CONST XCENTER = 160                         '??
CONST YCENTER = 100                         '??

CONST PI = 3.14151693

'Polyhedra stuff
REDIM SHARED Model(1) AS Point3d               '3d  Coords
REDIM SHARED CubePoly(1) AS PolyType           '12 faces/Polys
DIM   SHARED as single Thetax, Thetay, Thetaz           'Angle of rotation
DIM   SHARED as single camx, camy, camz              'camera

DECLARE SUB DrawModelHIDEFace (Model() AS ANY, Tri() AS ANY)
DECLARE SUB DrawModelFill (Model() AS ANY, Tri() AS ANY)
DECLARE SUB DrawModel (Model() AS ANY, Tri() AS ANY)
DECLARE SUB RotateAndProject (Model() AS ANY, AngleX as single, AngleY as single, AngleZ as single)
DECLARE SUB LoadCube (Model() AS ANY, Tri() AS ANY, Scale as single)

CLS
SCREEN 0
WIDTH 80

'Initialize 3d model

LoadCube Model(), CubePoly(), 1

dim as integer RendMode = 0

LOCATE 1, 1
PRINT "Choose Render Type:"
PRINT
PRINT "1. WireFrame"
PRINT "2. WireFrame + BackFace Culling"
PRINT "3. Filled"

dim as string K
K = INPUT(1)
SELECT CASE ASC(K)
CASE 49
RendMode = 0        'Wireframe
CASE 50
RendMode = 1        'Back face culled
CASE 51
RendMode = 2        'Filled
CASE ELSE
RendMode = 0
END SELECT

CLS
SCREEN 13
RANDOMIZE TIMER

camx = 0
camy = 0
camz = 0

Thetax = INT(RND * 360)
Thetay = INT(RND * 360)
Thetaz = INT(RND * 360)

dim as integer Finished = 0

DO

SELECT CASE UCASE(K)             'Camera control
CASE "A"
camz = camz + 1
CASE "Z"
camz = camz - 1
CASE "S"
camy = camy + 1
CASE "X"
camy = camy - 1
CASE "D"
camx = camx + 1
CASE "C"
camx = camx - 1
END SELECT

Thetax = (Thetax + 1) MOD 360          'rotate our angles
Thetay = (Thetay + 1) MOD 360
Thetaz = (Thetaz + 1) MOD 360
RotateAndProject Model(), Thetax, Thetay, Thetaz  '12 muls
LINE (0, 0)-(319, 199), 0, BF  'CLS
SELECT CASE RendMode
CASE 0
DrawModel Model(), CubePoly()      'WireFrame
CASE 1
DrawModelHIDEFace Model(), CubePoly()      'Wire with BF culling
CASE 2
DrawModelFill Model(), CubePoly()      'Filled
CASE ELSE
END SELECT
SLEEP

LOOP UNTIL MULTIKEY(&H01)

'vertices of Cube
CUBEDATA:
DATA  50, 50, 50            : 'x,y,z
DATA -50, 50, 50
DATA -50,-50, 50
DATA  50,-50, 50
DATA  50, 50,-50
DATA -50, 50,-50
DATA -50,-50,-50
DATA  50,-50,-50

'Faces/Poly in 4 point form
CUBECONNECT:
DATA 1, 2, 3, 4
DATA 2, 6, 7, 3
DATA 6, 5, 8, 7
DATA 5, 1, 4, 8
DATA 5, 6, 2, 1
DATA 4, 3, 7, 8

SUB DrawModel (Model() AS Point3d, Tri() AS PolyType) STATIC
dim j as integer
dim as single x1,x2,x3,y1,y2,y3
FOR i as integer = 1 TO UBOUND(Tri)
j  = Tri(i).idx
x1 = Model(Tri(j).p1).scrx       'Get triangles from "projected"
x2 = Model(Tri(j).p2).scrx       'X and Y coords since Znormal
X3 = Model(Tri(j).p3).scrx       'Does not require a Z coord
y1 = Model(Tri(j).p1).scry       'V1= Point1 connected to V2 then
y2 = Model(Tri(j).p2).scry       'V2 to V3 and so on...
Y3 = Model(Tri(j).p3).scry

LINE (x1, y1)-(x2, y2), Tri(j).clr
LINE -(X3, Y3), Tri(j).clr
LINE -(x1, y1), Tri(j).clr
NEXT i

END SUB

SUB DrawModelFill (Model() AS Point3d, Tri() AS PolyType) STATIC
dim as integer j
dim as single x1,x2,x3,y1,y2,y3,Znormal,xcen,ycen
''Draws our cube hiding the faces
FOR i as integer = 1 TO UBOUND(Tri)
j = Tri(i).idx
x1 = Model(Tri(j).p1).scrx       'Get triangles from "projected"
x2 = Model(Tri(j).p2).scrx       'X and Y coords since Znormal
X3 = Model(Tri(j).p3).scrx       'Does not require a Z coord
y1 = Model(Tri(j).p1).scry       'V1= Point1 connected to V2 then
y2 = Model(Tri(j).p2).scry       'V2 to V3 and so on...
Y3 = Model(Tri(j).p3).scry
Znormal = (x2 - x1) * (y1 - Y3) - (y2 - y1) * (x1 - X3)
IF (Znormal > 256) THEN              '<-256 so vector facing us
'cuz our z component is not
'normalized and paint would sometimes
'fail ;*)
LINE (x1, y1)-(x2, y2), Tri(j).clr
LINE -(X3, Y3), Tri(j).clr
LINE -(x1, y1), Tri(j).clr
xcen = (x1 + x2 + X3) \ 3
ycen = (y1 + y2 + Y3) \ 3
PAINT (xcen, ycen), Tri(j).clr
END IF
NEXT i

END SUB

SUB DrawModelHIDEFace (Model() AS Point3d, Tri() AS PolyType) STATIC
''Draws our cube hiding the faces
dim as integer j
dim as single x1,x2,x3,y1,y2,y3,Znormal,xcen,ycen
FOR i as integer = 1 TO UBOUND(Tri)
j  = Tri(i).idx
x1 = Model(Tri(j).p1).scrx       'Get triangles from "projected"
x2 = Model(Tri(j).p2).scrx       'X and Y coords since Znormal
X3 = Model(Tri(j).p3).scrx       'Does not require a Z coord
y1 = Model(Tri(j).p1).scry       'V1= Point1 connected to V2 then
y2 = Model(Tri(j).p2).scry       'V2 to V3 and so on...
Y3 = Model(Tri(j).p3).scry
Znormal = (x2 - x1) * (y1 - Y3) - (y2 - y1) * (x1 - X3)
IF (Znormal > 0) THEN               '<0 so vector facing us
LINE (x1, y1)-(x2, y2), Tri(j).clr
LINE -(X3, Y3), Tri(j).clr
LINE -(x1, y1), Tri(j).clr
END IF
NEXT i

END SUB

SUB LoadCube (Model() AS Point3d, Tri() AS PolyType, Scale as single)
REDIM Model(1 TO 8) AS Point3d   '8 vertices
RESTORE CUBEDATA
dim as single x,y,z
FOR i as integer = 1 TO 8
READ x, y, z
Model(i).x = x * Scale     'scale it according to
Model(i).y = y * Scale     'the parameter
Model(i).z = z * Scale
NEXT i

REDIM Tri(1 TO 12) AS PolyType
dim as integer j
dim as single p1,p2,p3,p4
j = 1
FOR i as integer = 1 TO 6
READ p1, p2, p3, p4
Tri(j).p1 = p1
Tri(j).p2 = p2
Tri(j).p3 = p4
Tri(j).idx = j
Tri(j).clr = 20 + INT(RND * 128)
j = j + 1
Tri(j).p1 = p2
Tri(j).p2 = p3
Tri(j).p3 = p4
Tri(j).idx = j
Tri(j).clr = 20 + INT(RND * 128)
j = j + 1
NEXT i

END SUB

SUB RotateAndProject (Model() AS Point3d, AngleX as single, AngleY as single, AngleZ as single) STATIC
''Right handed system
''when camera components increase:
''x=goes left
''y=goes down
''z=goes into the screen

' so when increased:
'x =right
'y=up
'z=into you

'          y
'         |
'         |
'         |
'         0-- -- -- 
'        /        x
'      /
'    /  z
'  \/

'''rotation: counter-clockwise of each axis
''ei.  make yourself perpenicular to the axis
''wave your hand from the center of your body to the left.
''That's how it rotates. ;*)

dim as single ax,ay,az,cx,sx,cy,sy,cz,sz
'convert degrees to radians
ax = AngleX * PI / 180
ay = AngleY * PI / 180
az = AngleZ * PI / 180

'Precalculate the SIN and COS of each angle
cx = COS(ax)
sx = SIN(ax)
cy = COS(ay)
sy = SIN(ay)
cz = COS(az)
sz = SIN(az)

'''After2 hours of work, I was able to weed out the constants from
'''Rotate and project N to reduce my muls to 9 instead of 12. woot
dim as single xx,xy,xz,yx,yy,yz,zx,zy,zz
xx = cy * cz
xy = sx * sy * cz - cx * sz
xz = cx * sy * cz + sx * sz

yx = cy * sz
yy = cx * cz + sx * sy * sz
yz = -sx * cz + cx * sy * sz

zx = -sy
zy = sx * cy
zz = cx * cy

FOR i as integer = LBOUND(Model) TO UBOUND(Model)
dim as single x,y,z,RotX,RotY,RotZ,distance
x = Model(i).x
y = Model(i).y
z = Model(i).z

RotX = (x * xx + y * xy + z * xz) - camx
RotY = (x * yx + y * yy + z * yz) - camy
RotZ = (x * zx + y * zy + z * zz) - camz

Model(i).xr = RotX
Model(i).yr = RotY
Model(i).zr = RotZ

'Project
Distance = (LENS - RotZ)
IF Distance > 0 THEN
Model(i).scrx = XCENTER + (LENS * RotX / Distance)
Model(i).scry = YCENTER - (LENS * RotY / Distance)
ELSE
END IF
NEXT i

END SUB

paul doe
Posts: 922
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: 3d without openGL

Hi, BasicCoder2
Slow progress but have been working my way through some 3D tutorials trying to resolve the 3D rotation about an arbitrary axis coding problem when all I have found is complex math only explanations I can't translate into actual FreeBasic code.

The problem you're having has to do with the way you are representing rotations. You are representing them in Euler angles (aka Yaw, Pitch and Roll), and with Euler you can only rotate about the origin of an absolute coordinate system (as can be seen in you example). For rotating about an arbitrary axis, you need to use vectors and matrices. This resource explains it very nicely:

http://www.codinglabs.net/article_world_view_projection_matrix.aspx

Also, you can check this slideshow, which compares different ways of representing rotations:

https://www.essentialmath.com/GDC2012/GDC2012_JMV_Rotations.pdf

The math can be daunting at first sight, but don't panic. We'll review it, and I'll show you how you can implement it in FreeBasic. I'm almost done. Until then, try to grasp at least those very basic concepts (vectors and matrices) so you'll have a better understanding of the code that I'm about to unleash ;-)

Regards,
Paul
paul doe
Posts: 922
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: 3d without openGL

Say, I think that this little bit of code (in 2D) may help you understand the different coordinate spaces that you need to manage. The demo is a very simple shape, that rotates around an arbitrary point, and shows you how the coordinate spaces relate to each other. As much of the code will be used in the 3D demo anyway, here it is. Read the comments on the code and compare the matrices and transformations with the resources that I gave you. Perhaps it will make everything more clear.

platform.bi

Code: Select all

#ifndef __platform__
#define __platform__

/'
platform specific definitions
'/
#ifdef __fb_64bit__
/'
64-bit definitions
'/
type int32 as long
type int64 as integer
type uint32 as ulong
type uint64 as integer
const as string fbVersion = "FreeBasic 64-bit"
#else
/'
32-bit definitions
'/
type int32 as integer
type int64 as longint
type uint32 as uinteger
type uint64 as ulongint
const as string fbVersion = "FreeBasic 32-bit"
#endif
#endif

core.bi

Code: Select all

#ifndef __core__
#define __core__

#include once "platform.bi"

/'
as FB doesn't implements the class keyword, this is used to distinguish
between a class (object) and a type (data)
'/
#ifdef class
#undef class
#define class type
#endif

'' null for objects
#ifdef nothing
#undef nothing
#define nothing 0
#else
#define nothing 0
#endif

/'
again, FB doesn't have the 'inherits' keyword to differentiate interface
inheritance and implementation inheritance. This define is useful to remember
that

syntactically, they are the same. Conceptually, they are very different
'/
#ifdef inherits
#undef inherits
#define inherits extends
#else
#define inherits extends
#endif

/'
FB also don't distinguish between an interface class and a class implementation
this keyword is used for that sole purpose
'/
#ifdef interface
#undef interface
#define interface type
#else
#define interface type
#endif
#endif

math.bi

Code: Select all

#ifndef __math__
#define __math__

/'
math, geometry and other useful numerical stuff

10/7/2017: removed all OpenGL-related stuff and the x86 assembler implementations of
floating-point modulus operator, and moved them to their own files (again).
'/

'' some convenience macros
#define max( a, b )                  iif( a > b, a, b )
#define min( a, b )                  iif( a < b, a, b )
#define clamp( mn, mx, v )      iif( v < mn, mn, iif( v > mx, mx, v ) )
#define wrap( wrapValue, v )   ( ( v ) + wrapValue ) mod wrapValue

'' useful constants
const as double pi = 4 * atn( 1 )
const as double twoPi = 2 * pi
const as double halfPi = pi / 2

const as double degToRad = pi / 180
const as double radToDeg = 180 / pi
const as double epsilon = 0.00000001

'' used to express angles in another unit
#define radians( ang )   ang * degToRad
#define degrees( ang )   ang * radToDeg

'' functions to return a delimited random value (uses FB implementation which
'' is a Mersenne Twister, can't remember the classification
declare function rndRange overload( byval as integer, byval as integer ) as integer
declare function rndRange( byval as single, byval as single ) as single
declare function rndRange( byval as double, byval as double ) as double

public function rndRange( byval mn as integer, byval mx as integer ) as integer
return int( rnd() * ( mx + 1 - mn ) + mn )
end function

public function rndRange( byval mn as single, byval mx as single ) as single
return rnd() * ( mx - mn ) + mn
end function

public function rndRange( byval mn as double, byval mx as double ) as double
return rnd() * ( mx - mn ) + mn
end function
#endif

shape.bi

Code: Select all

#ifndef __shape__
#define __shape__
/'
shapeEdge type definition

this type simply holds two vertices that define an edge of the shape
fairly straightforward
'/
#include once "core.bi"
#include once "vec2h.bi"
#include once "mat3.bi"
#include once "arrayList.bi"

type shapeEdge
public:
as vec2h ptr v1 = any
as vec2h ptr v2 = any

declare constructor()
declare constructor( byval p1 as vec2h ptr, byval p2 as vec2h ptr )
declare constructor( byref rhs as shapeEdge )

declare operator let( byref rhs as shapeEdge )
end type

constructor shapeEdge()
v1 = 0
v2 = 0
end constructor

constructor shapeEdge( byval p1 as vec2h ptr, byval p2 as vec2h ptr )
v1 = p1
v2 = p2
end constructor

constructor shapeEdge( byref e as shapeEdge )
v1 = e.v1
v2 = e.v2
end constructor

operator shapeEdge.let( byref rhs as shapeEdge )
v1 = rhs.v1
v2 = rhs.v2
end operator

/'
shape class

responsibility: this class represents a shape, defined by a collection of vertices and
the edges that connect them
mutability: mutable
'/
class shape
public:
declare constructor()
declare destructor()

declare property vertexCount() as integer
declare property edgeCount() as integer

declare function vertex( byval index as integer ) as vec2h ptr
declare function edge( byval index as integer ) as shapeEdge ptr

declare sub addVertex( byval v as vec2h ptr )
declare sub addEdge( byval e as shapeEdge ptr )

declare function transform() as mat3
declare sub transform( byval M as mat3 )

declare sub render( byval c as uint32 = rgba( 255, 255, 255, 255 ) )

declare sub computeBoundingBox()

private:
as arrayList   m_vertices '' list of vertices
as arraylist   m_edges '' list of edges
as mat3            m_transform = any '' the transformation matrix
as arrayList   m_boundingBox '' the list of vertices for the bounding box
as arrayList   m_boundingBoxEdges '' the list of edges for the bounding box
end class

constructor shape()
'' load an identity matrix in the transformation matrix when constructed
m_transform = matrices.identity2h()
end constructor

destructor shape()
/'
this step is necessary because my data structures (like the list)
don't do garbage collection
'/
'' delete vertices
for i as integer = 0 to m_vertices.count - 1
dim as vec2h ptr v = m_vertices.get( i )

delete( v )
next

'' delete edges
for i as integer = 0 to m_edges.count - 1
dim as shapeEdge ptr e = m_edges.get( i )

delete( e )
next i

'' delete bounding box
for i as integer = 0 to m_boundingBox.count - 1
dim as vec2h ptr e = m_boundingBox.get( i )

delete( e )
next i
end destructor

property shape.vertexCount() as integer
return( m_vertices.count )
end property

property shape.edgeCount() as integer
return( m_edges.count )
end property

function shape.vertex( byval index as integer ) as vec2h ptr
return( m_vertices.get( index ) )
end function

function shape.edge( byval index as integer ) as shapeEdge ptr
return( m_edges.get( index ) )
end function

sub shape.addVertex( byval v as vec2h ptr )
end sub

sub shape.addEdge( byval e as shapeEdge ptr )
end sub

function shape.transform() as mat3
return( m_transform )
end function

sub shape.transform( byval M as mat3 )
m_transform = M
end sub

sub shape.computeBoundingBox()
'' minimum and maximum positions of the bounding box
dim as vec2h tl = vec2h( -100000.0, -100000.0 )
dim as vec2h rb = vec2h( 100000.0, 100000.0 )

for i as integer = 0 to m_edges.count - 1
'' iterate through every edge
dim as shapeEdge ptr e = m_edges.get( i )

dim as vec2h v1 = *( e->v1 )
dim as vec2h v2 = *( e->v2 )

if( v1.x > tl.x ) then tl.x = v1.x
if( v1.y > tl.y ) then tl.y = v1.y

if( v2.x < rb.x ) then rb.x = v2.x
if( v2.y < rb.y ) then rb.y = v2.y
next

'' create the 4 corners of the bounding box...
m_boundingBox.add( new vec2h( tl.x, tl.y ) )
m_boundingBox.add( new vec2h( tl.x, rb.y ) )
m_boundingBox.add( new vec2h( rb.x, rb.y ) )
m_boundingBox.add( new vec2h( rb.x, tl.y ) )

'' ...and connect them with edges
m_boundingBoxEdges.add( new shapeEdge( m_boundingBox.get( 0 ), m_boundingBox.get( 1 ) ) )
m_boundingBoxEdges.add( new shapeEdge( m_boundingBox.get( 1 ), m_boundingBox.get( 2 ) ) )
m_boundingBoxEdges.add( new shapeEdge( m_boundingBox.get( 2 ), m_boundingBox.get( 3 ) ) )
m_boundingBoxEdges.add( new shapeEdge( m_boundingBox.get( 3 ), m_boundingBox.get( 0 ) ) )
end sub

sub shape.render( byval c as uint32 = rgba( 255, 255, 255, 255 ) )
/'
render the shape

the process is simple: transform each vertex with the provided
matrix (via multiplying), and use that transformed vector to
draw the shape
'/
for i as integer = 0 to m_edges.count - 1
'' transform both edge vectors first
dim as shapeEdge ptr e = m_edges.get( i )

dim as vec2h v1 = vec2h( m_transform * ( *e->v1 ) )
dim as vec2h v2 = vec2h( m_transform * ( *e->v2 ) )

'' and then draw the edge
line( v1.x, v1.y ) - ( v2.x, v2.y ), c
next

'' if the bounding box has been computed, draw it
if( m_boundingBox.count ) > 0 then
for i as integer = 0 to m_boundingBoxEdges.count - 1
'' transform both edge vertices first
dim as shapeEdge ptr e = m_boundingBoxEdges.get( i )

dim as vec2h v1 = vec2h( m_transform * ( *e->v1 ) )
dim as vec2h v2 = vec2h( m_transform * ( *e->v2 ) )

'' and then draw the edge
line( v1.x, v1.y ) - ( v2.x, v2.y ), rgba( 128, 128, 128, 255 )
next
end if
end sub
#endif

arrayList.bi

Code: Select all

#ifndef __arrayList__
#define __arrayList__

#include once "platform.bi"
#include once "core.bi"
#include once "math.bi"
#include once "crt.bi" '' needed for memcpy

/'
simple java-like ArrayList class

responsibility: represents a collection of objects, accessed and indexed
as an array
mutability: immutable

note that these classes aren't meant to do garbage collection,
merely to conveniently index and traverse a collection of objects
also the classes aren't optimized, as they are meant to be
used as a reference implementation (there's almost no error handling)
'/
class ArrayList extends object 'inherits ICollection
'' public interface
public:
declare constructor()

declare function get( byval index as uint64 ) as any ptr
declare function count() as uint64
declare sub add( byval e as any ptr )
declare sub insert( byval e as any ptr, byval before as uint64 )
declare function remove( byval index as uint64 ) as any ptr
declare function remove( byval item as any ptr ) as any ptr
declare function removeLast() as any ptr
declare function removeFirst() as any ptr

'' state members
protected:
as any ptr   m_element( any )
as uint64      m_count = any

/'
convenience macro for brevity
this macro resizes the m_element array by v elements, either positive or negative
if v is positive, the array expands
if v is negative, the array shrinks

the code uses +1 and -1 for further clarify the meaning of the expression
'/
#macro resizeElements( v )
m_count += v :
redim preserve m_element( 0 to m_count - 1 )
#endmacro
end class

constructor ArrayList()
m_count = 0
end constructor

function ArrayList.count() as uint64
return( m_count )
end function

function ArrayList.get( byval index as uint64 ) as any ptr
/'
gets the element of the list indicated by index
note that there is no error checking on this one, for this can
mask out of bound errors
if the list tries to access an item out of bounds, the application deserves to
fail miserably
'/
return( m_element( index ) )
end function

sub ArrayList.add( byval e as any ptr )
resizeElements( +1 )

m_element( m_count - 1 ) = e
end sub

sub ArrayList.insert( byval e as any ptr, byval before as uint64 )
'' don't allow insertion out of bounds
before = min( m_count, max( 0, before ) )

'' trivial case, inserting at the end of the list
if( before = m_count - 1 ) then
resizeElements( +1 )

m_element( m_count - 1 ) = m_element( m_count - 2 )
m_element( m_count - 2 ) = e
else
resizeElements( +1 )

'' calculate number of elements to move
dim as uint64 elem = m_count - 1 - before
'' and move them to make space for the inserted item
memcpy( @m_element( before + 1 ), @m_element( before ), elem * sizeOf( any ptr ) )

m_element( before ) = e
end if
end sub

function ArrayList.remove( byval item as any ptr ) as any ptr
'' removes an item from the list by pointer
dim as any ptr ret = 0
dim as int64 elem = -1 '' assume not found

'' search it (inefficiently)
for i as uint64 = 0 to m_count - 1
if( item = m_element( i ) ) then
'' found it
elem = i
exit for
end if
next

'' if found, return it, else return nothing
if( elem <> -1 ) then
return( remove( elem ) )
else
return( nothing )
end if
end function

function ArrayList.remove( byval index as uint64 ) as any ptr
'' removes an item from the list by index
'' don't allow removal out of bounds
index = min( m_count - 1, max( 0, index ) )

dim as any ptr ret = m_element( index )

if( index = m_count - 1 ) then
'' trivial removal, the last item
resizeElements( -1 )

return( ret )
end if

'' only 2 elements to remove remaining
if( index = 0 and m_count < 3 ) then
m_element( 0 ) = m_element( 1 )

resizeElements( -1 )

return( ret )
end if

'' general case (elements > 2)
'' number of elements to move
dim as uint64 elem = m_count - 1 - index
'' move the rest of the elements
memcpy( @m_element( index ), @m_element( index + 1 ), elem * sizeOf( any ptr ) )

resizeElements( -1 )

return( ret )
end function

function ArrayList.removeLast() as any ptr
'' convenience function for removing the last element of the list
dim as any ptr ret = m_element( m_count - 1 )

resizeElements( -1 )

return( ret )
end function

function ArrayList.removeFirst() as any ptr
'' convenience function for removing the first element of the list
dim as any ptr ret = m_element( 0 )

'' there's only one element remaining
if( m_count = 1 ) then
m_count -= 1
redim m_element( 0 )

return( ret )
end if

'' there's 2 elements remaining
if( m_count = 2 ) then
m_element( 0 ) = m_element( 1 )

resizeElements( -1 )

return( ret )
end if

'' general case
'' calculate number of elements to move
dim as uint64 elem = m_count - 1

'' move the remaining elements to their new position
memcpy( @m_element( 0 ), @m_element( 1 ), elem * sizeOf( any ptr ) )

'' and resize the member array
resizeElements( -1 )

return( ret )
end function
#endif

/'
'' some code for unit testing

class test extends object
public:
as integer value = any

declare constructor()
declare constructor( byval v as integer )
declare destructor()
end class

constructor test()
value = 0
end constructor

constructor test( byval v as integer )
value = v
end constructor

destructor test()
print "Destructor "; value; " called."
end destructor

dim as ArrayList al
dim as test ptr t
dim as integer numElements = 10
dim as any ptr what

for i as integer = 1 to numElements
dim as test ptr t = new test( i )

if i = 5 then
what = t
end if
next

al.insert( new test( 343 ), al.count - 2 )

t = al.remove( 8798 )
delete t

t = al.removeLast()
delete t

t = al.remove( what )
delete t

'' show the elements
for i as integer = 0 to al.count - 1
t = al.get( i )

print t->value
next

'' delete all elements
do while al.count > 0
t = al.removeFirst

delete( t )
t = 0
loop

'' this code should never be executed if everything went ok
for i as integer = 0 to al.count - 1
t = al.get( i )

print t->value
next

sleep()
'/

mat3.bi

Code: Select all

#ifndef __mat3__
#define __mat3__

#include once "vec2h.bi"
#include once "math.bi"
/'
| a b c |
3x3 Matrix type      | d e f |
| g h i |

note that this type also define operators to work with
2D homogeneous vectors

08/28/2017: fixed typo in multiplication of two matrices
'/
type mat3
public:
as double a, b, c
as double d, e, f
as double g, h, i

declare constructor()
declare constructor(_
byval na as double, byval nb as double, byval nc as double, _
byval nd as double, byval ne as double, byval nf as double, _
byval ng as double, byval nh as double, byval ni as double )

declare constructor( byref rhs as mat3 )
declare operator let( byref rhs as mat3 )

declare sub transpose()
declare function determinant() as double
declare sub inverse()
declare sub identity()

declare operator cast() as string
end type

constructor mat3() export
'' The default constructor creates an identity matrix, instead of a matrix of zeros
a = 1.0: b = 0.0: c = 0.0
d = 0.0: e = 1.0: f = 0.0
g = 0.0: h = 0.0: i = 1.0
end constructor

constructor mat3(   byval na as double, byval nb as double, byval nc as double, _
byval nd as double, byval ne as double, byval nf as double, _
byval ng as double, byval nh as double, byval ni as double ) export

a = na: b = nb: c = nc
d = nd: e = ne: f = nf
g = ng: h = nh: i = ni
end constructor

constructor mat3( byref rhs as mat3 ) export
'' copy constructor
a = rhs.a: b = rhs.b: c = rhs.c
d = rhs.d: e = rhs.e: f = rhs.f
g = rhs.g: h = rhs.h: i = rhs.i
end constructor

operator mat3.let( byref rhs as mat3 ) export
'' assignment constructor
a = rhs.a: b = rhs.b: c = rhs.c
d = rhs.d: e = rhs.e: f = rhs.f
g = rhs.g: h = rhs.h: i = rhs.i
end operator

sub mat3.transpose() export
/'
transpose:

| a b c |T    | a d g |
| d e f |  =  | b e h |
| g h i |     | c f i |
'/
b += d: d = b - d: b -= d '' swap b and d
c += g: g = c - g: c -= g '' swap c and g
f += h: h = f - h: f -= h '' swap f and h
end sub

function transpose( byval A as mat3 ) as mat3 export
A.transpose()

return( A )
end function

function mat3.determinant() as double export
'' computes the determinant of the matrix
dim as double det = _
( a * e * i _
+ b * f * g _
+ d * h * c _
- g * e * c _
- d * b * i _
- h * f * a )

'' this is, of course, not matematically correct but it saves
'' some comprobations when you're calculating the inverse of
'' the matrix (and to avoid dividing by zero)
if det = 0 then det = 1

return( det )
end function

function determinant( byval A as mat3 ) as double export
return( A.determinant() )
end function

sub mat3.inverse() export
'' inverse of a 3x3 matrix
'' the inverse is the adjoint divided through the determinant
dim as double det = this.determinant()

'' if the determinant is 0, the matrix has no inverse
if det > 0 then
dim as double rDet = 1 / det '' The reciprocal of the determinant

'' included in these calculations: minor, cofactor (changed signs), transpose
'' and division through determinant (expressed here by multiplying by the reciprocal)
dim as mat3 INV = mat3( _
(  e * i - h * f ) * rDet, ( -b * i + h * c ) * rDet, (  b * f - e * c ) * rDet, _
( -d * i + g * f ) * rDet, (  a * i - g * c ) * rDet, ( -a * f + d * c ) * rDet, _
(  d * h - g * e ) * rDet, ( -a * h + g * b ) * rDet, (  a * e - d * b ) * rDet )

'' set this matrix to the computed inverse
this = INV
end if
end sub

function inverse( byval A as mat3 ) as mat3 export
A.inverse()

return( A )
end function

sub mat3.identity() export
'' make the matrix an identity matrix
a = 1.0: b = 0.0: c = 0.0
d = 0.0: e = 1.0: f = 0.0
g = 0.0: h = 0.0: i = 1.0
end sub

operator mat3.cast() as string export
'' this is only to obtain a human readable representation of the matrix
return( _
"[" & trim( str( a ) ) & "][" & trim( str( b ) ) & "][" & trim( str( c ) ) & "]" & chr( 10 ) & chr( 13 ) & _
"[" & trim( str( d ) ) & "][" & trim( str( e ) ) & "][" & trim( str( f ) ) & "]" & chr( 10 ) & chr( 13 ) & _
"[" & trim( str( g ) ) & "][" & trim( str( h ) ) & "][" & trim( str( i ) ) & "]" )
end operator

operator + ( byref A as mat3, byref B as mat3 ) as mat3 export
'' adds two matrices
return( mat3( _
A.a + B.a, A.b + B.b, A.c + B.c, _
A.d + B.d, A.e + B.e, A.f + B.f, _
A.g + B.g, A.h + B.h, A.i + B.i ) )
end operator

operator - ( byref A as mat3, byref B as mat3 ) as mat3 export
'' substracts two matrices
return( mat3( _
A.a - B.a, A.b - B.b, A.c - B.c, _
A.d - B.d, A.e - B.e, A.f - B.f, _
A.g - B.g, A.h - B.h, A.i - B.i ) )
end operator

operator * ( byref A as mat3, byref s as double ) as mat3 export
'' multiply a matrix with a scalar
return( mat3( _
A.a * s, A.b * s, A.c * s, _
A.d * s, A.e * s, A.f * s, _
A.g * s, A.h * s, A.i * s ) )
end operator

operator * ( byref s as double, byref A as mat3 ) as mat3 export
'' multiply a matrix with a scalar
return( mat3( _
A.a * s, A.b * s, A.c * s, _
A.d * s, A.e * s, A.f * s, _
A.g * s, A.h * s, A.i * s ) )
end operator

operator * ( byref A as mat3, byref B as mat3 ) as mat3 export
'' multiply two matrices
return( mat3( _
A.a * B.a + A.b * B.d + A.c * B.g, A.a * B.b + A.b * B.e + A.c * B.h, A.a * B.c + A.b * B.f + A.c * B.i, _
A.d * B.a + A.e * B.d + A.f * B.g, A.d * B.b + A.e * B.e + A.f * B.h, A.d * B.c + A.e * B.f + A.f * B.i, _
A.g * B.a + A.h * B.d + A.i * B.g, A.g * B.b + A.h * B.e + A.i * B.h, A.g * B.c + A.h * B.f + A.i * B.i ) )
end operator

operator * ( byref A as mat3, byref v as vec2h ) as vec2h export
'' multiply a matrix with a column vector, resulting in a column vector
return( vec2h( A.a * v.x + A.b * v.y + A.c * v.w, A.d * v.x + A.e * v.y + A.f * v.w, A.g * v.x + A.h * v.y + A.i * v.w ) )
end operator

operator * ( byref v as vec2h, byref A as mat3 ) as vec2h export
'' multiply a vector with a row matrix, resulting in a row vector
return( vec2h( A.a * v.x + A.d * v.y + A.g * v.w, A.b * v.x + A.e * v.y + A.h * v.w, A.c * v.x + A.f * v.y + A.i * v.w ) )
end operator

operator / ( byref A as mat3, byref s as double ) as mat3 export
'' divide a matrix trough a scalar
return( mat3( _
A.a / s, A.b / s, A.c / s, _
A.d / s, A.e / s, A.f / s, _
A.g / s, A.h / s, A.i / s ) )
end operator

namespace matrices
function identity2h() as mat3
'' returns a 2D homogeneous identity matrix
return( mat3( _
1.0, 0.0, 0.0, _
0.0, 1.0, 0.0, _
0.0, 0.0, 1.0 ) )
end function

function translation2h( byval tx as double, byval ty as double ) as mat3
'' returns a 2D homogeneous translation matrix
return( mat3( _
1.0, 0.0, tx, _
0.0, 1.0, ty, _
0.0, 0.0, 1.0 ) )
end function

function scaling2h( byval sx as double, byval sy as double ) as mat3
'' returns a 2D scaling matrix
return( mat3( _
sx,      0.0,   0.0, _
0.0,   sy,      0.0, _
0.0,   0.0,   1.0 ) )
end function

function shear2h( byval sx as double, byval sy as double ) as mat3
'' returns a shearig matrix by sx and sy angles in degrees
return( mat3( _
1.0,                            tan( radians( sx ) ), 0.0, _
tan( radians( sy ) ), 1.0,                            0.0, _
0.0,                            0.0,                            1.0 ) )
end function

function rotation2h( byval ang as double ) as mat3
'' returns a 2D rotation matrix around by ang degrees about the origin
dim as double co = cos( radians( ang ) )
dim as double si = sin( radians( ang ) )

return( mat3( _
co   , -si   ,   0.0, _
si   , co   ,   0.0, _
0.0   ,   0.0   ,   1.0 ) )
end function

function rotationAxis2h( byval axis as vec2h, byval ang as double ) as mat3
'' returns a 2D rotation matrix around an arbitrary axis, by ang degrees
dim as double co = cos( radians( ang ) )
dim as double si = sin( radians( ang ) )

return( mat3( _
co, -si, -axis.x * co + axis.y * si + axis.x, _
si,  co, -axis.x * si - axis.y * co + axis.y, _
0.0, 0.0, 1.0 ) )
end function
end namespace
#endif

vec2h.bi

Code: Select all

#ifndef __vec2h__
#define __vec2h__
/'
homogeneous 2D vector type

| x |
| y |
| 1 |

'/
#include once "core.bi"
#include once "math.bi"

type vec2h
public:
as double x
as double y
as double w

declare constructor()
declare constructor( byval nx as double, byval ny as double, byval nw as double = 1.0 )
declare constructor( byref nv as vec2h )
declare operator let( byref rhs as vec2h )

declare function dot( byref b as vec2h ) as double
declare function cross( byref b as vec2h ) as double
declare function length() as double
declare function squaredLength() as double
declare function unit() as vec2h
declare sub makeHomogeneous()
declare sub normalize()
declare sub turnLeft()
declare sub turnRight()
declare sub rotate( byval rAngle as double )
declare function angle() as double
declare sub setLength( byval l as double )

declare operator cast() as string
end type

constructor vec2h() export
x = 0.0
y = 0.0
w = 1.0
end constructor

constructor vec2h( byval nx as double, byval ny as double, byval nw as double = 1.0 ) export
x = nx
y = ny
w = nw
end constructor

constructor vec2h( byref nv as vec2h ) export
x = nv.x
y = nv.y
w = nv.w
end constructor

operator vec2h.let( byref rhs as vec2h ) export
x = rhs.x
y = rhs.y
w = rhs.w
end operator

'' basic arithmetic operators
operator + ( byref lhs as vec2h, byref rhs as vec2h ) as vec2h export
return( vec2h( lhs.x + rhs.x, lhs.y + rhs.y ) )
end operator

operator - ( byref lhs as vec2h, byref rhs as vec2h ) as vec2h export
return( vec2h( lhs.x - rhs.x, lhs.y - rhs.y ) )
end operator

operator - ( byref lhs as vec2h ) as vec2h export
return( vec2h( -lhs.x, -lhs.y ) )
end operator

operator * ( byref lhs as vec2h, byref rhs as vec2h ) as vec2h export
return( vec2h( lhs.x * rhs.x, lhs.y * rhs.y ) )
end operator

operator * ( byref lhs as vec2h, byref rhs as double ) as vec2h export
return( vec2h( lhs.x * rhs, lhs.y * rhs ) )
end operator

operator * ( byref lhs as double, byref rhs as vec2h ) as vec2h export
return( vec2h( lhs * rhs.x, lhs * rhs.y ) )
end operator

operator * ( byref lhs as vec2h, byref rhs as integer ) as vec2h export
return( vec2h( lhs.x * rhs, lhs.y * rhs ) )
end operator

operator * ( byref lhs as integer, byref rhs as vec2h ) as vec2h export
return( vec2h( lhs * rhs.x, lhs * rhs.y ) )
end operator

operator / ( byref lhs as vec2h, byref rhs as vec2h ) as vec2h export
return( vec2h( lhs.x / rhs.x, lhs.y / rhs.y ) )
end operator

operator / ( byref lhs as vec2h, byref rhs as double ) as vec2h export
return( vec2h( lhs.x / rhs, lhs.y / rhs ) )
end operator

operator / ( byref lhs as double, byref rhs as vec2h ) as vec2h export
return( vec2h( lhs / rhs.x, lhs / rhs.y ) )
end operator

operator vec2h.cast() as string export
return(   "[" & str( this.x ) & "]" & chr( 10 ) & chr( 13 ) & _
"[" & str( this.y ) & "]" & chr( 10 ) & chr( 13 ) & _
"[" & str( w ) & "]" )
end operator

function vec2h.dot( byref b as vec2h ) as double export
'' returns the dot product of this vector with another vector b
return( x * b.x + y * b.y )
end function

function dot( byref v as vec2h, byref w as vec2h ) as double export
return( v.x * w.x + v.y * w.y )
end function

function vec2h.cross( byref b as vec2h ) as double export
/'
the cross product is not defined in 2d, so this function returns the z component
of the cross product of this vector with vector b, augmented to 3d
'/
return( x * b.y - y * b.x )
end function

function vec2h.length() as double export
'' returns the length of this vector
return( sqr( x * x + y * y ) )
end function

function vec2h.squaredLength() as double export
/'
returns the squared length of this vector
useful when one just want to compare two vectors to see which is longest,
as this avoids computing the square root
'/
return( x * x + y * y )
end function

function vec2h.unit() as vec2h export
'' return a normalized copy of this vector, without normalizing the vector itself
return vec2h( x, y ) / sqr( x * x + y * y )
end function

sub vec2h.normalize() export
'' normalizes this vector
dim as double l = sqr( x * x + y * y )

x /= l
y /= l
end sub

function normalize( byval v as vec2h ) as vec2h export
'' for compatibility
v.normalize()

return( v )
end function

/'
same with these two functions
turnLeft and turnRight rotates this vector 90 degrees to the left and right, while the
functions left and right return this vector rotated 90 degrees without actually rotating it
'/
sub vec2h.turnLeft() export
this = vec2h( y, -x )
end sub

sub vec2h.turnRight() export
this = vec2h( -y, x )
end sub

function turnLeft( byref v as vec2h ) as vec2h export
return( vec2h( v.y, -v.x ) )
end function

function turnRight( byref v as vec2h ) as vec2h export
return( vec2h( -v.y, v.x ) )
end function

sub vec2h.rotate( byval rAngle as double ) export
/'
rotates this vector by rAngle radians
'/

dim as double si = sin( rAngle )
dim as double co = cos( rAngle )

this = vec2h( x * co - y * si, x * si + y * co  )
end sub

function rotate( byref v as vec2h, byval rAngle as double ) as vec2h export
/'
returns the vector v, rotated by rAngle radians
'/

dim as double si = sin( rAngle )
dim as double co = cos( rAngle )

return( vec2h( v.x * co - v.y * si, v.x * si + v.y * co ) )
end function

function vec2h.angle() as double export
'' returns the angle that this vector points to, in radians

return( atan2( y, x ) )
end function

function vectorAngle overload( byval v as vec2h ) as double export
'' returns the angle that v vector points to, in radians

return atan2( v.y, v.x )
end function

function vectorAngle( byval v as vec2h, byval w as vec2h ) as double export
'' returns the angle between two vectors v and w

dim as double cosineOfAngle = dot( normalize( v ), normalize( w ) )

return( -acos( clamp( -1.0, 1.0, cosineOfAngle ) ) )
end function

sub vec2h.makeHomogeneous() export
'' homogeneize this vector (make the w component 1.0)

x /= w
y /= w
w /= w
end sub

sub vec2h.setLength( byval l as double )
'' sets the length of this vector
dim va as double = atan2( y, x )

x = l * cos( va )
y = l * sin( va )
end sub

function distance( byval a as vec2h, byval b as vec2h ) as double
'' returns the distance between two vectors
return( ( a - b ).length )
end function

function alignment( byval position as vec2h, byval size as vec2h, byval align as vec2h ) as vec2h
'' returns a vec2h aligned between position and position + size
'' align should be a normalized vector
return( position + size * align )
end function

function interpolate( byval p0 as vec2h, byval p1 as vec2h, byval t as double ) as vec2h
'' returns the interpolation point t (0..1) between p0 and p1
return( ( 1 - t ) * p0 + t * p1 )
end function
#endif

main.bas

Code: Select all

#include once "platform.bi"
#include once "core.bi"
#include once "mat3.bi"
#include once "vec2h.bi"
#include once "shape.bi"
#include once "fbgfx.bi" '' needed for the constants used by multiKey()

/'
testing the shape and 2D vector and matrix classes

conventions used:
RIGHT HANDED coordinate system
positive angle increment is COUNTER-CLOCKWISE
vertices are specified COUNTER-CLOCKWISE
angles are specified in DEGREES
'/

dim as integer scrW = 800, scrH = 600
screenRes( scrW, scrH, 32 )
windowTitle( fbVersion & " - 2D vector and matrix example" )

/'
this code defines a very simple 2D shape
note that all points are specified relative to the
( 0.0, 0.0 ) coordinate.

this is otherwise known as OBJECT SPACE
'/
dim as shape sh

with sh
.addVertex( new vec2h( -20, -10 ) )
.addVertex( new vec2h(  20, -10 ) )
.addVertex( new vec2h(   0,  30 ) )

.addEdge( new shapeEdge( sh.vertex( 0 ), sh.vertex( 1 ) ) )
.addEdge( new shapeEdge( sh.vertex( 1 ), sh.vertex( 2 ) ) )
.addEdge( new shapeEdge( sh.vertex( 2 ), sh.vertex( 0 ) ) )

'' when the object is finished, compute its bounding box
.computeBoundingBox()
end with

/'
define the main projection matrix (the PROJECTION SPACE coordinate system)
note that we map the Y axis to increase upwards, and the center of the
coordinate system to the center of the screen
'/
dim as mat3 P = mat3( _
1.0,  0.0, scrW / 2, _
0.0, -1.0, scrH / 2, _
0.0,  0.0, 1.0 )

'' angle of rotation of the shape, in degrees
dim as single ang = 0.0

'' axis of rotation in object coordinates
dim as vec2h axis = vec2h( 0.0, 100.0 )

'' position of the object in world coordinates
dim as vec2h position = vec2h( 50.0, 50.0 )

'' begin main loop
do
if multiKey( fb.sc_up ) then
'' up
position.y += 1
end if

if multiKey( fb.sc_down ) then
'' down
position.y -= 1
end if

if multiKey( fb.sc_left ) then
'' left
position.x -= 1
end if

if multiKey( fb.sc_right ) then
'' right
position.x += 1
end if

/'
lets compose some transformations

as a general rule, each transformation matrix must be recomputed every
time it changes
here, the translation and rotations can change depending on which
keys do you press, so we recompose them
'/

'' translation
dim as mat3 T = matrices.translation2h( position.x, position.y )

'' rotation about an arbitrary axis
dim as mat3 RA = matrices.rotationAxis2h( axis, ang )

/'
if you use column vectors and matrices (as I do), you have to specify
the last transformation FIRST, like this

P = projection matrix
T = translation matrix
RA = rotation about an arbitrary axis

and compose them by multiplying. Matrix multiplication is NOT
commutative, so the order IS important!!
'/

'' compose the transformation (first rotate, then translate, then project)
dim as mat3 transformation = P * T * RA

'' and then pass it to the shape
sh.transform( transformation )

'' render the screen
screenLock()
cls()

'' show the center of screen (the center of the coordinate system)
line( scrW / 2, 0 ) - ( scrW / 2, scrH - 1 ), rgba( 64, 64, 64, 255 )
line( 0, scrH / 2 ) - ( scrW - 1, scrH / 2 ), rgba( 64, 64, 64, 255 )

'' render the shape (yellow)
sh.render( rgba( 255, 255, 0, 255 ) )

/'
this bit of code shows you the axis used to rotate the shape,
in OBJECT space
'/
dim as vec2h tAxis = transformation * axis
circle( tAxis.x, tAxis.y ), 3, rgba( 0, 255, 0, 255 ), , , , f

/'
this bit shows you the origin of the shape, again, in OBJECT
space
'/
dim as vec2h tOrigin = transformation * vec2h( 0.0, 0.0 )
circle( tOrigin.x, tOrigin.y ), 3, rgba( 0, 0, 255, 255 ), , , , f

/'
and this bit shows you the position of the shape in WORLD
coordinates. Note that you don't have to transform, only
project them
'/
dim as vec2h tPos = P * position
circle( tPos.x, tPos.y ), 3, rgba( 255, 0, 0, 255 ), , , , f
screenUnlock()

'' increment the angle the object rotates
ang += 0.1
if ang > 360.0 then ang -= 360.0

'' spare some time, always
sleep( 1 )
loop until multiKey( fb.sc_escape )

Simply save each file with it's corresponding name in a folder of your choice and run 'main.bas'. Use the arrow keys to move the shape. Meanwhile, the shape will be rotating around an axis that you can change (see the code). Experiment a little and see how it's done in 2D, before we jump to 3D. It's a little easier to grasp ;-)

See ya
Paul
owen
Posts: 552
Joined: Apr 19, 2006 10:55
Location: Kissimmee, FL
Contact:

Re: 3d without openGL

here's what i was working on back in 2013

Code: Select all

'note: my arrays usually do not use sub script zero
'when i dim a(4) i use a(1),a(2),a(3),a(4)... a(0) is not used
'good example is a rectangel has for points, point 1,2,3,4 and
'i usually refer to these points in my arrays as a(1-4) not a(0-3)
'secondly, even though i dim a(4), knowing a(0-5) are allocated in memory
'i find it convenient to use a(0) to temporarily store the value of (1-4)
'starting in the top left corner cuz we read (left to right)
'but in a counter clockwise manner cuz my code figures angles increase this way
'a(1) is top left corner
'a(2) is bottom left corner
'a(3) is bottom right corner
'a(4) is top right corner
'note:
'a(5) would be center of rectangle
'a(6) would be width of rectangle
'a(7) would be height of rectangle
'for a cube
'a(1-4) are the front face corners
'a(5-8) are the back face corners
'a(6) would be center of the cube
'a(7-9) would be the width,height,depth
'rotation is counter clockwise 0 degrees is at 3 O'clock - 90 is @ 12 - 180 is @ 9 - 270 is @ 6
'axis xy from front view
'axis xz from bottom view
'axis yz from right view
Type point2d
x As Double
y As Double
End Type
Type point2dgamma
x As Double
y As Double
g As Double'gamma is ratio
End Type
Type point3d
x As Double
y As Double
z As Double
End Type
Type cube2dgamma
p(8) As point2dgamma
End Type
Type axes2dgamma
p(6) As point2dgamma
End Type
Type rotation3daxes
xy As Double
xz As Double
yz As Double
End Type
Type camera
pos3d      As point3d
rot3d      As rotation3daxes
stationary As Byte
visible    As Byte
lensdepth  As Integer'this adjusts FOV
End Type
Type lines
lstart As point3d
lend As point3d
End Type
Type line2dgamma
p(2) As point2dgamma
End Type

Declare Function rotate3dpoint(rotation_order As Integer,pivot As point3d, p As point3d, angle As rotation3daxes) As point3d
Declare Function proj3d(p3d As point3d,cam As camera) As point2dgamma
Declare Function abtp(abtpx1 as Double,abtpy1 as Double,abtpx2 as Double,abtpy2 as Double) as Double
Declare Function mymod(n As Double,m As Integer) As Double
Declare Sub drawonlens()

Const Pi = Atn(1) * 4
Dim As Integer _'iterators
i, _
j, _
k
'Dim Shared As Byte _'logic
'TRUE, _
'FALSE
'TRUE=-1
'FALSE=Not TRUE
Dim Shared As Integer _
mousex, _
mousey, _
mouseb, _
mousew, _
mousewp
Dim Shared tempd As Double
Dim Shared world_origin As point3d
world_origin.x=0
world_origin.y=0
world_origin.z=0

Dim pivotpoint3d As point3d
pivotpoint3d.x=0
pivotpoint3d.y=0
pivotpoint3d.z=0

Dim Shared vertex3d() As point3d
ReDim Preserve vertex3d(16)
For i = 1 To 14
Next

'Dim rotaxis As rotation3daxes
'rotaxis.xy=90
'rotaxis.xz=0
'rotaxis.yz=90

'4 cameras
'cam 1 stationary, front view
'cam 2 stationary, right side view
'cam 3 stationary, top view
'cam 4 can manuver, default isometric view (up/right/front)
Dim Shared cam(4) As camera

cam(1).lensdepth=256
'front view
cam(1).pos3d.x=0
cam(1).pos3d.y=0
cam(1).pos3d.z=512
cam(1).rot3d.xy=0
cam(1).rot3d.xz=0
cam(1).rot3d.yz=0
'right side view
'cam(1).pos3d.x=512
'cam(1).pos3d.y=0
'cam(1).pos3d.z=0
'cam(1).rot3d.xy=0
'cam(1).rot3d.xz=90
'cam(1).rot3d.yz=0
'right side view-2
'cam(1).pos3d.x=-512
'cam(1).pos3d.y=0
'cam(1).pos3d.z=0
'cam(1).rot3d.xy=0
'cam(1).rot3d.xz=270
'cam(1).rot3d.yz=0
'top view
'cam(1).pos3d.x=0
'cam(1).pos3d.y=512
'cam(1).pos3d.z=0
'cam(1).rot3d.xy=0
'cam(1).rot3d.xz=0
'cam(1).rot3d.yz=270
'bottom view
'cam(1).pos3d.x=0
'cam(1).pos3d.y=-512
'cam(1).pos3d.z=0
'cam(1).rot3d.xy=0
'cam(1).rot3d.xz=0
'cam(1).rot3d.yz=90
'1 is the top left corner of the lens
cam(1).stationary=TRUE'camera can not be repositioned
cam(1).visible=TRUE'camer can be seen and displayed by other cameras
'mousex=wx1+(wx2-wx1)*(mousex/(drawareax2-drawareax1))
Dim Shared As Integer wx1,wy1,wx2,wy2,wsize,scrnres
scrnres=256
wsize=256
wx1=wsize/2*-1
wy1=wsize/2*-1
wx2=wsize/2
wy2=wsize/2

ScreenRes scrnres,scrnres,8,2
ScreenSet(1,1)
View (0,0)-(scrnres-1,scrnres-1)'drawing area
Window (wx1,wy1)-(wx2,wy2)
Dim tempv As point3d
Dim temppivot As point3d
Dim tempvup As point3d
Dim tempvdn As point3d
Dim tempvlt As point3d
Dim tempvrt As point3d
Dim tempvfwd As point3d
Dim tempvrev As point3d
Dim tempangle As Double
Dim tempangleup As Double
Dim tempangledn As Double
Dim tempanglelt As Double
Dim tempanglert As Double
Dim tempanglefwd As Double
Dim tempanglerev As Double
Dim temprot As rotation3daxes
temprot.xy=0
temprot.xz=0
temprot.yz=0
Dim scube As cube2dgamma 'my spinning cube
Dim axes As axes2dgamma' my axes
Dim axesrot As rotation3daxes
axesrot.xy=0
axesrot.xz=0
axesrot.yz=0
Dim As Integer ti
Dim As String h
ti=1
Dim neg As Byte
Dim Shared As Integer rotationorder
rotationorder=5
Dim As Byte spincubexy,spincubexz,spincubeyz
Dim As Byte spinaxisxy,spinaxisxz,spinaxisyz
spincubexy=FALSE
spincubexz=FALSE
spincubeyz=FALSE
spinaxisxy=FALSE
spinaxisxz=FALSE
spinaxisyz=FALSE
Dim As Byte userotorder
userotorder=TRUE
Dim Shared line3d() As lines
ReDim Preserve line3d(1)
Dim lineg As line2dgamma
Dim shared linecount As Integer
linecount=0
Dim Shared As Byte leftmousebdown,rightmousebdown
leftmousebdown=FALSE
rightmousebdown=FALSE
Dim Shared As Integer mouseclicks
mouseclicks=0
Dim Shared drawingz As Integer
Dim Shared nm As Integer
nm=256
Do
For i=0 To 359
ScreenCopy 0,1
If cam(1).rot3d.xz<0 Then neg=TRUE Else neg = FALSE
cam(1).rot3d.xy=mymod(cam(1).rot3d.xy,360)
cam(1).rot3d.xz=mymod(cam(1).rot3d.xz,360)
cam(1).rot3d.yz=mymod(cam(1).rot3d.yz,360)
If cam(1).rot3d.xy<0 Then cam(1).rot3d.xy=360+cam(1).rot3d.xy
If cam(1).rot3d.xz<0 Then cam(1).rot3d.xz=360+cam(1).rot3d.xz
If cam(1).rot3d.yz<0 Then cam(1).rot3d.yz=360+cam(1).rot3d.yz
Locate 29,1
If userotorder=FALSE Then
Print "simulated axes - 6 rot orders   ";
Else
Print "simulated axes rotation order";rotationorder;
EndIf
Locate 30,1
Print "cam using rotation order";rotationorder;
Locate 1,1
Print "camrot axis xy ";cam(1).rot3d.xy
Print "camrot axis xz ";cam(1).rot3d.xz
Print "camrot axis yz ";cam(1).rot3d.yz
Print "cam x pos ";cam(1).pos3d.x
Print "cam y pos ";cam(1).pos3d.y
Print "cam z pos ";cam(1).pos3d.z
Print "sim axis xy ";axesrot.xy
Print "sim axis xz ";axesrot.xz
Print "sim axis yz ";axesrot.yz
Print "cam lens depth ";cam(1).lensdepth
Print "drawing depth ";drawingz
Print "nm=";nm
drawonlens
If spincubexy=TRUE Then temprot.xy=i Else temprot.xy=0
If spincubexz=TRUE Then temprot.xz=i Else temprot.xz=0
If spincubeyz=TRUE Then temprot.yz=i Else temprot.yz=0
If spinaxisxy=TRUE Then axesrot.xy=i' Else axesrot.xy=0
If spinaxisxz=TRUE Then axesrot.xz=i' Else axesrot.xz=0
If spinaxisyz=TRUE Then axesrot.yz=i' Else axesrot.yz=0
'spin cube
For j = 1 To 8
tempv=vertex3d(j)
tempv=rotate3dpoint(rotationorder,world_origin, tempv, temprot)
scube.p(j)=proj3d(tempv,cam(1))
Next
If scube.p(1).g<>0 Then
Circle (scube.p(1).x,scube.p(1).y),10*scube.p(1).g,15
If scube.p(2).g<>0 Then
Line(scube.p(1).x,scube.p(1).y)-(scube.p(2).x,scube.p(2).y),13
EndIf
If scube.p(4).g<>0 Then
Line(scube.p(1).x,scube.p(1).y)-(scube.p(4).x,scube.p(4).y),13
EndIf
If scube.p(5).g<>0 Then
Line(scube.p(1).x,scube.p(1).y)-(scube.p(5).x,scube.p(5).y),15
EndIf
End If
If scube.p(2).g<>0 Then
If scube.p(3).g<>0 Then
Line(scube.p(2).x,scube.p(2).y)-(scube.p(3).x,scube.p(3).y),13
EndIf
If scube.p(6).g<>0 Then
Line(scube.p(2).x,scube.p(2).y)-(scube.p(6).x,scube.p(6).y),15
EndIf
EndIf
If scube.p(3).g<>0 Then
If scube.p(4).g<>0 Then
Line(scube.p(3).x,scube.p(3).y)-(scube.p(4).x,scube.p(4).y),13
EndIf
If scube.p(7).g<>0 Then
Line(scube.p(3).x,scube.p(3).y)-(scube.p(7).x,scube.p(7).y),15
EndIf
EndIf
If scube.p(4).g<>0 And scube.p(8).g<>0 Then
Line(scube.p(4).x,scube.p(4).y)-(scube.p(8).x,scube.p(8).y),15
EndIf
If scube.p(5).g<>0 Then
If scube.p(6).g<>0 Then
Line(scube.p(5).x,scube.p(5).y)-(scube.p(6).x,scube.p(6).y),14
EndIf
If scube.p(8).g<>0 Then
Line(scube.p(5).x,scube.p(5).y)-(scube.p(8).x,scube.p(8).y),14
EndIf
EndIf
If scube.p(6).g<>0 And scube.p(7).g<>0 Then
Line(scube.p(6).x,scube.p(6).y)-(scube.p(7).x,scube.p(7).y),14
EndIf
If scube.p(7).g<>0 And scube.p(8).g<>0 Then
Line(scube.p(7).x,scube.p(7).y)-(scube.p(8).x,scube.p(8).y),14
EndIf
For j=1 To 6
tempv=vertex3d(j+8)
If userotorder=TRUE Then
tempv=rotate3dpoint(rotationorder,world_origin, tempv, axesrot)
Else
tempv=rotate3dpoint(j,world_origin, tempv, axesrot)
End If
axes.p(j)=proj3d(tempv,cam(1))
Next
For j=2 To 6 Step 2
Circle (axes.p(j).x,axes.p(j).y),10*axes.p(j).g,15
Next
If axes.p(1).g<>0 And axes.p(2).g<>0 Then
Line(axes.p(1).x,axes.p(1).y)-(axes.p(2).x,axes.p(2).y),10
EndIf
If axes.p(3).g<>0 And axes.p(4).g<>0 Then
Line(axes.p(3).x,axes.p(3).y)-(axes.p(4).x,axes.p(4).y),11
EndIf
If axes.p(5).g<>0 And axes.p(6).g<>0 Then
Line(axes.p(5).x,axes.p(5).y)-(axes.p(6).x,axes.p(6).y),12
EndIf
For j = 1 To linecount
tempv=line3d(j).lstart
lineg.p(1)=proj3d(tempv,cam(1))
tempv=line3d(j).lend
lineg.p(2)=proj3d(tempv,cam(1))
If lineg.p(1).g<>0 And lineg.p(2).g<>0 Then
Line(lineg.p(1).x,lineg.p(1).y)-(lineg.p(2).x,lineg.p(2).y)
EndIf
Next

tempv=cam(1).pos3d
h=InKey
Select Case h
Case Chr(27)
Exit Do
Case "+"
wsize=wsize-4
If wsize=0 Then wsize=4
wx1=wsize/2*-1
wy1=wsize/2*-1
wx2=wsize/2
wy2=wsize/2
Window (wx1,wy1)-(wx2,wy2)
Case "-"
wsize=wsize+4
wx1=wsize/2*-1
wy1=wsize/2*-1
wx2=wsize/2
wy2=wsize/2
Window (wx1,wy1)-(wx2,wy2)
Case "d"
drawingz=drawingz+1
Case "c"
drawingz=drawingz-1
'If drawingz<0 Then drawingz=0
Case "a"
cam(1).lensdepth=cam(1).lensdepth+1
Case "z"
cam(1).lensdepth=cam(1).lensdepth-1
If cam(1).lensdepth< 1 Then cam(1).lensdepth=1
Case "q"
If userotorder=FALSE Then userotorder=TRUE Else userotorder=FALSE
Case "Q"
axesrot.xy=0
axesrot.xz=0
axesrot.yz=0
Case "r","R"
rotationorder=rotationorder+1
If rotationorder=7 Then rotationorder=1
Case "1"
If spincubexy=FALSE Then
spincubexy=TRUE
Else
spincubexy=FALSE
EndIf
Case "2"
If spincubexz=FALSE Then
spincubexz=TRUE
Else
spincubexz=FALSE
EndIf
Case "3"
If spincubeyz=FALSE Then
spincubeyz=TRUE
Else
spincubeyz=FALSE
EndIf
Case "!"
If spinaxisxy=FALSE Then
spinaxisxy=TRUE
Else
spinaxisxy=FALSE
EndIf
Case "@"
If spinaxisxz=FALSE Then
spinaxisxz=TRUE
Else
spinaxisxz=FALSE
EndIf
Case "#"
If spinaxisyz=FALSE Then
spinaxisyz=TRUE
Else
spinaxisyz=FALSE
EndIf
Case Chr(255)+"H"'up
'according to camera's current rotation on the 3 axes
'adjust cam.x,y,z effectively moving the camera up
tempv.y=cam(1).pos3d.y +10
cam(1).pos3d=rotate3dpoint(rotationorder,cam(1).pos3d, tempv, cam(1).rot3d)
Case Chr(255)+"P"'dn
'according to camera's current rotation on the 3 axes
'adjust cam.x,y,z effectively moving the camera down
tempv.y=cam(1).pos3d.y -10
cam(1).pos3d=rotate3dpoint(rotationorder,cam(1).pos3d, tempv, cam(1).rot3d)
Case Chr(255)+"K"'lt
'according to camera's current rotation on the 3 axes
'adjust cam.x,y,z effectively moving the camera left
tempv.x=cam(1).pos3d.x -10
cam(1).pos3d=rotate3dpoint(rotationorder,cam(1).pos3d, tempv, cam(1).rot3d)
Case Chr(255)+"M"'rt
'according to camera's current rotation on the 3 axes
'adjust cam.x,y,z effectively moving the camera right
tempv.x=cam(1).pos3d.x +10
cam(1).pos3d=rotate3dpoint(rotationorder,cam(1).pos3d, tempv, cam(1).rot3d)
Case "w"
'according to camera's current rotation on the 3 axes
'adjust cam.x,y,z effectively moving the camera forward
tempv.z=cam(1).pos3d.z +10
cam(1).pos3d=rotate3dpoint(rotationorder,cam(1).pos3d, tempv, cam(1).rot3d)
Case "s"
'according to camera's current rotation on the 3 axes
'adjust cam.x,y,z effectively moving the camera backwards
tempv.z=cam(1).pos3d.z -10
cam(1).pos3d=rotate3dpoint(rotationorder,cam(1).pos3d, tempv, cam(1).rot3d)
Case "y"
'according to camera's current rotation on the 3 axes
'adjust cam.x,y,z effectively rolling the camera clock wise
cam(1).rot3d.xy=cam(1).rot3d.xy+1
Case "Y"
axesrot.xy=Int(axesrot.xy)+1
Case "h"
'according to camera's current rotation on the 3 axes
'adjust cam.x,y,z effectively rolling the camera counter clock wise
cam(1).rot3d.xy=cam(1).rot3d.xy-1
Case "H"
axesrot.xy=Int(axesrot.xy)-1
Case "u"
'according to camera's current rotation on the 3 axes
'adjust cam.x,y,z effectively yawing the camera's nose to the left
cam(1).rot3d.xz=cam(1).rot3d.xz+1
Case "U"
axesrot.xz=Int(axesrot.xz)+1
Case "j"
'according to camera's current rotation on the 3 axes
'adjust cam.x,y,z effectively yawing the camera's nose to the right
cam(1).rot3d.xz=cam(1).rot3d.xz-1
Case "J"
axesrot.xz=Int(axesrot.xz)-1
Case "i"
'according to camera's current rotation on the 3 axes
'adjust cam.x,y,z effectively pitching the camera's nose up
cam(1).rot3d.yz=cam(1).rot3d.yz+1
Case "I"
axesrot.yz=Int(axesrot.yz)+1
Case "k"
'according to camera's current rotation on the 3 axes
'adjust cam.x,y,z effectively pitching the camera's nose down
cam(1).rot3d.yz=cam(1).rot3d.yz-1
Case "K"
axesrot.yz=Int(axesrot.yz)-1
Case Chr(27), Chr(255) + "k"
Exit Do
Case Else
End Select
GetMouse mousex,mousey,mousew,mouseb
Sleep 1

Next
Loop
End

Function rotate3dpoint(rotation_order As Integer,pivot As point3d, p As point3d, angle As rotation3daxes) As point3d
Dim As Double _
dx,           _
dy,           _
dz,           _
anglexy,      _
anglexz,      _
angleyz

dx=p.x-pivot.x
dy=p.y-pivot.y
dz=p.z-pivot.z
anglexy=angle.xy*pi/180
anglexz=angle.xz*pi/180
angleyz=angle.yz*pi/180
'starting off from the front or right views and yawing
'rotation order #3 works when rotation on xz axis 0 to 359 (yaw)
'ie. rotating xy axis makes it roll
'    rotating yz axis makes it pitch
'starting off from the top view
'rotation 5 works
'Locate 31,20
Select Case rotation_order
Case 1
'Print "xy - xz - yz";
p.x=dx*Cos(anglexy) - dy*Sin(anglexy)
p.y=dy*Cos(anglexy) + dx*Sin(anglexy)
dx=p.x
dy=p.y
p.x=dx*Cos(anglexz) + dz*Sin(anglexz)
p.z=dz*Cos(anglexz) - dx*Sin(anglexz)
dz=p.z
p.y=dy*Cos(angleyz) - dz*Sin(angleyz)
p.z=dz*Cos(angleyz) + dy*Sin(angleyz)
Case 2
'Print "xy - yz - xz";
p.x=dx*Cos(anglexy) - dy*Sin(anglexy)
p.y=dy*Cos(anglexy) + dx*Sin(anglexy)
dx=p.x
dy=p.y
p.y=dy*Cos(angleyz) - dz*Sin(angleyz)
p.z=dz*Cos(angleyz) + dy*Sin(angleyz)
dz=p.z
p.x=dx*Cos(anglexz) + dz*Sin(anglexz)
p.z=dz*Cos(anglexz) - dx*Sin(anglexz)
Case 3
'Print "xz - xy - yz";
p.x=dx*Cos(anglexz) + dz*Sin(anglexz)
p.z=dz*Cos(anglexz) - dx*Sin(anglexz)
dx=p.x
dz=p.z
p.x=dx*Cos(anglexy) - dy*Sin(anglexy)
p.y=dy*Cos(anglexy) + dx*Sin(anglexy)
dy=p.y
p.y=dy*Cos(angleyz) - dz*Sin(angleyz)
p.z=dz*Cos(angleyz) + dy*Sin(angleyz)
Case 4
'Print "xz - yz - xy";
p.x=dx*Cos(anglexz) + dz*Sin(anglexz)
p.z=dz*Cos(anglexz) - dx*Sin(anglexz)
dx=p.x
dz=p.z
p.y=dy*Cos(angleyz) - dz*Sin(angleyz)
p.z=dz*Cos(angleyz) + dy*Sin(angleyz)
dy=p.y
p.x=dx*Cos(anglexy) - dy*Sin(anglexy)
p.y=dy*Cos(anglexy) + dx*Sin(anglexy)
Case 5
'Print "yz - xy - xz";
p.y=dy*Cos(angleyz) - dz*Sin(angleyz)
p.z=dz*Cos(angleyz) + dy*Sin(angleyz)
dy=p.y
dz=p.z
p.x=dx*Cos(anglexy) - dy*Sin(anglexy)
p.y=dy*Cos(anglexy) + dx*Sin(anglexy)
dx=p.x
p.x=dx*Cos(anglexz) + dz*Sin(anglexz)
p.z=dz*Cos(anglexz) - dx*Sin(anglexz)
Case 6
'Print "yz - xz - xy";
p.y=dy*Cos(angleyz) - dz*Sin(angleyz)
p.z=dz*Cos(angleyz) + dy*Sin(angleyz)
dy=p.y
dz=p.z
p.x=dx*Cos(anglexz) + dz*Sin(anglexz)
p.z=dz*Cos(anglexz) - dx*Sin(anglexz)
dx=p.x
p.x=dx*Cos(anglexy) - dy*Sin(anglexy)
p.y=dy*Cos(anglexy) + dx*Sin(anglexy)
End Select

p.x=p.x+pivot.x
p.y=p.y+pivot.y
p.z=p.z+pivot.z
Return p
End Function

'project_3d_point_to_2d_camera_lens_to_screen_xy_coordinates_with_gamma
Function proj3d(   _
p3d As point3d, _
cam As camera   _
) As point2dgamma
Dim As Integer camlensdepth
camlensdepth=cam.lensdepth
Dim p2dg As point2dgamma
Dim As Double _
z_distance,   _
z_ratio
Dim As point3d _
tp,            _'temp_point
tc              'temp_camera_position
Dim trot As rotation3daxes
Dim reversero As Integer
Select Case rotationorder
Case 1
reversero=6
Case 2
reversero=4
Case 3
reversero=5
Case 4
reversero=2
Case 5
reversero=3
Case 6
reversero=1
End Select
tp=p3d
trot.xy=cam.rot3d.xy*-1
trot.xz=cam.rot3d.xz*-1
trot.yz=cam.rot3d.yz*-1
tp=rotate3dpoint(reversero,cam.pos3d, p3d, trot)
tc=cam.pos3d
tp.x=tp.x-tc.x
tp.y=tp.y-tc.y
z_distance=tc.z-tp.z
If z_distance=0 Then z_distance=1
tempd=z_distance
z_ratio=camlensdepth/z_distance
p2dg.x=tp.x*z_ratio
p2dg.y=tp.y*z_ratio
If tp.z>=tc.z Then
p2dg.g=0
Else
p2dg.g=z_ratio
EndIf
Return p2dg
End Function

Sub drawonlens()
Dim As point3d mouse3d, scrnc3d, tempv
Dim As point2dgamma mouse2d, scrnc2d
'scrnc3d.x=0+cam(1).pos3d.x
'scrnc3d.y=0+cam(1).pos3d.y
'scrnc3d.z=cam(1).pos3d.z-256
'scrnc3d=rotate3dpoint(rotationorder,cam(1).pos3d,scrnc3d,cam(1).rot3d)
'Print Int(scrnc3d.x);Int(scrnc3d.y);Int(scrnc3d.z)
mousex=wx1+(wx2-wx1)*(mousex/scrnres)
mousey=(wy1+(wy2-wy1)*(mousey/scrnres))*-1

mouse3d.x=mousex*(256/cam(1).lensdepth)*((256-drawingz)/256)+cam(1).pos3d.x'*(drawingz/128)
mouse3d.y=mousey*(256/cam(1).lensdepth)*((256-drawingz)/256)+cam(1).pos3d.y
mouse3d.z=cam(1).pos3d.z-256+drawingz
mouse3d=rotate3dpoint(rotationorder,cam(1).pos3d,mouse3d,cam(1).rot3d)
Print Int(mouse3d.x);Int(mouse3d.y);Int(mouse3d.z)
'tempv=scrnc3d
'scrnc2d=proj3d(tempv,cam(1))
'tempv=mouse3d
'mouse2d=proj3d(tempv,cam(1))
'Line(scrnc2d.x,scrnc2d.y)-(mouse2d.x,mouse2d.y)
Select Case mouseb
Case 0
If leftmousebdown=TRUE Then
Select Case mouseclicks
Case 0
mouseclicks=1
line3d(0).lstart=mouse3d
Case 1
line3d(0).lend=mouse3d
linecount=linecount+1
ReDim Preserve line3d(linecount)
line3d(linecount)=line3d(0)
line3d(0).lstart=mouse3d
End Select
'mouse button released
'linecount=linecount+1
'ReDim Preserve line3d(linecount)
'line3d(linecount).lstart=scrnc3d
'line3d(linecount).lend=mouse3d
EndIf
If rightmousebdown=TRUE Then
If mouseclicks=1 Then
'linecount=linecount-1
mouseclicks=0
EndIf
EndIf
leftmousebdown=FALSE
rightmousebdown=FALSE
Case 1
leftmousebdown=TRUE
Case 2
rightmousebdown=TRUE
End Select
Print "line count=";linecount
If mouseclicks=1 Then
tempv=line3d(0).lstart
scrnc2d=proj3d(tempv,cam(1))
tempv=mouse3d
mouse2d=proj3d(tempv,cam(1))
Line(scrnc2d.x,scrnc2d.y)-(mouse2d.x,mouse2d.y)
End If
End Sub

Function abtp(abtpx1 as Double,abtpy1 as Double,abtpx2 as Double,abtpy2 as Double) as Double
Dim As Double xlength,ylength,angle
xlength=(abtpx1-abtpx2)*-1
ylength=(abtpy1-abtpy2)*-1
angle=atan2(ylength,xlength)/pi*180
If angle<0 then angle=360+angle Mod 360
abtp=angle
End Function

Function mymod(n As Double,m As Integer) As Double
Dim As Integer i
Dim As Byte neg, dec, inf
Select Case n
Case 0
mymod = 0
Case Else
If n<0 Then neg = TRUE Else neg = FALSE
dec=FALSE
inf=FALSE
Dim As String nstring
nstring=LTrim(Str(n))
'If Mid(nstring,1,1)="-" Then neg=TRUE Else neg=FALSE
If neg=TRUE Then nstring= Mid(nstring,2)
For i = 1 To Len(nstring)
Select Case Mid(nstring,i,1)
Case "0" To "9"
'this is ok
Case "."
dec=TRUE
Case Else
'Cls
'Print nstring
'Print Mid(nstring,i,1)
'sleep
inf=TRUE
Exit For
End Select
Next
If inf=TRUE Then
mymod=0
Else
If neg=TRUE Then
If dec=TRUE Then
mymod=Val(Str(val(Mid(nstring,1,InStr(nstring,".")-1)) Mod m)+"."+Mid(nstring,InStr(nstring,".")+1))*-1
Else
mymod = n Mod m
End If
Else
If dec=TRUE Then
mymod=Val(Str(val(Mid(nstring,1,InStr(nstring,".")-1)) Mod m)+"."+Mid(nstring,InStr(nstring,".")+1))
Else
mymod = n Mod m
End If
EndIf
EndIf
End Select
End Function

'spinning cube
Data -100, 100, 100
Data -100,-100, 100
Data  100,-100, 100
Data  100, 100, 100

Data -100, 100,-100
Data -100,-100,-100
Data  100,-100,-100
Data  100, 100,-100
'axes
'x axis
Data -100,0,0
Data  100,0,0
'y axis
Data 0, 100,0
Data 0,-100,0
'z axis
Data 0,0, 100
Data 0,0,-100

''axes
''x axis
'Data -100,10,10
'Data  100,10,10
''y axis
'Data 10, 100,10
'Data 10,-100,10
''z axis
'Data 10,10, 100
'Data 10,10,-100

BasicCoder2
Posts: 3400
Joined: Jan 01, 2009 7:03

Re: 3d without openGL

paul doe wrote:This resource explains it very nicely:
http://www.codinglabs.net/article_world ... atrix.aspx

Oh no!! Homework :) It may take some time to work through it.
Rotating and moving about a 2D world is something I have done a lot of. Everything from Asteroids to raycasting dungeons (pseudo 3D but looked great with textured floors, walls and ceilings) . It was only when I thought about moving the viewer up into the Z axis I had problems. I call z the up axis as I see the x,y as the floor although I note that many examples have y as the up axis as you might find in a math or physics book.

This is an example of the raycasting 3D . If differs from the way it was done in Wolfenstein 3D. I took advantage of a modern computer's speed and the variation I used enables curved walls. The demo only has simple sprites but it looks better with animated sprites that move around the corridors and better textures. Looking at it now I see I could speed up the drawing code.
viewtopic.php?f=3&t=20594&hilit=raycasting
.
paul doe
Posts: 922
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: 3d without openGL

BasicCoder2 wrote:Rotating and moving about a 2D world is something I have done a lot of. Everything from Asteroids to raycasting dungeons (pseudo 3D but looked great with textured floors, walls and ceilings) . It was only when I thought about moving the viewer up into the Z axis I had problems. I call z the up axis as I see the x,y as the floor although I note that many examples have y as the up axis as you might find in a math or physics book.

This is an example of the raycasting 3D . If differs from the way it was done in Wolfenstein 3D. I took advantage of a modern computer's speed and the variation I used enables curved walls. The demo only has simple sprites but it looks better with animated sprites that move around the corridors and better textures. Looking at it now I see I could speed up the drawing code.

Looks nice. I have a raycaster somewhere, don't remember where. It was more similar to the one used in Doom2 (with sectors, linedefs and all that). But it seems that you're missing the point.
It is not about what the code does, it's how it does it. While it looks deceptively simple, it isn't, trust me. By the way, didn't you noticed that you can do perfectly good 3D code with the mat3 definitions? As it is a 3x3 matrix, you can't represent encode translations or projections also, but you can do that manually and it still works. In fact, the 3D code that I'm preparing initially used that implementation. To be able to work with OpenGL however, you need 4x4 matrices. Also, it is advisable that you adopt a more common convention for the axes, like the ones used by OpenGL/Direct3D. That way, you will be able to recycle much of the insight you gain by studying all that matrix and vector stuff. The only thing I know that uses the convention you mentioned is Maya.

Let's see if I can make my point across this time. Platform.bi, core.bi, mat3.bi and vec2h.bi are all the same as the previous example:

grid.bi

Code: Select all

#ifndef __grid__
#define __grid__

#include once "core.bi"
#include once "vec2h.bi"
#include once "mat3.bi"

/'
A bounding box (or bounding rectangle) object

this is useful for clipping of objects, geometry, and collision detection, to
name a few applications.
The grid object derives from here, and also provides spatial partition functionality
besides the bounding functionality
'/
class boundingBox extends object
protected:
as mat3 m_transform = any
as mat3 m_invTransform = any

as double m_width = any
as double m_height = any

public:
declare constructor()
declare constructor( byval w as double, byval h as double )

declare property transform() as mat3
declare property transform( byval M as mat3 )

declare property invTransform() as mat3

declare virtual property width() as double
declare virtual property width( byval value as double )

declare virtual property height() as double
declare virtual property height( byval value as double )

declare function isInside( byval p as vec2h ) as boolean
declare function getPoint( byval p as vec2h ) as vec2h

declare virtual sub render( byval c as uint32 = rgba( 222, 222, 222, 255 ) )
end class

constructor boundingBox()
m_width = 100.0
m_height = 100.0

m_transform = matrices.identity2h()
m_invTransform = inverse( m_transform )
end constructor

constructor boundingBox( byval w as double, byval h as double )
m_width = iif( w > 0, w, 100.0 )
m_height = iif( h > 0, h, 100.0 )

m_transform = matrices.identity2h()
m_invTransform = inverse( m_transform )

end constructor

property boundingBox.transform() as mat3
return( m_transform )
end property

property boundingBox.transform( byval M as mat3 )
m_transform = M
'' update also the inverse matrix of the transformation
m_invTransform = inverse( m_transform )
end property

property boundingBox.invTransform() as mat3
return( m_invTransform )
end property

property boundingBox.width() as double
return( m_width )
end property

property boundingBox.width( byval value as double )
m_width = value
end property

property boundingBox.height() as double
return( m_height )
end property

property boundingBox.height( byval value as double )
m_height = value
end property

function boundingBox.getPoint( byval p as vec2h ) as vec2h
'' returns the point in bounding box coordinates
return( m_invTransform * p )
end function

function boundingBox.isInside( byval p as vec2h ) as boolean
/'
this function returns true if a point p lies inside the bounding box

0,0 +-------+
|       |
|   p   |
|       |
+-------+
m_width, m_height

it works transforming the point (which must be specified in global coordinates)
to the coordinate system of the bounding box, by multiplying the point with the
inverse transformation matrix of the bounding box.
After the transformation, the test is straightforward.
'/
dim as vec2h tp = getPoint( p )

return( iif( tp.x >= 0 andAlso tp.x <= m_width - 1 andAlso tp.y >= 0 andAlso tp.y <= m_height - 1, true, false ) )
end function

sub boundingBox.render( byval c as uint32 = rgba( 222, 222, 222, 255 ) )
'' renders the bounding box (mostly for debug purposes)

/'
p0 +-----+ p3
|     |
|     |
|     |
p1 +-----+ p2
'/
'' transform the origin and derive the rest of the points from there
dim as vec2h p0 = m_transform * vec2h( 0.0, 0.0 )
dim as vec2h p1 = m_transform * vec2h( 0.0, m_height )
dim as vec2h p2 = m_transform * vec2h( m_width, m_height )
dim as vec2h p3 = m_transform * vec2h( m_width, 0.0 )

line( p0.x, p0.y ) - ( p1.x, p1.y ), c
line( p1.x, p1.y ) - ( p2.x, p2.y ), c
line( p2.x, p2.y ) - ( p3.x, p3.y ), c
line( p3.x, p3.y ) - ( p0.x, p0.y ), c

dim as vec2h center = vec2h( p0.x + m_width / 2, p0.y + m_height / 2 )

circle( center.x, center.y ), 6, rgba( 255, 0, 255, 255 ), , , , f
end sub

/'
grid object

A grid object that can be used as an acceleration structure
Derives from boundingBox
'/
class grid extends boundingBox
protected:
as double m_cellWidth
as double m_cellHeight
as integer m_gridXSize
as integer m_gridYSize

public:
declare constructor()
declare constructor( byval w as double, byval h as double, byval xSize as integer, byval ySize as integer )
declare destructor()

declare property width() as double
declare property width( byval value as double )

declare property height() as double
declare property height( byval value as double )

declare property gridXSize() as integer
declare property gridXSize( byval value as integer )

declare property gridYSize() as integer
declare property gridYSize( byval value as integer )

declare property cellWidth() as double
declare property cellHeight() as double

declare function getCell( byval p as vec2h ) as vec2h

declare sub render( byval c as uint32 = rgba( 200, 200, 200, 255 ) )
end class

constructor grid( byval w as double, byval h as double, byval xSize as integer, byval ySize as integer )
'' call the base constructor to initialize
base.constructor( w, h )

m_gridXSize = iif( xSize > 0, xSize, 1 )
m_gridYSize = iif( ySize > 0, ySize, 1 )
m_cellWidth = m_width / m_gridXSize
m_cellHeight = m_height / m_gridYSize
end constructor

destructor grid()

end destructor

property grid.cellWidth() as double
return( m_cellWidth )
end property

property grid.cellHeight() as double
return( m_cellHeight )
end property

property grid.width() as double
return( m_width )
end property

property grid.width( byval value as double )
m_width = value
m_cellWidth = m_width / m_gridXSize
end property

property grid.height() as double
return( m_height )
end property

property grid.height( byval value as double )
m_height = value
m_cellHeight = m_height / m_gridYSize
end property

property grid.gridXSize() as integer
return( m_gridXSize )
end property

property grid.gridXSize( byval value as integer )
m_gridXSize = value
m_cellWidth = m_width / m_gridXSize
end property

property grid.gridYSize() as integer
return( m_gridYSize )
end property

property grid.gridYSize( byval value as integer )
m_gridYSize = value
m_cellHeight = m_height / m_gridYSize
end property

function grid.getCell( byval p as vec2h ) as vec2h
'' returns the cell in the grid this point lies on
'' assumes the point is already in grid space coordinates
return( vec2h( p.x / m_cellWidth, p.y / m_cellHeight ) )
end function

sub grid.render( byval c as uint32 = rgba( 200, 200, 200, 255 ) )
/'
get the 4 corners of the grid

p0-----p3
|       |
|       |
p1-----p2
'/
'' transform the grid corners to grid space
dim as vec2h p0 = m_transform * vec2h( 0.0, 0.0 )
dim as vec2h p1 = m_transform * vec2h( 0.0, m_height )
dim as vec2h p2 = m_transform * vec2h( m_width, m_height )
dim as vec2h p3 = m_transform * vec2h( m_width, 0.0 )

'' draw the borders
line( p0.x, p0.y ) - ( p1.x, p1.y ), c
line( p1.x, p1.y ) - ( p2.x, p2.y ), c
line( p2.x, p2.y ) - ( p3.x, p3.y ), c
line( p3.x, p3.y ) - ( p0.x, p0.y ), c

'' horizontal lines
for y as double = 0 to 1 step 1 / m_gridYSize
dim as vec2h st = interpolate( p0, p1, y )
dim as vec2h en = interpolate( p3, p2, y )

line( st.x, st.y ) - ( en.x, en.y ), c
next

'' vertical lines
for x as double = 0 to 1 step 1 / m_gridXSize
dim as vec2h st = interpolate( p0, p3, x )
dim as vec2h en = interpolate( p1, p2, x )

line( st.x, st.y ) - ( en.x, en.y ), c
next
end sub
#endif

grid test.bas

Code: Select all

#include once "platform.bi"
#include once "core.bi"
#include once "grid.bi"
#include once "vec2h.bi"
#include once "mat3.bi"

/'
grid debug program

conventions: same as the previous code
'/

dim as integer scrW = 800, scrH = 600
screenRes( scrW, scrH, 32 )
windowTitle( fbVersion & " - Simple matrix and vector demo" )

dim as vec2h mp '' mouse coordinates
dim as vec2h gp '' mouse coordinates in grid space

'' the angle of rotation
dim as double ang = 0.0

'' instantiate a grid ( 400 x 400 px, 10 x 10 cells )
dim as grid g = grid( 400, 400, 10, 10 )

'' position of the grid (the logical origin of the grid is at the top left corner)
dim as vec2h gridPos = vec2h( scrW / 2 - g.width / 2, scrH / 2 - g.height / 2 )

'' axis of rotation
dim as vec2h axis = vec2h( gridPos.x, gridPos.y )

dim as string k
dim as integer mx, my

do
getMouse( mx, my )
mp = vec2h( mx, my )

k = lcase( inkey() )

'' translate the grid to its position
dim as mat3 T = matrices.translation2h( gridPos.x, gridPos.y )
'' rotation about the center of the grid
dim as mat3 R = matrices.rotationAxis2h( vec2h( g.width / 2, g.height / 2 ), ang )
'' shear the grid a little to deform it
dim as mat3 S = matrices.shear2h( 25, 12 )

'' transform the grid
'' try to change the order of this, or to provide another matrix and see what happens
g.transform = T * S * R

'' transform the mouse pointer to grid space
gp = g.getPoint( mp )

'' get the mouse coordinates in grid cell space
dim as vec2h cell = g.getCell( g.invTransform * vec2h( mp ) )

screenLock()
cls()

'' render the grid
g.render( rgba( 64, 64, 64, 255 ) )

/'
this is the important bit of code that does the magic

what it actually does, is computing which cell of the grid the user is pointing
with the mouse. Note, however, that the grid can be grotesquely deformed, yet
the cell is computed correctly. And, as you can see, is in fact treated like it
was a simple, untransformed, rectangle on the screen
'/

'' check if the mouse pointer is inside the grid
if( cell.x >= 0 and cell.x <= g.gridXSize and cell.y >= 0 and cell.y <= g.gridYSize ) then
/'
if it is, draw the cell the mouse is pointing to

the points are winded like this:

p0 +--------+ p3
|        |
|        |
p1 +--------+ p2

which is to say, are computed in counter-clockwise order
'/

'' top left
dim as vec2h p0 = g.transform * _
vec2h( int( cell.x ) * g.cellWidth, int( cell.y ) * g.cellHeight )

'' bottom left
dim as vec2h p1 = g.transform * _
vec2h( int( cell.x ) * g.cellWidth, int( cell.y ) * g.cellHeight + g.cellHeight )

'' bottom right
dim as vec2h p2 = g.transform * _
vec2h( int( cell.x ) * g.cellWidth + g.cellWidth, int( cell.y ) * g.cellHeight + g.cellHeight )

'' top right
dim as vec2h p3 = g.transform * _
vec2h( int( cell.x ) * g.cellWidth + g.cellWidth, int( cell.y ) * g.cellHeight )

'' draw the cell edges
line( p0.x, p0.y ) - ( p1.x, p1.y ), rgba( 255, 255, 0, 255 )
line( p1.x, p1.y ) - ( p2.x, p2.y ), rgba( 255, 255, 0, 255 )
line( p2.x, p2.y ) - ( p3.x, p3.y ), rgba( 255, 255, 0, 255 )
line( p3.x, p3.y ) - ( p0.x, p0.y ), rgba( 255, 255, 0, 255 )
end if

screenUnlock()

ang += 0.05

if ang > 360.0 then
ang -= 360.0
end if

sleep( 1 )
loop until k = chr( 27 )

In this more than unimpressive demo, you see a (somewhat deformed) grid that it's rotating. And it detects the cell that you are pointing at with the mouse. Nothing fancy, no? But, take a look at the main loop. Specifically, the part that calculates which cell you are picking. Simple enough, right? The trick here is that it uses coordinate system mapping (via matrix multiplication) to map the mouse coordinates to the coordinate system of the grid, where it is trivial to compute the cell being picked. And all that while the grid is being transformed. If you where to do that in an absolute coordinate system (as with Euler angles), you will be in for a lot of trouble. Wanna try it? I'll like to see ;-)

owen wrote:here's what i was working on back in 2013

Perfect timing. Thanks, owen.

@BasicCoder2: look inside owen's code, specifically the rotate3dPoint function. You'll see that, with Euler angles, you have six different ways in which you can rotate a point, and the axes change depending on which one you rotate first (play with owen's code to see what I mean). And all of them about the absolute coordinates of the world, not that of the object itself. This problem disappears if you use matrices (no matter in which order you compose the rotations; as far as the matrix is concerned, they are multiplied all at once)

The thing to remember here is: matrix * point = point transformed to the matrix coordinate system. InverseMatrix * point = point from the matrix coordinate system to absolute (aka world) coordinates.

Hope this helps with your homework ;-)
Paul
owen
Posts: 552
Joined: Apr 19, 2006 10:55
Location: Kissimmee, FL
Contact:

Re: 3d without openGL

@ paul doe

This problem disappears if you use matrices

I couldn't agree with you more.
And not only that, using matrices avoids gimbal lock

in my example you can see it encounter gimbal lock by first rotating 90 degrees on the x axis, then 90 on y axis and then stop at 89 on the z.
when you get to 89 on the z axis and proceed to rotate to 90 look closely and notice a tiny glitch.

what a wonderful experience it was to try and avoid gimbal lock to no avail. well actually i kinda did it but what length i went to.

im glad that my example can serve the perfect example of what not to do. LOL
dodicat
Posts: 5951
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: 3d without openGL

Basiccoder2
Rotating about an axis.
I did this a while ago
Windows.
The main point is lines 274/275

preset the z rotation at pi/2

then tilt the y by -pi/8

then rotate about the x axis only.

The rest of the code is the shape and texture to rotate.
(cube + numbers)
But the idea should apply to any 3D rotate about a point.

Code: Select all

#include Once "windows.bi"
#include Once "fbgfx.bi"
Const pi=4*Atn(1)

Function contrast(c As Ulong) As Ulong
#define Intrange(f,l) Int(Rnd*((l+1)-(f))+(f))
'get the rgb values
Dim As Ubyte r=Cptr(Ubyte Ptr,@c),g=Cptr(Ubyte Ptr,@c),b=Cptr(Ubyte Ptr,@c),r2,g2,b2
Do
r2=Intrange(0,255):g2=IntRange(0,255):b2=IntRange(0,255)
'get at least 120 byte difference
Loop Until Abs(r-r2)>120 Andalso Abs(g-g2)>120 Andalso Abs(b-b2)>120
Return Rgb(r2,g2,b2)
End Function

#define BOLD  2
#define ITALIC 4
Sub DrawFont(Byref BUFFER As Any Ptr=0,Byval POSX As Long, Byval POSY As Long, _
Byref FTEXT As String, Byref FNAME As String,Byval FSIZE As Long, _
Byval FCOLOR As Ulong=Rgba(255,255,255,255),Byval FSTYLE As Long=0, _
Byval CHARSET As Long=DEFAULT_CHARSET) Export

Static FINIT As Long
Static As hdc THEDC
Static As hbitmap THEBMP
Static As Any Ptr THEPTR
Static As fb.image Ptr FBBLK
Static As Long TXTSZ,RESU,RESUU
Static As hfont THEFONT
Static As Long FW,FI,TXYY',FCOR
Static DSKWND As hwnd, DSKDC As hdc
Static MYBMPINFO As BITMAPINFO
Static As TEXTMETRIC MYTXINFO
Static As SIZE TXTSIZE
Static As RECT RCT
Static As Ubyte Ptr ubp
ubp=Cptr(Ubyte Ptr,@FCOLOR)
Swap ubp,ubp
Dim As Ubyte alphaval =ubp
ubp=0
#define FontSize(PointSize) - MulDiv(PointSize, GetDeviceCaps(THEDC, LOGPIXELSY), 72)

If FINIT = 0 Then
FINIT = 1
.biWidth = 2048
.biHeight = -513
.biPlanes = 1
.biBitCount = 32
.biCompression = BI_RGB
End With
DSKWND = GetDesktopWindow()
DSKDC = GetDC(DSKWND)
THEDC = CreateCompatibleDC(DSKDC)
THEBMP = CreateDIBSection(THEDC,@MYBMPINFO,DIB_RGB_COLORS,@THEPTR,null,null)
ReleaseDC(DSKWND,DSKDC)
End If
If (FSTYLE And 2) Then FW = FW_BOLD Else FW = FW_NORMAL
If (FSTYLE And 4) Then FI = True Else FI = False
THEFONT = CreateFont(FontSize(FSIZE),0,0,0,FW,FI,0,0,CHARSET,0,0,0,0,Cast(Any Ptr,Strptr(FNAME)))
SelectObject(THEDC,THEBMP)
SelectObject(THEDC,THEFONT)
GetTextMetrics(THEDC,@MYTXINFO)
GetTextExtentPoint32(THEDC,Strptr(FTEXT),Len(FTEXT),@TXTSIZE)
TXTSZ = TXTSIZE.CX
TXYY = TXTSIZE.CY
If (FSTYLE And 4) Then
If MYTXINFO.tmOverhang Then
TXTSZ += MYTXINFO.tmOverhang
Else
TXTSZ += 1+(FSIZE/2)
End If
TXYY += 1+(FSIZE/8)
End If
RCT.LEFT = 0
RCT.TOP = 1
RCT.RIGHT = TXTSZ
RCT.BOTTOM = TXYY+1
TXTSZ -= 1
TXYY -= 1
SetBkColor(THEDC,Rgba(255,0,255,0))
SetTextColor(THEDC,FCOLOR)
SystemParametersInfo(SPI_GETFONTSMOOTHING,null,@RESU,null)
If RESU Then SystemParametersInfo(SPI_SETFONTSMOOTHING,False,@RESUU,null)
ExtTextOut(THEDC,0,1,ETO_CLIPPED Or ETO_OPAQUE,@RCT,Strptr(FTEXT),Len(FTEXT),null)
If RESU Then SystemParametersInfo(SPI_SETFONTSMOOTHING,True,@RESUU,null)
FBBLK = THEPTR+(2048*4)-Sizeof(fb.image)
FBBLK->Type = 7
FBBLK->bpp = 4
FBBLK->Width = 2048
FBBLK->height = 512
FBBLK->pitch = 2048*4
Put BUFFER,(POSX,POSY),FBBLK,(0,0)-(TXTSZ-1,TXYY),Alpha,alphaval
DeleteObject(THEFONT)
End Sub

Type v3
As Single x,y,z
As Ulong c
End Type

Type _float
As Single x,y,z
End Type

Type sincos 'FLOATS for angles
As Single sx,sy,sz
As Single cx,cy,cz
Declare Static Function construct(As Single,As Single,As Single) As sincos
End Type

Function sincos.construct(x As Single,y As Single,z As Single) As sincos
Return   Type <sincos>(Sin(x),Sin(y),Sin(z), _
Cos(x),Cos(y),Cos(z))
End Function
Dim Shared As v3 eyepoint

Sub load(im As Any Ptr,w() As V3,Byref ctr As V3,T As Long)
Dim As Ulong c,bc=Rgb(255,0,255)
Dim As Integer pitch
Dim As Any Ptr row
Dim As Ulong Ptr pixel
Dim As Integer ddx,ddy,count
Imageinfo im,ddx,ddy,,pitch,row
For y As Long=0 To ddy-1
For x As Long=0 To ddx-1
pixel=row+pitch*(y)+(x) Shl 2
(c)=*pixel
if c<>bc then
count+=1
Redim Preserve w(1 To count)
Select Case As Const T
Case 1 :w(count)=Type(x,y,0,c) 'front
Case 2 :w(count)=Type(y,0,x,c)'top
Case 3 :w(count)=Type(y,x,ddy,c)'back
Case 4 :w(count)=Type(x,ddy,y,c)'base
Case 5 :w(count)=Type(ddx,y,x,c)'r side
Case 6 :w(count)=Type(0,x,y,c)'l side
End Select
end if
If x=ddx\2 Andalso y=ddy\2 Then ctr=w(count):ctr.c=count
Next x
Next y
End Sub

Function Rotate(c As V3,p As V3,a As sincos,scale As _float=Type<_float>(1,1,1)) As V3
Dim As Single dx=p.x-c.x,dy=p.y-c.y,dz=p.z-c.z
Return Type<V3>((scale.x)*((a.cy*a.cz)*dx+(-a.cx*a.sz+a.sx*a.sy*a.cz)*dy+(a.sx*a.sz+a.cx*a.sy*a.cz)*dz)+c.x,_
(scale.y)*((a.cy*a.sz)*dx+(a.cx*a.cz+a.sx*a.sy*a.sz)*dy+(-a.sx*a.cz+a.cx*a.sy*a.sz)*dz)+c.y,_
(scale.z)*((-a.sy)*dx+(a.sx*a.cy)*dy+(a.cx*a.cy)*dz)+c.z,p.c)
End Function

Sub RotateImage(wa() As V3,angle As _float,Byref centroid As V3,ctr As V3,sc As Single=1.75,nflag As Long,n() As V3)
#define map(a,b,x,c,d) ((d)-(c))*((x)-(a))/((b)-(a))+(c)
Var s=sincos.construct(angle.x,angle.y,angle.z)
Var g=Rotate(Type(0,0,0),n(nflag),s)
Dim As Ubyte rd,gr,bl
Dim  As Single dx,dy,dz,w
Dim As Single SinAX=Sin(angle.x)
Dim As Single SinAY=Sin(angle.y)
Dim As Single SinAZ=Sin(angle.z)
Dim As Single CosAX=Cos(angle.x)
Dim As Single CosAY=Cos(angle.y)
Dim As Single CosAZ=Cos(angle.z)
Dim As V3 centre=Type(100,100,100) 'the centre of rotation (fulcrum)
Dim As V3 result
Dim As Single dp=.6*sc

For z As Long=Lbound(wa) To Ubound(wa)

dx=wa(z).x-centre.x
dy=wa(z).y-centre.y
dz=wa(z).z-centre.z

Result.x=sc*((Cosay*Cosaz)*dx+(-Cosax*Sinaz+Sinax*Sinay*Cosaz)*dy+(Sinax*Sinaz+Cosax*Sinay*Cosaz)*dz)+centre.x
result.y=sc*((Cosay*Sinaz)*dx+(Cosax*Cosaz+Sinax*Sinay*Sinaz)*dy+(-Sinax*Cosaz+Cosax*Sinay*Sinaz)*dz)+centre.y
result.z=sc*((-Sinay)*dx+(Sinax*Cosay)*dy+(Cosax*Cosay)*dz)+centre.z
If z=ctr.c Then  centroid=result':If flag=0 Then Exit Sub
'If flag Then
'this bit is to add perspective ================
w = 1 + (result.z/eyepoint.z)
result.x = (result.x-eyepoint.x)/w+eyepoint.x +300
result.y = (result.y-eyepoint.y)/w+eyepoint.y +200
result.z = (result.z-eyepoint.z)/w+eyepoint.z
rd= Cptr(Ubyte Ptr,@wa(z).c) 'shaders
gr= Cptr(Ubyte Ptr,@wa(z).c)
bl= Cptr(Ubyte Ptr,@wa(z).c)
rd=cp*rd:gr=cp*gr:bl=cp*bl
Line(result.x-dp,result.y-dp)-(result.x+dp,result.y+dp),Rgb(rd,gr,bl),bf
' End If
Next z
End Sub

'sort each image by .z centroid distance
Sub sort(array() As V3,painter() As Long)
For p1 As Long  = 1 To Ubound(array,1) - 1
For p2 As Long  = p1 + 1 To Ubound(array,1)
If array(p1).z<array(p2).z Then Swap painter(p1),painter(p2):Swap array(p1),array(p2)
Next p2
Next p1
End Sub

Function Regulate(Byval MyFps As Long,Byref fps As Long) As Long
Static As Double timervalue,_lastsleeptime,t3,frames
Var t=Timer
frames+=1
If (t-t3)>=1 Then t3=t:fps=frames:frames=0
Var sleeptime=_lastsleeptime+((1/myfps)-T+timervalue)*1000
If sleeptime<1 Then sleeptime=1
_lastsleeptime=sleeptime
timervalue=T
Return sleeptime
End Function

'==========================================================================

Screen 19,32,2
color ,rgb(0,0,50)
Dim As Any Ptr image(1 To 6)

Randomize 2
For n As Long=1 To 6
image(n)=Imagecreate(200,200)
Next n
'normals to each cube face
Dim As V3 norm(1 To 6)={(0,0,1),(0,1,0),(0,0,-1),(0,-1,0),(-1,0,0),(1,0,0)}

eyepoint=Type(100,100,800) 'behind image centres
'fill the images
Dim As String fnt="arial"
drawfont(image(1),60,20,"1",fnt,100,contrast(Rgb(0,255,255)))
drawfont(image(2),60,20,"3",fnt,100,contrast(Rgb(55,255,0)))
drawfont(image(3),60,20,"6",fnt,100,contrast(Rgb(155,100,255)))
drawfont(image(4),60,20,"4",fnt,100,contrast(Rgb(0,255,0)))
drawfont(image(5),60,20,"5",fnt,100,contrast(Rgb(255,00,0)))
drawfont(image(6),60,20,"2",fnt,100,contrast(Rgb(0,0,255)))
Dim As Ulong cc
For n As Long=1 To 6'borders
Select Case n
Case 1:cc=Rgb(0,255,255)
Case 2:cc=Rgb(255,255,0)
Case 3:cc=Rgb(255,255,255)
Case 4:cc=Rgb(0,255,0)
Case 5:cc=Rgb(255,00,0)
Case 6:cc=Rgb(0,0,255)
End Select
'frames
For k As Long=0 To 5
Line image(n),(0+k,0+k)-(199-k,199-k),cc,b
Next k
Next n

Redim As V3 w1(),w2(),w3(),w4(),w5(),w6()'main arrays

Dim As V3 centroid(1 To 6),ctr(1 To 6)

Dim As _float angle
Dim As Long fps
Dim As Long painter(1 To 6)
For n As Long=1 To 6:painter(n)=n:Next n
Screenset 1,0

angle.z=pi/2
angle.y=-pi/8

Do
'rotate
angle.x+=.05  :If angle.x>=2*pi Then angle.x=0
'angle.y+=.025 :If angle.y>=2*pi Then angle.y=0
'angle.z+=.015 :If angle.z>=2*pi Then angle.z=0

'sort by .z to reset painter and centroids
sort(centroid(),painter())

Cls
Drawfont(,20,20,"Framerate " &fps,"arial",20,Rgb(200,100,0),italic Or bold)
'draw in 3D order
For n As Long=1 To 6
Select Case As Const painter(n)
Case 1:RotateImage(w1(),angle,centroid(1),ctr(1),,1,norm())
Case 2:RotateImage(w2(),angle,centroid(2),ctr(2),,2,norm())
Case 3:RotateImage(w3(),angle,centroid(3),ctr(3),,3,norm())
Case 4:RotateImage(w4(),angle,centroid(4),ctr(4),,4,norm())
Case 5:RotateImage(w5(),angle,centroid(5),ctr(5),,5,norm())
Case 6:RotateImage(w6(),angle,centroid(6),ctr(6),,6,norm())
End Select
Next n

Flip
For n As Long=1 To 6:painter(n)=n:Next n 'reset the painter
Sleep regulate(30,fps),1
Loop Until Len(Inkey)
Sleep
For n As Long=1 To 6
Imagedestroy image(n)
Next n