3d without openGL

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

3d without openGL

Postby BasicCoder2 » Oct 08, 2017 4:57

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

Re: 3d without openGL

Postby paul doe » Oct 08, 2017 5:12

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

Re: 3d without openGL

Postby dafhi » Oct 08, 2017 6:16

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: 3397
Joined: Jan 01, 2009 7:03

Re: 3d without openGL

Postby BasicCoder2 » Oct 08, 2017 6:35

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

Re: 3d without openGL

Postby dafhi » Oct 08, 2017 7:22

i'm kinda busy, but check this out

Code: Select all

type p3d
    as single x,y,z
End Type

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


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


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

screenres w,h

dim as point3d  p

p.ori += type(midx, midy)

pset (p.x, p.y)

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

Re: 3d without openGL

Postby fatman2021 » Oct 08, 2017 13:55

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: 3397
Joined: Jan 01, 2009 7:03

Re: 3d without openGL

Postby BasicCoder2 » Oct 09, 2017 20:28

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: 3397
Joined: Jan 01, 2009 7:03

Re: 3d without openGL

Postby BasicCoder2 » Oct 09, 2017 20:30

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

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

Code: Select all

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

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

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

'Tweaked to compile with FreeBASIC by BasicCoder2


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

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

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

CONST PI = 3.14151693

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

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


CLS
SCREEN 0
WIDTH 80


'Initialize 3d model

LoadCube Model(), CubePoly(), 1

dim as integer RendMode = 0

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

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

CLS
SCREEN 13
RANDOMIZE TIMER

camx = 0
camy = 0
camz = 0

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

dim as integer Finished = 0

DO

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

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

LOOP UNTIL MULTIKEY(&H01)


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

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

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

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



END SUB

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



END SUB

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




END SUB

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

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

END SUB

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

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

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

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

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

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

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

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

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

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

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

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

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

END SUB

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

Re: 3d without openGL

Postby paul doe » Oct 10, 2017 1:22

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

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

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

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

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

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

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

Re: 3d without openGL

Postby paul doe » Oct 10, 2017 3:25

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

Re: 3d without openGL

Postby owen » Oct 10, 2017 6:16

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: 3397
Joined: Jan 01, 2009 7:03

Re: 3d without openGL

Postby BasicCoder2 » Oct 10, 2017 6:55

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

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

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

Re: 3d without openGL

Postby paul doe » Oct 10, 2017 17:19

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

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

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

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

grid.bi

Code: Select all

#ifndef __grid__
   #define __grid__
   
   #include once "core.bi"
   #include once "vec2h.bi"
   #include once "mat3.bi"
   
   /'
      A bounding box (or bounding rectangle) object
      
      this is useful for clipping of objects, geometry, and collision detection, to
      name a few applications.
      The grid object derives from here, and also provides spatial partition functionality
      besides the bounding functionality
   '/
   class boundingBox extends object
      protected:
         as mat3 m_transform = any
         as mat3 m_invTransform = any
         
         as double m_width = any
         as double m_height = any
         
      public:
         declare constructor()
         declare constructor( byval w as double, byval h as double )
               
         declare property transform() as mat3
         declare property transform( byval M as mat3 )
         
         declare property invTransform() as mat3
         
         declare virtual property width() as double
         declare virtual property width( byval value as double )
         
         declare virtual property height() as double
         declare virtual property height( byval value as double )
         
         declare function isInside( byval p as vec2h ) as boolean
         declare function getPoint( byval p as vec2h ) as vec2h
         
         declare virtual sub render( byval c as uint32 = rgba( 222, 222, 222, 255 ) )
   end class

   constructor boundingBox()
      m_width = 100.0
      m_height = 100.0
      
      m_transform = matrices.identity2h()
      m_invTransform = inverse( m_transform )
   end constructor
   
   constructor boundingBox( byval w as double, byval h as double )
      m_width = iif( w > 0, w, 100.0 )
      m_height = iif( h > 0, h, 100.0 )
      
      m_transform = matrices.identity2h()
      m_invTransform = inverse( m_transform )
      
   end constructor
   
   property boundingBox.transform() as mat3
      return( m_transform )
   end property
   
   property boundingBox.transform( byval M as mat3 )
      m_transform = M
      '' update also the inverse matrix of the transformation
      m_invTransform = inverse( m_transform )
   end property
   
   property boundingBox.invTransform() as mat3
      return( m_invTransform )
   end property
   
   property boundingBox.width() as double
      return( m_width )
   end property
   
   property boundingBox.width( byval value as double )
      m_width = value
   end property
   
   property boundingBox.height() as double
      return( m_height )
   end property
   
   property boundingBox.height( byval value as double )
      m_height = value
   end property
   
   function boundingBox.getPoint( byval p as vec2h ) as vec2h
      '' returns the point in bounding box coordinates
      return( m_invTransform * p )
   end function
   
   function boundingBox.isInside( byval p as vec2h ) as boolean
      /'
         this function returns true if a point p lies inside the bounding box
              
         0,0 +-------+
             |       |
             |   p   |
             |       |
             +-------+
                       m_width, m_height
         
         it works transforming the point (which must be specified in global coordinates)
         to the coordinate system of the bounding box, by multiplying the point with the
         inverse transformation matrix of the bounding box.
         After the transformation, the test is straightforward.
      '/
      dim as vec2h tp = getPoint( p )
      
      return( iif( tp.x >= 0 andAlso tp.x <= m_width - 1 andAlso tp.y >= 0 andAlso tp.y <= m_height - 1, true, false ) )
   end function
   
   sub boundingBox.render( byval c as uint32 = rgba( 222, 222, 222, 255 ) )
      '' renders the bounding box (mostly for debug purposes)
      
      /'
            p0 +-----+ p3
               |     |
               |     |
               |     |
            p1 +-----+ p2
      '/
      '' transform the origin and derive the rest of the points from there         
      dim as vec2h p0 = m_transform * vec2h( 0.0, 0.0 )
      dim as vec2h p1 = m_transform * vec2h( 0.0, m_height )
      dim as vec2h p2 = m_transform * vec2h( m_width, m_height )
      dim as vec2h p3 = m_transform * vec2h( m_width, 0.0 )
            
      line( p0.x, p0.y ) - ( p1.x, p1.y ), c
      line( p1.x, p1.y ) - ( p2.x, p2.y ), c
      line( p2.x, p2.y ) - ( p3.x, p3.y ), c
      line( p3.x, p3.y ) - ( p0.x, p0.y ), c
      
      dim as vec2h center = vec2h( p0.x + m_width / 2, p0.y + m_height / 2 )
      
      circle( center.x, center.y ), 6, rgba( 255, 0, 255, 255 ), , , , f
   end sub

   /'
      grid object
      
      A grid object that can be used as an acceleration structure
      Derives from boundingBox
   '/
   class grid extends boundingBox
      protected:
         as double m_cellWidth
         as double m_cellHeight
         as integer m_gridXSize
         as integer m_gridYSize
      
      public:
         declare constructor()
         declare constructor( byval w as double, byval h as double, byval xSize as integer, byval ySize as integer )
         declare destructor()
   
         declare property width() as double
         declare property width( byval value as double )
         
         declare property height() as double
         declare property height( byval value as double )
         
         declare property gridXSize() as integer
         declare property gridXSize( byval value as integer )
         
         declare property gridYSize() as integer
         declare property gridYSize( byval value as integer )
         
         declare property cellWidth() as double
         declare property cellHeight() as double
   
         declare function getCell( byval p as vec2h ) as vec2h
         
         declare sub render( byval c as uint32 = rgba( 200, 200, 200, 255 ) )         
   end class
      
   constructor grid( byval w as double, byval h as double, byval xSize as integer, byval ySize as integer )      
      '' call the base constructor to initialize
      base.constructor( w, h )
         
      m_gridXSize = iif( xSize > 0, xSize, 1 )
      m_gridYSize = iif( ySize > 0, ySize, 1 )      
      m_cellWidth = m_width / m_gridXSize
      m_cellHeight = m_height / m_gridYSize
   end constructor
   
   destructor grid()
   
   end destructor
   
   property grid.cellWidth() as double
      return( m_cellWidth )
   end property
   
   property grid.cellHeight() as double
      return( m_cellHeight )
   end property
   
   property grid.width() as double
      return( m_width )
   end property
   
   property grid.width( byval value as double )
      m_width = value
      m_cellWidth = m_width / m_gridXSize   
   end property
   
   property grid.height() as double
      return( m_height )
   end property
   
   property grid.height( byval value as double )
      m_height = value
      m_cellHeight = m_height / m_gridYSize
   end property
   
   property grid.gridXSize() as integer
      return( m_gridXSize )
   end property
   
   property grid.gridXSize( byval value as integer )
      m_gridXSize = value
      m_cellWidth = m_width / m_gridXSize   
   end property
   
   property grid.gridYSize() as integer
      return( m_gridYSize )
   end property
   
   property grid.gridYSize( byval value as integer )
      m_gridYSize = value
      m_cellHeight = m_height / m_gridYSize
   end property
   
   function grid.getCell( byval p as vec2h ) as vec2h
      '' returns the cell in the grid this point lies on
      '' assumes the point is already in grid space coordinates
      return( vec2h( p.x / m_cellWidth, p.y / m_cellHeight ) )   
   end function

   sub grid.render( byval c as uint32 = rgba( 200, 200, 200, 255 ) )
      /'
         get the 4 corners of the grid
         
         p0-----p3
         |       |
         |       |
         p1-----p2
      '/      
      '' transform the grid corners to grid space
      dim as vec2h p0 = m_transform * vec2h( 0.0, 0.0 )
      dim as vec2h p1 = m_transform * vec2h( 0.0, m_height )
      dim as vec2h p2 = m_transform * vec2h( m_width, m_height )
      dim as vec2h p3 = m_transform * vec2h( m_width, 0.0 )
      
      '' draw the borders
      line( p0.x, p0.y ) - ( p1.x, p1.y ), c
      line( p1.x, p1.y ) - ( p2.x, p2.y ), c
      line( p2.x, p2.y ) - ( p3.x, p3.y ), c
      line( p3.x, p3.y ) - ( p0.x, p0.y ), c
      
      '' horizontal lines
      for y as double = 0 to 1 step 1 / m_gridYSize
         dim as vec2h st = interpolate( p0, p1, y )
         dim as vec2h en = interpolate( p3, p2, y )
         
         line( st.x, st.y ) - ( en.x, en.y ), c
      next
      
      '' vertical lines
      for x as double = 0 to 1 step 1 / m_gridXSize
         dim as vec2h st = interpolate( p0, p3, x )
         dim as vec2h en = interpolate( p1, p2, x )
         
         line( st.x, st.y ) - ( en.x, en.y ), c
      next      
   end sub
#endif


grid test.bas

Code: Select all

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

/'
   grid debug program
   
   conventions: same as the previous code
'/

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

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

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

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

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

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

dim as string k
dim as integer mx, my

do
   getMouse( mx, my )
   mp = vec2h( mx, my )
   
   k = lcase( inkey() )
      
   '' translate the grid to its position
   dim as mat3 T = matrices.translation2h( gridPos.x, gridPos.y )
   '' rotation about the center of the grid
   dim as mat3 R = matrices.rotationAxis2h( vec2h( g.width / 2, g.height / 2 ), ang )
   '' shear the grid a little to deform it
   dim as mat3 S = matrices.shear2h( 25, 12 )
   
   '' transform the grid
   '' try to change the order of this, or to provide another matrix and see what happens
   g.transform = T * S * R
   
   '' transform the mouse pointer to grid space
   gp = g.getPoint( mp )
   
   '' get the mouse coordinates in grid cell space
   dim as vec2h cell = g.getCell( g.invTransform * vec2h( mp ) )
   
   screenLock()
      cls()
      
      '' render the grid      
      g.render( rgba( 64, 64, 64, 255 ) )
      
      /'
         this is the important bit of code that does the magic
         
         what it actually does, is computing which cell of the grid the user is pointing
         with the mouse. Note, however, that the grid can be grotesquely deformed, yet
         the cell is computed correctly. And, as you can see, is in fact treated like it
         was a simple, untransformed, rectangle on the screen
      '/
      
      '' check if the mouse pointer is inside the grid
      if( cell.x >= 0 and cell.x <= g.gridXSize and cell.y >= 0 and cell.y <= g.gridYSize ) then
         /'
            if it is, draw the cell the mouse is pointing to
            
            the points are winded like this:
            
            p0 +--------+ p3
               |        |
               |        |
            p1 +--------+ p2
            
            which is to say, are computed in counter-clockwise order
         '/
         
         '' top left
         dim as vec2h p0 = g.transform * _
            vec2h( int( cell.x ) * g.cellWidth, int( cell.y ) * g.cellHeight )
         
         '' bottom left
         dim as vec2h p1 = g.transform * _
            vec2h( int( cell.x ) * g.cellWidth, int( cell.y ) * g.cellHeight + g.cellHeight )
         
         '' bottom right
         dim as vec2h p2 = g.transform * _
            vec2h( int( cell.x ) * g.cellWidth + g.cellWidth, int( cell.y ) * g.cellHeight + g.cellHeight )
         
         '' top right
         dim as vec2h p3 = g.transform * _
            vec2h( int( cell.x ) * g.cellWidth + g.cellWidth, int( cell.y ) * g.cellHeight )
         
         '' draw the cell edges
         line( p0.x, p0.y ) - ( p1.x, p1.y ), rgba( 255, 255, 0, 255 )
         line( p1.x, p1.y ) - ( p2.x, p2.y ), rgba( 255, 255, 0, 255 )
         line( p2.x, p2.y ) - ( p3.x, p3.y ), rgba( 255, 255, 0, 255 )
         line( p3.x, p3.y ) - ( p0.x, p0.y ), rgba( 255, 255, 0, 255 )         
      end if
            
   screenUnlock()
   
   ang += 0.05
   
   if ang > 360.0 then
      ang -= 360.0
   end if
   
   sleep( 1 )
loop until k = chr( 27 )

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

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

Perfect timing. Thanks, owen.

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

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

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

Re: 3d without openGL

Postby owen » Oct 10, 2017 17:44

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

Re: 3d without openGL

Postby dodicat » Oct 10, 2017 18:45

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
       
       
           

Return to “Projects”

Who is online

Users browsing this forum: No registered users and 1 guest