3d without openGL

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

3d without openGL

Post by BasicCoder2 »

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.
https://www.youtube.com/watch?v=75o5pmeXUMo
Last edited by BasicCoder2 on Oct 08, 2017 5:52, edited 2 times in total.
paul doe
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: 3d without openGL

Post by paul doe »

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: 1640
Joined: Jun 04, 2005 9:51

Re: 3d without openGL

Post by dafhi »

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: 3906
Joined: Jan 01, 2009 7:03
Location: Australia

Re: 3d without openGL

Post by BasicCoder2 »

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
    read rocket.pt(i).x
    read rocket.pt(i).y
    read rocket.pt(i).z
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: 1640
Joined: Jun 04, 2005 9:51

Re: 3d without openGL

Post by dafhi »

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: 215
Joined: Dec 14, 2013 0:43

Re: 3d without openGL

Post by fatman2021 »

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
    read rocket.pt(i).x
    read rocket.pt(i).y
    read rocket.pt(i).z
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: 3906
Joined: Jan 01, 2009 7:03
Location: Australia

Re: 3d without openGL

Post by BasicCoder2 »

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: 3906
Joined: Jan 01, 2009 7:03
Location: Australia

Re: 3d without openGL

Post by 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.

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
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: 3d without openGL

Post by paul doe »

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 ... atrix.aspx

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

https://www.essentialmath.com/GDC2012/G ... ations.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
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: 3d without openGL

Post by paul doe »

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 )
		m_vertices.add( v )
	end sub
	
	sub shape.addEdge( byval e as shapeEdge ptr )
		m_edges.add( e )
	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 )
		al.add( t )
		
		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: 555
Joined: Apr 19, 2006 10:55
Location: Kissimmee, FL
Contact:

Re: 3d without openGL

Post by owen »

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
	Read vertex3d(i).x
	Read vertex3d(i).y
	Read vertex3d(i).z
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: 3906
Joined: Jan 01, 2009 7:03
Location: Australia

Re: 3d without openGL

Post by BasicCoder2 »

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
Moderator
Posts: 1730
Joined: Jul 25, 2017 17:22
Location: Argentina

Re: 3d without openGL

Post by paul doe »

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: 555
Joined: Apr 19, 2006 10:55
Location: Kissimmee, FL
Contact:

Re: 3d without openGL

Post by owen »

@ 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: 7976
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: 3d without openGL

Post by dodicat »

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)[2],g=Cptr(Ubyte Ptr,@c)[1],b=Cptr(Ubyte Ptr,@c)[0],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[0],ubp[2]
    Dim As Ubyte alphaval =ubp[3]
    ubp[3]=0
    #define FontSize(PointSize) - MulDiv(PointSize, GetDeviceCaps(THEDC, LOGPIXELSY), 72)
    
    If FINIT = 0 Then  
        FINIT = 1  
        With MYBMPINFO.bmiheader
            .biSize = Sizeof(BITMAPINFOHEADER)
            .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)
    Var cp=map(-1,1,g.z,.2,1)'shader factor
    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)[2] 'shaders
            gr= Cptr(Ubyte Ptr,@wa(z).c)[1] 
            bl= Cptr(Ubyte Ptr,@wa(z).c)[0]
            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)
load(image(1),w1(),ctr(1),1)
load(image(2),w2(),ctr(2),2)
load(image(3),w3(),ctr(3),3)
load(image(4),w4(),ctr(4),4)
load(image(5),w5(),ctr(5),5)
load(image(6),w6(),ctr(6),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
        
        
           
Post Reply