Tutorial: Software 3d and matrix manipulations.

Game development specific discussions.
podvornyak
Posts: 148
Joined: Nov 12, 2007 11:46
Location: Russia

Tutorial: Software 3d and matrix manipulations.

Postby podvornyak » Jun 05, 2013 7:36

Tutorial is about matrix multiplications and hardware independent graphic. If you have a questions or corrections, post them.

Code: Select all

' nblJIG 2013

' Esc - exit.
' W - move forward.
' S - move backward
' A - strafe left
' D - strafe right
' C - strafe down
' SPACE - strafe up
' Q - tilt left
' E - tilt right

' What is the matrix? Matrix is just an array. No more, no less.
' That array contain a specified values. What special about them?
' Nothing. That values is just scalars. What does it mean?
' Simple. Let say you have a value X = 2, and you want to scale that value
' ten times. So you just need to multiply X with ten. X*10=20
' In that case 10 is scalar. If that scalar will be -0.5, then X become
' X*-0.5 = -1. That is how matrixes work. Then why matrixes is so popular?
' It is cause of martix multiplication. Let's say you need move one million
' 3d points to specified angle in 3d environment. First you'll move each
' point around x and than around y to get result position.
' With matrixes it became one operation - vector with matrix multiplication.
' And not only rotate. Even move and scale in the same one operation.
' To do this you need to get the matrix containing all that transformations.
' To get that matrix you need multiply five matrixes - matrix of translation,
' matrix of transformation and three matrixes of rotation (x, y, z)
' If you think that is to complicate preparations, you can count by yourself,
' but when you get the final matrix you will change your mind.

' Let's define matrix now. Matrix is two dimensional array.

data 1, 0, 0, 0
data 0, 1, 0, 0
data 0, 0, 1, 0
data 0, 0, 0, 1

' That is an 4X4 matrix and it suit in most cases. With those values
' it is a SINGLE matrix. Single matrix do nothing. It is just like a clear page.
' In this code i use one dimensional array to store values.

' Allocate some storages.
dim as single ptr matrix_global = allocate(sizeof(single)*16)
dim as single ptr matrix_rotate_x = allocate(sizeof(single)*16)
dim as single ptr matrix_rotate_y = allocate(sizeof(single)*16)
dim as single ptr matrix_rotate_z = allocate(sizeof(single)*16)
dim as single ptr matrix_translate = allocate(sizeof(single)*16)
dim as single ptr matrix_buffer = allocate(sizeof(single)*16)
dim as single ptr matrix_result_one = allocate(sizeof(single)*16)
dim as single ptr matrix_result_two = allocate(sizeof(single)*16)

' Differend storages will contain differen values, so that why i've
' define eight storages. Global matrix will contain final matrix of
' all transformations. Rotate matrixes will contain sinus and cosinus
' values of rotation angles. Translate will contain translation values
' Last three matrixes will contain special defined transformation values
' and matrix multiplication results.

' All storages for now not even clean, so let's assign to them SINGLE
' matrix values from DATA block of code.

' Template variables of type
dim shared as integer i
dim shared as single value_single

' for each value of all storages
for i = 0 to 15

   ' Read value from data block of code,
   read value_single
   
   ' and assign.
   matrix_global    [i] = value_single
   matrix_rotate_x  [i] = value_single
   matrix_rotate_y  [i] = value_single
   matrix_rotate_z  [i] = value_single
   matrix_translate [i] = value_single
   matrix_buffer    [i] = value_single
next i

' To see how each matrix values should look i'll type them in comments

' Rotation x matrix

'   1 |     0  |      0  | 0
'   0 | cos(x) | -sin(x) | 0
'   0 | sin(x) |  cos(x) | 0
'   0 |     0  |      0  | 1

' Rotation y matrix

'    cos(y) | 0 | sin(y) | 0
'         0 | 1 |      0 | 0
'   -sin(y) | 0 | cos(y) | 0
'         0 | 0 |      0 | 1
'
' Rotation z matrix

'   cos(z) | -sin(z) | 0 | 0
'   sin(z) |  cos(z) | 0 | 0
'        0 |       0 | 1 | 0
'        0 |       0 | 0 | 1

' Translation matrix

'   1 | 0 | 0 | x
'   0 | 1 | 0 | y
'   0 | 0 | 1 | z
'   0 | 0 | 0 | 1
'
' That is how matrixes looks like. If you wish to know more about
' matrix dimensions and types you can search information over internet.
' In this tutorial i don't spot on other transformation matrixes, cause
' we don't use them.

' Well. For easy access to matrixes values i'll assign pointers
' to some of them.

' Defining names of pointers.
dim shared as single ptr cos_angle_x1, cos_angle_x2, _
                   cos_angle_y1, cos_angle_y2, _
                   cos_angle_z1, cos_angle_z2, _
                   sin_angle_x, sin_angle_x_minus, _
                   sin_angle_y, sin_angle_y_minus, _
                   sin_angle_z, sin_angle_z_minus, _
                   translate_x, translate_y, translate_z, _
                   camera_distance
                  
' Assign pointers to matrixes variables.
cos_angle_x1      = @matrix_rotate_x [5]
sin_angle_x_minus   = @matrix_rotate_x [6]
sin_angle_x      = @matrix_rotate_x [9]
cos_angle_x2      = @matrix_rotate_x [10]

cos_angle_y1      = @matrix_rotate_y [0]
sin_angle_y      = @matrix_rotate_y [2]
sin_angle_y_minus   = @matrix_rotate_y [8]
cos_angle_y2      = @matrix_rotate_y [10]

cos_angle_z1      = @matrix_rotate_z [0]
sin_angle_z_minus   = @matrix_rotate_z [1]
sin_angle_z      = @matrix_rotate_z [4]
cos_angle_z2      = @matrix_rotate_z [5]

translate_x = @matrix_translate [3]
translate_y = @matrix_translate [7]
translate_z = @matrix_translate [11]

camera_distance = @matrix_buffer [11]

' Now we need angles values to calculate trigonometric values.

' Angles of rotation variables.
dim shared as single angle_x, angle_y, angle_z

' Next step is functions.

' First is function that refill all matrixes with new values.

sub matrixes_refill

' Function assume nothing and return nothing.
' It calculate trigonometric values and assign them to matrixes.

   *cos_angle_x1      = cos(angle_x)
   *cos_angle_x2      = *cos_angle_x1
   *sin_angle_x      = sin(angle_x)
   *sin_angle_x_minus   = -(*sin_angle_x)

   *cos_angle_y1      = cos(angle_y)
   *cos_angle_y2      = *cos_angle_y1
   *sin_angle_y      = sin(angle_y)
   *sin_angle_y_minus   = -(*sin_angle_y)

   *cos_angle_z1      = cos(angle_z)
   *cos_angle_z2      = *cos_angle_z1
   *sin_angle_z      = sin(angle_z)
   *sin_angle_z_minus   = -(*sin_angle_z)

end sub

' Next, we need function that copy values of one matrix to another.

sub matrix_copy (a as single ptr, b as single ptr)

' Function assume two pointers to arrays and copy values from a to b

   for i =  0 to 15
         b[i] = a[i]
   next i
end sub

' And most important functions.

' Multiplication of two matrixes.
' Rule of multiplication is: "Multiply row with column". Lets make it.

sub multiply_matrix (a as single ptr, b as single ptr, c as single ptr)

' Function assume three pointers to arrays, multiply values from
' b array to value from a array and put the result in c array.

   c[0] = a[0]*b[0]   + a[1]*b[4]  + a[2]*b[8]   + a[3]*b[12]
   c[1] = a[0]*b[1]   + a[1]*b[5]  + a[2]*b[9]   + a[3]*b[13]
   c[2] = a[0]*b[2]   + a[1]*b[6]  + a[2]*b[10]  + a[3]*b[14]
   c[3] = a[0]*b[3]   + a[1]*b[7]  + a[2]*b[11]  + a[3]*b[15]
   
   c[4] = a[4]*b[0]   + a[5]*b[4]  + a[6]*b[8]   + a[7]*b[12]
   c[5] = a[4]*b[1]   + a[5]*b[5]  + a[6]*b[9]   + a[7]*b[13]
   c[6] = a[4]*b[2]   + a[5]*b[6]  + a[6]*b[10]  + a[7]*b[14]
   c[7] = a[4]*b[3]   + a[5]*b[7]  + a[6]*b[11]  + a[7]*b[15]
   
   c[8] = a[8]*b[0]   + a[9]*b[4]  + a[10]*b[8]  + a[11]*b[12]
   c[9] = a[8]*b[1]   + a[9]*b[5]  + a[10]*b[9]  + a[11]*b[13]
   c[10] = a[8]*b[2]  + a[9]*b[6]  + a[10]*b[10] + a[11]*b[14]
   c[11] = a[8]*b[3]  + a[9]*b[7]  + a[10]*b[11] + a[11]*b[15]

   c[12] = a[12]*b[0] + a[13]*b[4] + a[14]*b[8]  + a[15]*b[12]
   c[13] = a[12]*b[1] + a[13]*b[5] + a[14]*b[9]  + a[15]*b[13]
   c[14] = a[12]*b[2] + a[13]*b[6] + a[14]*b[10] + a[15]*b[14]
   c[15] = a[12]*b[3] + a[13]*b[7] + a[14]*b[11] + a[15]*b[15]

' You can use iteration cycles for this function, but it will
' significantly reduce calculation speed.
end sub

' Multiplication matrix with vector.
' Same rule, except - result is vector.

sub multiply_vector (a as single ptr, b as single ptr, c as single ptr)

' Function assume one matrix array pointer, two vector array pointers,
' multiply values from b array to value from a array
' and put the result in c array.

   c[0] = a[0]*b[0]  + a[1]*b[1]  + a[2]*b[2]  + a[3]*b[3]
   c[1] = a[4]*b[0]  + a[5]*b[1]  + a[6]*b[2]  + a[7]*b[3]
   c[2] = a[8]*b[0]  + a[9]*b[1]  + a[10]*b[2] + a[11]*b[3]
   c[3] = a[12]*b[0] + a[13]*b[1] + a[14]*b[2] + a[15]*b[3]

end sub

' What next? We need to get vectors. Vectors? Yes - them.
' But where? Simple - load obj file or generate array of 3d-points. Yes -
' x, y and z values of point is a vector. Null vector actually. And those we
' will transform with matrixes - 3d geometry. But our vector suppose to be
' 4-value point. The value of the 4-th variable must be 1. That is it. So let's load
' some geometry. You can use your own *.obj file, or download file from
' post link.

' Type vector contain four values x, y, z, 1
type TYPE_VECTOR
   as single position(3) => {0,0,0,1}
end type

' Resizable array of vector type variables.
redim shared as TYPE_VECTOR vertex(0)

' function to load *.obj files
sub load_obj_file(filename as string, _
                offset_x as single = 0, offset_y as single = 0, offset_z as single = 0)
       
      dim as integer file, dimension, lenght, i
      dim as string buffer, value, symbol

      file = freefile
      open filename for binary access read as #file

      if (file<>0) then
      while not eof(file)
         line input #file, buffer
         lenght = len(buffer)
         select case mid(buffer, 1, 2)
            case "v "
               dimension = 0
               for i = lenght to 2 step -1
                  symbol = mid(buffer, i, 1)
                  if symbol <> " " then
                     value = symbol + value
                  else
      if dimension = 0 then vertex(ubound(vertex)).position(dimension) = val(value)+offset_x
      if dimension = 1 then vertex(ubound(vertex)).position(dimension) = val(value)+offset_y
      if dimension = 2 then vertex(ubound(vertex)).position(dimension) = val(value)+offset_z
      dimension += 1
      value = ""
                  end if
               next i
      redim preserve as TYPE_VECTOR vertex(lbound(vertex) to ubound(vertex)+1)
            end select
         wend
      end if
   close #file
end sub

' You can assign offset to loaded geometry by adding three values after
' filename argument, x, y, z
load_obj_file "file.obj", , , -900
' Also you can load more than one file. Call function again, with new arguments.
' load_obj_file "file.obj"

' Now we put all together.

' Window size and bit depth.
dim as integer screen_width=1024, screen_height=768, bit_depth = 32

' Position of pixel on screen, mouse position and event check for mouse and keyboard.
dim as integer screen_x, screen_y, mouse_x, mouse_y, event_check = 0

' Middle screen position.
dim as integer screen_width_half = screen_width/2, _
            screen_height_half = screen_height/2

' Fps calculation variables.
dim as double time_last, time_current, fps_count, fps_value


' Get current system time.
time_last = timer

' Start window.
screenres screen_width, screen_height, bit_depth, 0, 0

' Set mouse in the middle of the window and hide system cursor.
' On start, if cursor is out of starting window borders the values
' of position is unpredictable. I'll Fix it with ScreenEvent function
' at final stage. Just move mouse untill you see geometry.
setmouse screen_width_half, screen_height_half, 0

' Name window.
windowtitle "matrix-shmatrix-mathematics"

' Define pointer and assign it to back buffer.
dim as ubyte ptr frame_buffer = screenptr

' Start main cycle
while not multikey(&h01) ' Esc key for exit
   
   ' get current time
   time_current=timer
   ' increment fps counter
   fps_count += 1
   
   'check - is one second passed by?
   if time_current > time_last + 1 then
   
      ' store current time,
      time_last = time_current
      ' store actual fps
      fps_value = fps_count
      ' drop down counter to zero
      fps_count = 0
   end if

   ' clean delta values
   *translate_x = 0
   *translate_y = 0
   *translate_z = 0
   angle_x = 0
   angle_y = 0
   angle_z = 0

   ' get mouse current position values
   getmouse (mouse_x, mouse_y)
   
   ' if mouse position values not equal to middle of window position
   if mouse_x<>screen_width_half or mouse_y<>screen_height_half then
   
      ' store delta values. X mouse movment is rotation around Y aixs. Same for Y movement.
      angle_y = (screen_width_half - mouse_x)/500
      angle_x = (screen_height_half - mouse_y)/500
      
      ' set back mouse in the middle of the window
      setmouse(screen_width_half, screen_height_half)
      
      ' check event on
      event_check = 1
   end if

   ' if key is pressed - store delta value and check event on
   if multikey(&h1e) then *translate_x = 0.1:  event_check = 1: end if ' a
   if multikey(&h20) then *translate_x = -0.1: event_check = 1: end if ' d
   if multikey(&h2c) then                      event_check = 1: end if ' z
   if multikey(&h2d) then                      event_check = 1: end if ' x
   if multikey(&h2e) then *translate_y = 0.1:  event_check = 1: end if ' c
   if multikey(&h39) then *translate_y = -0.1: event_check = 1: end if ' space
   if multikey(&h11) then *translate_z = -0.1: event_check = 1: end if ' w
   if multikey(&h1f) then *translate_z = 0.1:  event_check = 1: end if ' s
   if multikey(&h10) then angle_z = -0.004:    event_check = 1: end if ' q
   if multikey(&h12) then angle_z = 0.004:     event_check = 1: end if ' e

   ' event check is on?
   if event_check then

      ' refill matrixes with new delta values
      matrixes_refill()
      
' Before start multiplying matrixes, assign negative translation value -1000
' to buffer matrix. It is necessary cause we did not create any camera and
' all transformations going around projection plane. We can
' say that middle of projection plane is camera point of view (POV).
' With assigning negative distance value we telling that, actually
' camera is behind projection plane and transformations should happen
' around camera position, not around camera POV. If you need more clues
' about projection plane, let me know.

      *camera_distance=-1000 ' matrix_buffer

' Now we start multiply matrixes. We got matrix with first transformation -
' translation of camera position. Also, after refilling other matrixes
' with new delta values we can multiply them to our first transformation.
' Lets do this.
      
      ' multiplying buffer matrix with translation matrix, adding more
      ' translations to our transformations.
      multiply_matrix (matrix_buffer, matrix_translate, matrix_result_one)
      
      ' multiplying result matrix with rotation matrixes.
      ' watchout name of variables. i'm crossing it to evade copying values
      multiply_matrix (matrix_result_one, matrix_rotate_x, matrix_result_two)
      multiply_matrix (matrix_result_two, matrix_rotate_y, matrix_result_one)
      multiply_matrix (matrix_result_one, matrix_rotate_z, matrix_result_two)
      
      ' All transvormations is multiplyed to matrix. Now we can move back
      ' camera position in positive dirrection.
      *camera_distance=1000 ' matrix_buffer
      
      ' and add this transformation to matrix
      ' multiplying last result to buffer matrix
      multiply_matrix ( matrix_result_two, matrix_buffer, matrix_result_one)
      
      ' and last multiplication
      ' multiply transformations that already happen before, stored in global matrix.
      ' Yes - at the first itteration of cycle that matrix is clean, but if you look to next string
      ' of code, you'll see that we use matrix_copy function and it argumented with global
      ' matrix. On each cycle iteration we just adding some delta transformations to global matrix.
      multiply_matrix ( matrix_result_one, matrix_global, matrix_result_two)
      
      ' copying result values to global matrix
      matrix_copy ( matrix_result_two, matrix_global)
      
      'off event check
      event_check = 0
      
   end if

' Ok. We got global transformation matrix and next step is transform
' geometry with that and draw it on screen.

   screenlock
   cls

   ' Cause we use UBYTE pointer to back buffer we must assign color
   ' values to each channel of RGBA except Alpha channel.
   
   ' To spot how do this easy i've put defination of temporal pointer
   ' to back buffer memory in here, but better you move it outside
   ' main cycle. Also i'll define temporal storage to hold transformed
   ' values of one vertex, and that defination must be moved outside
   ' cycle to.

   ' This is a temporal variable to hold one vertex values.
   dim as single cursor(3) => {0,0,0,1}
   ' defining pointer to current memory position
   dim as ubyte ptr tmp
   
   ' Now we go throug all vertexes of geometry, deform them with
   ' multiply_vector function, calculate projection position of
   ' pixel on screen and add color to that pixel.
   for i = 0 to ubound(vertex)-1
   
      ' multiplying vector (vertex of geometry) with global matrix
      multiply_vector (matrix_global, @vertex(i).position(0), @cursor(0))
      ' we got position of vertex after transition and rotation
      ' stored in cursor() array.
      
      ' calculating screen pixel position
      screen_x = screen_width_half  + cursor(0)*1000/(cursor(2)+1000)
      screen_y = screen_height_half - cursor(1)*1000/(cursor(2)+1000)
      
      ' That is it - we know pixel position on screen. Last thing
      ' is add color value to this pixel.
      
      ' Check  - is pixel fit the window size and point is in front of camera?
      if ((screen_x < screen_width and screen_x > -1) and _
                    (screen_y < screen_height and screen_y> -1)) and _
                     cursor(2)>-1000 then
         
         ' we using 32 bit color (24 bit acrually, last one is alpha
         ' channel). It mean that for color of each pixel on screen,
         ' four bytes allocated in frame buffer - Red, Green, Blue
         ' and Alpha. Alpha is the last byte, but other is going in
         ' back order - BGRA. Why is it? I Don't know. Anyway - you
         ' can use uinteger pointer to back buffer and assign all
         ' bytes at once with RGBA(r,g,b,a) function. But it would be
         ' harder to compare color value of pixel from buffer with
         ' your value-to-assign. It may become more complicated.
         
         ' store memory adress of first byte of color value.
         tmp = frame_buffer + screen_y*screen_width*4 + screen_x*4
         
         ' blue component is high enough?
         if *tmp < 195 then
            ' increment blue value
            *tmp += 60
         end if
         
         ' increment pointer
         tmp += 1
         
         ' green component is high enough?
         if *tmp < 225 then
            ' increment green value
            *tmp += 30
         end if
         
         ' increment pointer
         tmp +=1
         
         ' red component is high enough?
         if *tmp < 245 then
            ' increment red value
            *tmp += 10
         end if
      end if
   next i
   
   ' Displaying fps and amount of vertexes.
   draw string (0,10), "fps: "+ str(fps_value)
   draw string (0,20), "vertexes: "+ str(ubound(vertex)-1)
   screenunlock
   sleep 1
wend
deallocate matrix_global
deallocate matrix_rotate_x
deallocate matrix_rotate_y
deallocate matrix_rotate_z
deallocate matrix_translate
deallocate matrix_buffer
deallocate matrix_result_one
deallocate matrix_result_two
' That is all. Hope you find this tutorial interesting... at least.
end


FILE.OBJ file 200k points 5.7 mb
http://yadi.sk/d/3VYOz7HL5lnyT

If you interested in another software 3d tutorial, about drawing polygons, let me know.
Last edited by podvornyak on Sep 06, 2013 0:05, edited 20 times in total.
petan
Posts: 683
Joined: Feb 16, 2010 15:34
Location: Europe
Contact:

Re: Tutorial: Software 3d and matrix manipulations.

Postby petan » Jun 05, 2013 10:15

Seeing inverted motions - my mouse <-> blue cloud on monitor.
e.g.: Mouse goes down, but cloud goes up.
podvornyak
Posts: 148
Joined: Nov 12, 2007 11:46
Location: Russia

Re: Tutorial: Software 3d and matrix manipulations.

Postby podvornyak » Jun 05, 2013 11:08

petan wrote:...


Blue cloud? Try to fly closer with W key or farther with S key. Yes - it is a free camera - if you move mouse down the view move up... like when you turn your head...
Last edited by podvornyak on Jun 13, 2013 9:56, edited 1 time in total.
petan
Posts: 683
Joined: Feb 16, 2010 15:34
Location: Europe
Contact:

Re: Tutorial: Software 3d and matrix manipulations.

Postby petan » Jun 05, 2013 11:58

Aha, in such manner thinked, understanding, thx.
For my last missing thing (4D visualisation on really huge data) I am laboring on independent camera features - motion&view, t.m. camera moving in one direction and I can change among front-back-left-right-up-down cameraview (like in fly-fight-combat games).
FotonCat
Posts: 37
Joined: Nov 26, 2009 11:47
Location: Russia
Contact:

Re: Tutorial: Software 3d and matrix manipulations.

Postby FotonCat » Jun 12, 2013 6:11

Thank you very much for such easy to understand 3d matrix tutorial! It will be very useful for me to learn how matrixes work in the 3d software rendering, because I'm developing my own 3d software renderer currently. :) Good job!
dodicat
Posts: 5772
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: Tutorial: Software 3d and matrix manipulations.

Postby dodicat » Jun 13, 2013 0:41

Here's a simple Y axis rotate and some mouse movements and mousewheel using the obj file:

Code: Select all

type V3
    as single x,y,z
    Declare Function PointRotate(As v3,As v3,As v3=Type<v3>(1,1,1)) As v3
end type
#define vct type <V3>
#define onscreen xx>=0 and xx<xres and yy>=0 and yy<yres
#macro ppset(_x,_y,colour)
pixel=row+pitch*(_y)+4*(_x)
*pixel=(colour)
#endmacro
Operator + (v1 As v3,v2 As v3) As v3
Return Type<v3>(v1.x+v2.x,v1.y+v2.y,v1.z+v2.z)
End Operator
Operator -(v1 As v3,v2 As v3) As v3
Return Type<v3>(v1.x-v2.x,v1.y-v2.y,v1.z-v2.z)
End Operator
Operator * (f As Single,v1 As v3) As v3
Return vct(f*v1.x,f*v1.y,f*v1.z)
End Operator
Operator *(v1 As v3,f As Single) As v3
Return f*v1
End Operator

Function v3.PointRotate(_point As v3,Angle As v3,scale As v3=Type<v3>(1,1,1)) As v3
    Dim As v3 p=vct(This-_point)
    Dim As v3 rot,temp
    Dim As Single s=Sin(angle.x),c=Cos(angle.x)
    temp=vct((p.y)*C+(-p.z)*S,(p.z)*C+(p.y)*S)
    rot.y=temp.x
    s=Sin(angle.y):c=Cos(angle.y)
    temp=vct((temp.y)*C+(-p.x)*S,(p.x)*C+(temp.y)*S)
    rot.z=temp.x
    s=Sin(angle.z):c=Cos(angle.z)
    temp=vct((temp.y)*C+(-rot.y)*S,(rot.y)*C+(temp.y)*S)
    rot.x=temp.x:rot.y=temp.y
    Return vct((scale.x*rot.x+_point.x),(scale.y*rot.y+_point.y),(scale.z*rot.z+_point.z))
End Function

redim shared as V3 vertex(0)

sub load_obj_file(filename as string, _
                offset_x as single = 0, offset_y as single = 0, offset_z as single = 0)
       
      dim as integer file, dimension, lenght, i
      dim as string buffer, value, symbol

      file = freefile
      open filename for binary access read as #file

      if (file<>0) then
      while not eof(file)
         line input #file, buffer
         lenght = len(buffer)
         select case mid(buffer, 1, 2)
            case "v "
               dimension = 0
               for i = lenght to 2 step -1
                  symbol = mid(buffer, i, 1)
                  if symbol <> " " then
                     value = symbol + value
                  else
      if dimension = 0 then vertex(ubound(vertex)).x = val(value)+offset_x
      if dimension = 1 then vertex(ubound(vertex)).y = val(value)+offset_y
      if dimension = 2 then vertex(ubound(vertex)).z = val(value)+offset_z
      dimension += 1
      value = ""
                  end if
               next i
      redim preserve as V3 vertex(lbound(vertex) to ubound(vertex)+1)
            end select
         wend
      end if
   close #file
end sub

sub blow(v() as V3,mag as double,d as V3)
    for n as integer=lbound(V) to ubound(V)
    V(n)=mag*V(n)+D
    next n
end sub

load_obj_file "file.obj", , , -900
Dim As Any Pointer row
Dim As Integer pitch,xres,yres
Dim As Uinteger Pointer pixel
Screen 20,32
row=Screenptr
Screeninfo xres,yres,,,pitch

blow(vertex(),10,vct(xres/2,yres/2-200,9000))

dim as integer cx=xres/2
dim as integer cy=yres/2
dim as V3 C=vct(cx,cy,0)
dim as V3 angle
dim as single pi=4*atn(1)
dim as integer mx,my,wheel
dim as V3 mouse,scale
do
    getmouse mx,my,wheel
    scale=vct(1+wheel/10,1+wheel/10,1+wheel/10)
    mouse=vct(mx,my,0)-C
    angle.z=-pi
    angle.y=angle.y+.1
    screenlock
    cls
    for z as integer=lbound(vertex) to ubound(vertex)
        var t=vertex(z).pointrotate(C,angle,scale)
        dim as integer xx=t.x+mouse.x,yy=t.y+mouse.y
       if onscreen then
        ppset(xx,yy,rgb(0,0,255))
        end if
        next z
    screenunlock
    sleep 1,1
    loop until len(inkey)
podvornyak
Posts: 148
Joined: Nov 12, 2007 11:46
Location: Russia

Re: Tutorial: Software 3d and matrix manipulations.

Postby podvornyak » Jun 13, 2013 6:57

dodicat wrote:...

What about it? Can't say that is clear... except it work three time slower.
dodicat
Posts: 5772
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: Tutorial: Software 3d and matrix manipulations.

Postby dodicat » Jun 13, 2013 12:16

It's not as fast as yours, only a simple alternative and a bit of a mess around.

Here it is again with some perspective added.
Now she is Port and Starboard handed.

Code: Select all

 
Type V3
    As Single x,y,z
    Declare Function PointRotate(As v3,As v3,As v3=Type<v3>(1,1,1)) As v3
End Type
#define map(a,b,x,c,d) ((d)-(c))*((x)-(a))/((b)-(a))+(c)
#define vct type <V3>
#define onscreen xx>=0 and xx<xres and yy>=0 and yy<yres
#macro ppset(_x,_y,colour)
pixel=row+pitch*(_y)+(_x)*4
*pixel=(colour)
#endmacro
Operator + (v1 As v3,v2 As v3) As v3
Return Type<v3>(v1.x+v2.x,v1.y+v2.y,v1.z+v2.z)
End Operator
Operator -(v1 As v3,v2 As v3) As v3
Return Type<v3>(v1.x-v2.x,v1.y-v2.y,v1.z-v2.z)
End Operator
Operator * (f As Single,v1 As v3) As v3
Return vct(f*v1.x,f*v1.y,f*v1.z)
End Operator
Operator *(v1 As v3,f As Single) As v3
Return f*v1
End Operator

Function v3.PointRotate(_point As v3,Angle As v3,scale As v3=Type<v3>(1,1,1)) As v3
    Dim As v3 p=vct(This-_point)
    Dim As v3 rot,temp
    Dim As Single s=Sin(angle.x),c=Cos(angle.x)
    temp=vct((p.y)*C+(-p.z)*S,(p.z)*C+(p.y)*S)
    rot.y=temp.x
    s=Sin(angle.y):c=Cos(angle.y)
    temp=vct((temp.y)*C+(-p.x)*S,(p.x)*C+(temp.y)*S)
    rot.z=temp.x
    s=Sin(angle.z):c=Cos(angle.z)
    temp=vct((temp.y)*C+(-rot.y)*S,(rot.y)*C+(temp.y)*S)
    rot.x=temp.x:rot.y=temp.y
    Return vct((scale.x*rot.x+_point.x),(scale.y*rot.y+_point.y),(scale.z*rot.z+_point.z))
End Function

Redim Shared As V3 vertex(0)

Sub load_obj_file(filename As String, _
    offset_x As Single = 0, offset_y As Single = 0, offset_z As Single = 0)
   
    Dim As Integer file, dimension, lenght, i
    Dim As String buffer, value, symbol
   
    file = Freefile
    Open filename For Binary Access Read As #file
   
    If (file<>0) Then
        While Not Eof(file)
            Line Input #file, buffer
            lenght = Len(buffer)
            Select Case Mid(buffer, 1, 2)
            Case "v "
                dimension = 0
                For i = lenght To 2 Step -1
                    symbol = Mid(buffer, i, 1)
                    If symbol <> " " Then
                        value = symbol + value
                    Else
                        If dimension = 0 Then vertex(Ubound(vertex)).x = Val(value)+offset_x
                        If dimension = 1 Then vertex(Ubound(vertex)).y = Val(value)+offset_y
                        If dimension = 2 Then vertex(Ubound(vertex)).z = Val(value)+offset_z
                        dimension += 1
                        value = ""
                    End If
                Next i
                Redim Preserve As V3 vertex(Lbound(vertex) To Ubound(vertex)+1)
            End Select
        Wend
    End If
    Close #file
End Sub



Sub blow(v() As V3,mag As Double,d As V3)
    For n As Integer=Lbound(V) To Ubound(V)
        V(n)=mag*V(n)+D
    Next n
End Sub

Function apply_perspective(p As V3,eyepoint As V3) As V3
    Dim As Single   w=1+(p.z/eyepoint.z)
    Return vct((p.x-eyepoint.x)/w+eyepoint.x,(p.y-eyepoint.y)/w+eyepoint.y,(p.z-eyepoint.z)/w+eyepoint.z)
End Function



load_obj_file "file.obj", , , -900

Dim As Any Pointer row
Dim As Integer pitch,xres,yres
Dim As Uinteger Pointer pixel
Screen 20,32
row=Screenptr
Screeninfo xres,yres,,,pitch

blow(vertex(),10,vct(xres/2,yres/2-200,9000))

Dim As Integer cx=xres/2
Dim As Integer cy=yres/2
Dim As V3 C=vct(cx,cy,0)
Dim As V3 angle
Dim As Single pi=4*Atn(1)
Dim As Integer mx,my,wheel
Dim As V3 mouse,scale
angle.z=-pi
Do
    Getmouse mx,my,wheel
    scale=vct(1+wheel/10,1+wheel/10,1+wheel/10)
    mouse=vct(mx,my,0)-C
    angle.y=angle.y+.1
    Screenlock
    Cls
    For z As Integer=Lbound(vertex) To Ubound(vertex)
        var t=vertex(z).pointrotate(C,angle,scale)
        t=apply_perspective(t,vct(cx,cy,500))
        Dim As Integer xx=t.x+mouse.x,yy=t.y+mouse.y
        dim as integer col=map(-900*scale.x,900*scale.x,t.z,250,10)
        If onscreen Then
            ppset(xx,yy,Rgb(col,255-col,.8*col))
        End If
    Next z
    Screenunlock
    Sleep 1,1
    Loop Until Len(Inkey)
podvornyak
Posts: 148
Joined: Nov 12, 2007 11:46
Location: Russia

Re: Tutorial: Software 3d and matrix manipulations.

Postby podvornyak » Jun 13, 2013 13:23

dodicat wrote:simple alternative
Ah... I see. Syntax depth always welcome. I gonna love it soon - reading uncommented code. No joke. It is part of the deal to become programer, i think... true programer. XD
dodicat
Posts: 5772
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: Tutorial: Software 3d and matrix manipulations.

Postby dodicat » Jun 13, 2013 14:35

Hi podvornyak.
I've made loads of rotators, but nothing really fast, so I'll take note of your 4 X 4 matrix method.

If you want perspective then you need another (perspective) matrix 4 X 4, and another Matrix multiply.
How would this affect your own speed, have you tried it?

Sorry about lack of code comments, I didn't really think it worthy, it's only fun code, nobody would be expected to use it, especially for a huge number of points, e.g. bitmaps e.t.c.
podvornyak
Posts: 148
Joined: Nov 12, 2007 11:46
Location: Russia

Re: Tutorial: Software 3d and matrix manipulations.

Postby podvornyak » Jun 14, 2013 13:35

...
Last edited by podvornyak on Jun 17, 2013 18:57, edited 11 times in total.
dodicat
Posts: 5772
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: Tutorial: Software 3d and matrix manipulations.

Postby dodicat » Jun 15, 2013 0:48

Yea Podvornyak, couldn't get it to run.
Look forward to your updated code.

Here's another type of 3D rotator, Rodrigues.

http://en.wikipedia.org/wiki/Rodrigues' ... on_formula

I've done a simple example with four points.
With this rotator you can use either a single axis to rotate round or you can define ANY three axis as the base space.

This type of thing is quite good at say viewing orbits with different axial tilts.

Probably for thousands of points it would be quite slow due to the vector computations.

I've set the three axis as normal x,y and z, but they can be changed.
This is not meant to be anything pictorial, but just runs through Rodrigues method.
The mouse moves the centre of rotation X and Y, the wheel for Z

Code: Select all

Setenviron("fbgfx=GDI")

Type v3
    As Single x,y,z
    As Uinteger colour
    Declare Property length As Single
    Declare Property unit As v3
    Declare Function AxialRotate(As v3,As Single,As v3) As v3
    Declare Function CentreRotate(As v3,As v3=Type<v3>(1,1,1)) As v3
    Declare Function perspective(eyepoint As v3) As v3
End Type
'short cuts to save typing
#define vct Type<v3>
#define dot *
#define cross ^
'The three axis
Dim Shared As V3  X_axis,Y_axis,Z_axis
'Some vector operators:
Operator + (v1 As v3,v2 As v3) As v3
Return Type<v3>(v1.x+v2.x,v1.y+v2.y,v1.z+v2.z)
End Operator

Operator -(v1 As v3,v2 As v3) As v3
Return Type<v3>(v1.x-v2.x,v1.y-v2.y,v1.z-v2.z)
End Operator

Operator * (f As Single,v1 As v3) As v3 'pre mult by scalar
Return vct(f*v1.x,f*v1.y,f*v1.z)
End Operator

Operator *(v1 As v3,f As Single) As v3  'post mult by scalar
Return f*v1
End Operator

Operator * (v1 As v3,v2 As v3) As Single 'dot product
Return v1.x*v2.x+v1.y*v2.y+v1.z*v2.z
End Operator

Operator ^ (v1 As v3,v2 As v3) As v3     'cross product
Return Type<v3>(v1.y*v2.z-v2.y*v1.z,-(v1.x*v2.z-v2.x*v1.z),v1.x*v2.y-v2.x*v1.y)
End Operator

Property v3.length As Single
Return Sqr(this.x*this.x+this.y*this.y+this.z*this.z)
End Property

' ~ normalize
Property v3.unit As v3
Dim n As Single=this.length
If n=0 Then n=1e-20
Return vct(this.x/n,this.y/n,this.z/n)
End Property


Function v3.perspective(eyepoint As v3) As v3
    Dim As Single   w=1+(this.z/eyepoint.z)
    If w=0 Then w=1e-20
    Var result=(This-eyepoint)*(1/w)+eyepoint
    result.colour=this.colour
    Return result
End Function

'Rotate about a given axis direction (Rodrigue method)
'norm in the function is passed as a unit vector (the axis)
Function v3.AxialRotate(centre As v3,Angle As Single,norm As v3) As v3
    Dim As v3 V=This-centre,result
    result=(V*Cos(Angle)+(Norm cross V)*Sin(Angle)+Norm*(Norm dot V)*(1-Cos(Angle)))+centre
    result.colour=this.colour
    Return result
End Function

'General rotate about  any three axis
'using the Axial rotate about the chosen axis
Function V3.CentreRotate(ctr As v3,Ang As v3) As V3
    Var t1=this.AxialRotate(ctr,Ang.x,x_axis)
    Var t2=t1.AxialRotate(ctr,Ang.y,y_axis)
    Return t2.AxialRotate(ctr,Ang.z,z_axis)
End Function
'================  End of Rotation necessities ========================

'FOR RUNNING EXAMPLE:

'Quicksort
Sub Qsort(array() As V3,begin As Integer,Finish As Uinteger)
    Dim As Integer i=begin,j=finish
    Dim As V3 x =array(((I+J)\2))
    While  I <= J
        While array(I).z > X.z
            I+=1
        Wend
        While array(J).z < X.z
            J-=1
        Wend
        If I<=J Then
            Swap array(I),array(J)
            I+=1
            J-=1
        End If
    Wend
    If J > begin Then Qsort(array(),begin,J)
    If I < Finish Then Qsort(array(),I,Finish)
End Sub

'speed regulators
Function framecounter() As Integer
    Var t1=Timer,t2=t1
    Static As Double t3,frames,answer
    frames=frames+1
    If (t2-t3)>=1 Then
        t3=t2
        answer=frames
        frames=0
    End If
    Return answer
End Function

Function regulate(MyFps As Integer,Byref fps As Integer) As Integer
    fps=framecounter
    Static As Double timervalue
    Static As Double delta,lastsleeptime,sleeptime
    Var k=1/myfps
    If Abs(fps-myfps)>1 Then
        If fps<Myfps Then delta=delta-k Else delta=delta+k
    End If
    sleeptime=lastsleeptime+((1/myfps)-(Timer-timervalue))*(2000)+delta
    If sleeptime<1 Then sleeptime=1
    lastsleeptime=sleeptime
    timervalue=Timer
    Return sleeptime
End Function
'============================================================
'define any three axis of rotation
'here, just the normal X,Y,Z
X_axis=vct(1,0,0)
Y_axis=vct(0,1,0)
Z_axis=vct(0,0,1)

'always normalize any axis (.unit), although here they are already done here

X_axis=X_Axis.unit
Y_axis=Y_Axis.unit
Z_axis=Z_axis.unit


'map used for radius on Z axis
#define map(a,b,x,c,d) ((d)-(c))*((x)-(a))/((b)-(a))+(c)

Dim As Integer xres,yres
Screen 20,32
Screeninfo xres,yres

Dim As v3 centre  'Centre of rotation
Dim As V3 ang     'angle of rotation
Dim As V3 ScreenEyepoint=(xres/2,yres/2,500) 'for the perspective

'set up 8 points to rotate
'That is the corners of a box
Redim  As V3 array()
Redim As V3 rotated()
Dim As V3 pts
For n As Integer=1 To 8
    Select Case n
    Case 1:pts=vct(-1, 1,-1)
    Case 2:pts=vct( 1, 1,-1)
    Case 3:pts=vct( 1,-1,-1)
    Case 4:pts=vct(-1,-1,-1)
    Case 5:pts=vct(-1, 1, 1)
    Case 6:pts=vct( 1, 1, 1)
    Case 7:pts=vct( 1,-1, 1)
    Case 8:pts=vct(-1,-1, 1)
    End Select
    Redim Preserve array(1 To Ubound(array)+1)
    'magnify and translate the points to suit screen
    pts=100*pts+vct(xres/2,yres/2,0)
    'vectors also have a colour field
    pts.colour=Rgb(Rnd*255,Rnd*255,Rnd*255)
    array(Ubound(array))=pts
Next n
'set up an array to hold all rotated points
Redim rotated(Lbound(array) To Ubound(array))

Dim As Integer mx,my,wheel
Dim As Integer FPS,sleeptime 'Speed calmers
Do
    sleeptime=regulate(50,FPS)
    Getmouse mx,my,wheel
    centre=vct(mx,my,wheel*10)
   
    'increment the angle of rotation
    ang=vct(ang.x+.01,ang.y+.02,ang.z+.03)
   
    'rotate all with perspective
    For n As Integer=Lbound(array) To Ubound(array)
        rotated(n)=array(n).CentreRotate(centre,ang)
        rotated(n)=rotated(n).perspective(ScreenEyePoint)
    Next n
   
    'sort to paint furthest first
    Qsort(rotated(),Lbound(rotated),Ubound(rotated))
   
    Screenlock
    Cls
    Draw String (20,20),"FPS = " & FPS
    Draw String(mx,my-70),"Centre of rotation"
    Draw String (mx,my-50),"(" &centre.x & "," & centre.y & "," & centre.z & ")"
   
    'draw to screen, radius depending on Z distance
    For n As Integer=Lbound(array) To Ubound(array)
        Var rad=map(200,-200,rotated(n).z,5,15)
        If rad<2 Then rad=2
        Circle(rotated(n).x,rotated(n).y),rad,rotated(n).colour ,,,,f
    Next n
   
    Screenunlock
    Sleep sleeptime,1
Loop Until Len(Inkey)
Sleep


 
podvornyak
Posts: 148
Joined: Nov 12, 2007 11:46
Location: Russia

Re: Tutorial: Software 3d and matrix manipulations.

Postby podvornyak » Jun 22, 2013 10:13

Polygon filling and z buffer.

engine_software.bi

Code: Select all

' nblJIG 2013

' If you dont understand matrix calculations mechanism, check code sample at first post.

' Data block contain SINGLE MATRIX values.
data 1, 0, 0, 0
data 0, 1, 0, 0
data 0, 0, 1, 0
data 0, 0, 0, 1

' System variables. Keyboard, mouse, time...
dim shared as integer screen_width = 1024, screen_height = 768, bit_depth = 32, _
               screen_width_half , screen_height_half, screen_size, _
               fps_count, fps_value

   screen_width_half = screen_width/2
   screen_height_half = screen_height/2
   screen_size = screen_width*screen_height

dim as double time_last, time_current, time_frame

dim as integer mouse_x, mouse_y, mouse_buttion, mouse_wheel, _
            mouse_focus, mouse_focus_last, event_check

dim as single move_shift
dim as byte face_visable

' Matrix storages
dim as single ptr matrix_global    = new single[16]
dim as single ptr matrix_rotate_x  = new single[16]
dim as single ptr matrix_rotate_y  = new single[16]
dim as single ptr matrix_rotate_z  = new single[16]
dim as single ptr matrix_translate = new single[16]
dim as single ptr matrix_buffer    = new single[16]
dim as single ptr matrix_result_1  = new single[16]
dim as single ptr matrix_result_2  = new single[16]

' Angle of rotation storages
dim shared as single angle_x, angle_y, angle_z

' Pointers to matrix storage values
dim shared as single ptr cos_angle_x1, cos_angle_x2, _
                   cos_angle_y1, cos_angle_y2, _
                   cos_angle_z1, cos_angle_z2, _
                   sin_angle_x, sin_angle_x_minus, _
                   sin_angle_y, sin_angle_y_minus, _
                   sin_angle_z, sin_angle_z_minus, _
                   translate_x, translate_y, translate_z, _
                   camera_distance

' Pointers assignation.
cos_angle_x1      = @matrix_rotate_x [5]
sin_angle_x_minus   = @matrix_rotate_x [6]
sin_angle_x         = @matrix_rotate_x [9]
cos_angle_x2      = @matrix_rotate_x [10]
cos_angle_y1      = @matrix_rotate_y [0]
sin_angle_y         = @matrix_rotate_y [2]
sin_angle_y_minus   = @matrix_rotate_y [8]
cos_angle_y2      = @matrix_rotate_y [10]
cos_angle_z1      = @matrix_rotate_z [0]
sin_angle_z_minus   = @matrix_rotate_z [1]
sin_angle_z         = @matrix_rotate_z [4]
cos_angle_z2      = @matrix_rotate_z [5]

translate_x = @matrix_translate  [3]
translate_y = @matrix_translate  [7]
translate_z = @matrix_translate  [11]

camera_distance = @matrix_buffer [11]

' Function to refill trigonometric values in matrix storages
sub matrix_refill(x as ubyte = 1, y as ubyte = 1, z as ubyte = 1)
   if x then
      *cos_angle_x1      = cos(angle_x)
      *cos_angle_x2      = *cos_angle_x1
      *sin_angle_x      = sin(angle_x)
      *sin_angle_x_minus   = -(*sin_angle_x)
   end if
   if y then
      *cos_angle_y1      = cos(angle_y)
      *cos_angle_y2      = *cos_angle_y1
      *sin_angle_y      = sin(angle_y)
      *sin_angle_y_minus   = -(*sin_angle_y)
   end if
   if z then
      *cos_angle_z1      = cos(angle_z)
      *cos_angle_z2      = *cos_angle_z1
      *sin_angle_z      = sin(angle_z)
      *sin_angle_z_minus   = -(*sin_angle_z)
   end if
end sub

' Function of matrix with matrix multiplying.
sub multiply_matrix(a as single ptr, b as single ptr, c as single ptr)
   c[0] = a[0]*b[0]   + a[1]*b[4]  + a[2]*b[8]   + a[3]*b[12]
   c[1] = a[0]*b[1]   + a[1]*b[5]  + a[2]*b[9]   + a[3]*b[13]
   c[2] = a[0]*b[2]   + a[1]*b[6]  + a[2]*b[10]  + a[3]*b[14]
   c[3] = a[0]*b[3]   + a[1]*b[7]  + a[2]*b[11]  + a[3]*b[15]
   
   c[4] = a[4]*b[0]   + a[5]*b[4]  + a[6]*b[8]   + a[7]*b[12]
   c[5] = a[4]*b[1]   + a[5]*b[5]  + a[6]*b[9]   + a[7]*b[13]
   c[6] = a[4]*b[2]   + a[5]*b[6]  + a[6]*b[10]  + a[7]*b[14]
   c[7] = a[4]*b[3]   + a[5]*b[7]  + a[6]*b[11]  + a[7]*b[15]
   
   c[8] = a[8]*b[0]   + a[9]*b[4]  + a[10]*b[8]  + a[11]*b[12]
   c[9] = a[8]*b[1]   + a[9]*b[5]  + a[10]*b[9]  + a[11]*b[13]
   c[10] = a[8]*b[2]  + a[9]*b[6]  + a[10]*b[10] + a[11]*b[14]
   c[11] = a[8]*b[3]  + a[9]*b[7]  + a[10]*b[11] + a[11]*b[15]

   c[12] = a[12]*b[0] + a[13]*b[4] + a[14]*b[8]  + a[15]*b[12]
   c[13] = a[12]*b[1] + a[13]*b[5] + a[14]*b[9]  + a[15]*b[13]
   c[14] = a[12]*b[2] + a[13]*b[6] + a[14]*b[10] + a[15]*b[14]
   c[15] = a[12]*b[3] + a[13]*b[7] + a[14]*b[11] + a[15]*b[15]
end sub

' Function of vector with matrix multiplying.
sub multiply_vector(a as single ptr, b as single ptr, c as single ptr)
   c[0] = a[0]*b[0]  + a[1]*b[1]  + a[2]*b[2]  + a[3]*b[3]
   c[1] = a[4]*b[0]  + a[5]*b[1]  + a[6]*b[2]  + a[7]*b[3]
   c[2] = a[8]*b[0]  + a[9]*b[1]  + a[10]*b[2] + a[11]*b[3]
   c[3] = a[12]*b[0] + a[13]*b[1] + a[14]*b[2] + a[15]*b[3]
end sub

' Function of copying matrix values to other storage.
sub matrix_copy(a as single ptr, b as single ptr)
   b[0]=a[0]:   b[1]=a[1]:   b[2]=a[2]:   b[3]=a[3]
   b[4]=a[4]:   b[5]=a[5]:   b[6]=a[6]:   b[7]=a[7]
   b[8]=a[8]:   b[9]=a[9]:   b[10]=a[10]: b[11]=a[11]
   b[12]=a[12]: b[13]=a[13]: b[14]=a[14]: b[15]=a[15]
end sub

' Filling matrix storages with SINGLE MATRIX values.
dim as single value_single
for i as integer = 0 to 15
   read value_single
   matrix_global    [i] = value_single
   matrix_rotate_x  [i] = value_single
   matrix_rotate_y  [i] = value_single
   matrix_rotate_z  [i] = value_single
   matrix_translate [i] = value_single
   matrix_buffer    [i] = value_single
next i

' Defining and allocating storage for image data.
dim shared as uinteger ptr frame_image
frame_image = new uinteger[screen_size]

' Function that copy data
' Used to copy image data to back buffer of screen.
' Simple memory copying.
sub frame_copy (source as uinteger ptr, target as uinteger ptr)
   for i as uinteger = 0 to screen_size-1
      *(target+i) = *(source+i)
   next i
end sub

' Function that clean data.
' Used to clean image data.
' Simple memory filling.
sub frame_clean(target as uinteger ptr)
   for i as uinteger = 0 to screen_size-1
      *(target+i) = 0
   next i
end sub

' Defining and allocating storage for pixel drawing check.
dim shared as single ptr z_buffer
z_buffer = new single[screen_size]

' Function to clean z buffer values.
sub clean_z_buffer()
   for i as integer = 0 to screen_size-1
      *(z_buffer+i) = 0
   next i
end sub

' Variables for 3d vertex calculations.
dim shared as single screen_z, z_buffer_value
' Variables for 2d pixel calculations.
dim shared as integer pixel_ax, pixel_ay, pixel_bx, pixel_by, pixel_cx, pixel_cy

' Function that draw filled triangle at image data storage.
sub draw_triangle(c as uinteger)
   dim as integer x1, x2, y, position, size

   ' Sort 3 pixels to order ay < by < cy
   if pixel_by > pixel_cy then   swap pixel_by, pixel_cy: swap pixel_bx, pixel_cx: end if
   if pixel_ay > pixel_cy then swap pixel_ay, pixel_cy: swap pixel_ax, pixel_cx: end if
   if pixel_ay > pixel_by then swap pixel_ay, pixel_by: swap pixel_ax, pixel_bx: end if
   ' In case polygon is less than one pixel size.
   if pixel_cy = pixel_ay then return
   
   ' Calculating start and end pixels for each scanline.
   for y = pixel_ay to pixel_cy step 1
      x1 = pixel_ax+(y-pixel_ay)*(pixel_cx-pixel_ax)/(pixel_cy-pixel_ay)
      if y < pixel_by then
         x2 = pixel_ax+(y-pixel_ay)*(pixel_bx-pixel_ax)/(pixel_by-pixel_ay)
      else
         if pixel_cy = pixel_by then
            x2 = pixel_bx
         else
            x2 = pixel_bx+(y-pixel_by)*(pixel_cx-pixel_bx)/(pixel_cy-pixel_by)
         end if
      end if
      if x1 > x2 then swap x1, x2
      ' Position of the start pixel in storage
      position = y*screen_width+x1
      ' Amount of the pixels in line.
      size = x2-x1
      for i as integer = 0 to size
         ' Compare z distance of the face center point
         ' with buffer value.
         if z_buffer_value > *(z_buffer+position+i) then
         ' Set color
         *(frame_image+position+i) = c
         ' Set buffer value
         *(z_buffer+position+i) = z_buffer_value
         end if
      next i
   next y
end sub

' Defining geometry storage
type TYPE_VERTEX
   as single ptr p
   declare constructor ()
   declare destructor ()
end type

constructor TYPE_VERTEX ()
   this.p = new single[4]
   this.p[3] = 1
end constructor

destructor TYPE_VERTEX ()
   delete[] this.p
   this.p = 0
end destructor

type TYPE_FACE
   as uinteger v(2) ' vertexes numbers
   as uinteger n(2) ' normals numbers
   as uinteger fc ' color value
   declare constructor ()
end type
constructor TYPE_FACE ()
   dim as ubyte ccc = 100+rnd*155
   this.fc = rgba(ccc, ccc, ccc, 0)
end constructor

type TYPE_GEOMETRY
   as uinteger vn ' vertexes amount number
   as uinteger nn ' faces amount number
   as uinteger fn ' faces amount number
   as TYPE_VERTEX ptr v ' pointer to vertexes storage
   as TYPE_VERTEX ptr n ' pointer to normals storage
   as TYPE_FACE ptr f   ' pointer to faces storage
   declare constructor (as integer, as integer, as integer)
   declare destructor ()
end type

constructor TYPE_GEOMETRY(vertexes as integer, normals as integer, faces as integer)
   this.vn = vertexes
   this.nn = normals
   this.fn = faces
   this.v = new TYPE_VERTEX[this.vn]
   this.n = new TYPE_VERTEX[this.nn]
   this.f = new TYPE_FACE[this.fn]
end constructor

destructor TYPE_GEOMETRY ()
   delete[] this.v
   delete[] this.n
   delete[] this.f
   this.v=0
   this.n=0
   this.f=0
end destructor

' Temporal pointer to geometry type variable
dim shared as TYPE_GEOMETRY ptr ptr_geometry

' Loding geometry from *.obj format file.
' Loads only triangulated mesh. Faces requied.
function load_obj_file(filename as string) as TYPE_GEOMETRY ptr

   dim as integer file, dimension, lenght, map, amount_vertex=0, amount_normal=0, amount_face=0
   dim as string buffer, value, symbol

   file = freefile
   if (open (filename for binary access read as #file)) = 0 then
      while not eof(file)
         line input #file, buffer
         select case mid(buffer, 1, 2)
         case "v "
            amount_vertex += 1
         case "vn"
            amount_normal += 1
         case "f "
            amount_face += 1
         end select
      wend

      ptr_geometry = new TYPE_GEOMETRY(amount_vertex, amount_normal, amount_face)

      amount_vertex=0
      amount_normal=0
      amount_face=0

      seek file, 1
      while not eof(file)
         line input #file, buffer
         lenght = len(buffer)
         select case mid(buffer, 1, 2)

         case "v "
            dimension = 2
            for i as integer = lenght to 2 step -1
               symbol = mid(buffer, i, 1)
               if symbol = " " then
                  ptr_geometry->v[amount_vertex].p[dimension] = val(value)
                  dimension -= 1
                  value = ""
               elseif dimension > -1 then
                  value = symbol + value
               end if
            next i
            amount_vertex += 1

         case "vn"
            dimension = 2
            for i as integer = lenght to 3 step -1
               symbol = mid(buffer, i, 1)
               if symbol = " " and dimension > -1 then
                  ptr_geometry->n[amount_normal].p[dimension] = val(value)
                  dimension -= 1
                  value = ""
               elseif dimension > -1 then
                  value = symbol + value
               end if
            next i
            amount_normal += 1

         case "f "
            map=2
            dimension = 2
            for i as integer = lenght to 2 step -1
               symbol = mid(buffer, i, 1)
               if symbol = " " and dimension > -1 then
                  ptr_geometry->f[amount_face].v(dimension) = val(value)-1
                  dimension -= 1
                  map = 2
                  value = ""
               elseif symbol = "/" and map = 2 then
                  ptr_geometry->f[amount_face].n(dimension) = val(value)-1
                  map -= 1
                  value = ""
               elseif symbol = "/" and map = 1 then
                  value = ""
               elseif dimension > -1 then
                  value = symbol + value
               end if
            next i
            amount_face += 1
         end select
      wend
      close #file
      
      print amount_face, amount_normal, amount_vertex
      if ptr_geometry->vn>0 and ptr_geometry->fn>0 then
         return ptr_geometry
         ptr_geometry = 0
      else
         delete ptr_geometry
         ptr_geometry = 0
         end
         return 0
      end if
   else
      print "no such file or access denide"
   end if
end function
' Now to the *.bas file...


engine_software.bas

Code: Select all

' nblJIG 2013

' Esc - exit.
' W - move forward.
' S - move backward
' A - strafe left
' D - strafe right
' C - strafe down
' SPACE - strafe up
' Q - tilt left
' E - tilt right
' SHIFT - hold to increase movment speed

' First - iclude a *.bi file
#include "engine_software.bi"

' Load geometry.
dim as TYPE_GEOMETRY ptr o1 = load_obj_file("file.obj")
' Create empty copy of geometry.
dim as TYPE_GEOMETRY ptr o2 = new TYPE_GEOMETRY(o1->vn, o1->nn, o1->fn)

' Copy vertex and normal numbers of the face.
for i as integer = 0 to o1->fn-1

o2->f[i].v(0)=o1->f[i].v(0)
o2->f[i].v(1)=o1->f[i].v(1)
o2->f[i].v(2)=o1->f[i].v(2)

o2->f[i].n(0)=o1->f[i].n(0)
o2->f[i].n(1)=o1->f[i].n(1)
o2->f[i].n(2)=o1->f[i].n(2)

o2->f[i].fc=o1->f[i].fc
next i
' It is necessary to work with original data values, cause while transforming
' original values we loose original data. So that why we should store
' transformed values to copy storage or transform values on demand and
' do not store transformations at all. Limitation of floating point calculation...

' Starting window and doing other system related calls.
screenres screen_width, screen_height, bit_depth, 0, 16
windowtitle "matrix-shmatrix-mathematics"
setmouse screen_width_half, screen_height_half, 0

' After window is started, defining pointer to back buffer of screen.
dim as uinteger ptr frame_buffer = screenptr


' Starting main cycle of program.
time_last = timer
while not multikey(&h01)
   
   ' fps calculations
   time_current=timer
   fps_count += 1
   
   if time_current > time_last + 1 then
      time_last = time_current
      fps_value = fps_count
      fps_count = 0
   end if
   
   ' Cleaning delta values
   *translate_x = 0
   *translate_y = 0
   *translate_z = 0
   angle_x = 0
   angle_y = 0
   angle_z = 0

   ' Getting delta values
   mouse_focus_last = mouse_focus
   mouse_focus = getmouse (mouse_x, mouse_y)
   if mouse_focus=0 then
      if mouse_x<>screen_width_half or mouse_y<>screen_height_half then
         angle_y = (screen_width_half - mouse_x)*0.001
         angle_x = (screen_height_half - mouse_y)*0.001
         setmouse(screen_width_half, screen_height_half)
         event_check = 1
      end if
      
      move_shift = 1
      if multikey(&h2a) then move_shift = 30 ' shift

      if multikey(&h1e) then *translate_x = 0.1 * move_shift:  event_check = 1 ' a
      if multikey(&h20) then *translate_x = -0.1 * move_shift: event_check = 1 ' d
      if multikey(&h2c) then event_check = 1 ' z
      if multikey(&h2d) then event_check = 1 ' x
      if multikey(&h2e) then *translate_y = 0.1 * move_shift:  event_check = 1 ' c
      if multikey(&h39) then *translate_y = -0.1 * move_shift: event_check = 1 ' space
      if multikey(&h11) then *translate_z = -0.1 * move_shift: event_check = 1 ' w
      if multikey(&h1f) then *translate_z = 0.1 * move_shift:  event_check = 1 ' s
      if multikey(&h10) then angle_z = -0.005 * move_shift:    event_check = 1 ' q
      if multikey(&h12) then angle_z = 0.005 * move_shift:     event_check = 1 ' e
   end if
   if mouse_focus_last <> mouse_focus then
      *translate_x = 0
      *translate_y = 0
      *translate_z = 0
      angle_x = 0
      angle_y = 0
      angle_z = 0
   end if

   ' Calculating view transformations.
   if event_check then
   
      matrix_refill()
      
      *camera_distance=-1000
      multiply_matrix  (matrix_buffer, matrix_translate, matrix_result_1)
      multiply_matrix  (matrix_result_1, matrix_rotate_x, matrix_result_2)
      multiply_matrix  (matrix_result_2, matrix_rotate_y, matrix_result_1)
      multiply_matrix  (matrix_result_1, matrix_rotate_z, matrix_result_2)
      
      *camera_distance=1000
      multiply_matrix  (matrix_result_2, matrix_buffer, matrix_result_1)
      multiply_matrix  (matrix_result_1, matrix_global, matrix_result_2)
      matrix_copy      (matrix_result_2, matrix_global)

      event_check = 0
   end if

   ' Applying view transformations to geometry and storing copy of geometry.
   ' o1 to o2
   for i as integer = 0 to o1->vn-1
      multiply_vector (matrix_global, o1->v[i].p, o2->v[i].p)
   next i

   ' Cleaning image data and z_buffer values.
   frame_clean(frame_image)
   clean_z_buffer()

   ' For each face of geometry
   for i as integer = 0 to o2->fn-1
      ' Calculating face z position.
      screen_z = (o2->v[o2->f[i].v(0)].p[2] + o2->v[o2->f[i].v(1)].p[2] + o2->v[o2->f[i].v(2)].p[2])/3

      ' Face is in front of view.
      if screen_z > - 1000 then

' Calculating screen positions for all, related to face, vertexes.
pixel_ax = screen_width_half  + o2->v[o2->f[i].v(0)].p[0]*1000/(o2->v[o2->f[i].v(0)].p[2]+1000)
pixel_ay = screen_height_half - o2->v[o2->f[i].v(0)].p[1]*1000/(o2->v[o2->f[i].v(0)].p[2]+1000)
pixel_bx = screen_width_half  + o2->v[o2->f[i].v(1)].p[0]*1000/(o2->v[o2->f[i].v(1)].p[2]+1000)
pixel_by = screen_height_half - o2->v[o2->f[i].v(1)].p[1]*1000/(o2->v[o2->f[i].v(1)].p[2]+1000)
pixel_cx = screen_width_half  + o2->v[o2->f[i].v(2)].p[0]*1000/(o2->v[o2->f[i].v(2)].p[2]+1000)
pixel_cy = screen_height_half - o2->v[o2->f[i].v(2)].p[1]*1000/(o2->v[o2->f[i].v(2)].p[2]+1000)

' Check - is any pixels hit the screen?
face_visable = 0
if (pixel_ax > -1 and pixel_ax < screen_width) and (pixel_ay > -1 and pixel_ay < screen_height) then
   face_visable += 1
end if
if (pixel_bx > -1 and pixel_bx < screen_width) and (pixel_by > -1 and pixel_by < screen_height) then
   face_visable += 1
end if
if (pixel_cx > -1 and pixel_cx < screen_width) and (pixel_cy > -1 and pixel_cy < screen_height) then
   face_visable += 1
end if

' And if all three pixels is on screen - draw triangle.
' For other cases you can design your own functions that split one triangle
' in 2 or 3 triangles to fit the screen. Cause we have all positions of
' pixels it is not a problem to find intersection with screen sides and
' create 1, 2 or 3 more pixels and pass them to draw triangle function.
if face_visable = 3 then
   ' Calculating z distance of face for z_buffer check.
   z_buffer_value = 1+1/(screen_z + (-1000))
   ' Draw triangle.
   draw_triangle(o2->f[i].fc)
end if
      end if
   next i
   
   ' Time to put image on screen.
   screenlock
   
   ' Copy image data.
   frame_copy(frame_image, frame_buffer)
   draw string (0,10), "fps: "+ str(fps_value)
   screenunlock
   sleep 1
   ' That is all. Hope you enjoy it. See you around.
wend
end


Don't forget to place *.obj file near executable. http://yadi.sk/d/Fz8SEItW6Q_fF

If you cant compile or run this sample, keep in mind that is an example. But it work fine for me. Both linux and windows xp.
Last edited by podvornyak on Jul 02, 2013 17:57, edited 2 times in total.
podvornyak
Posts: 148
Joined: Nov 12, 2007 11:46
Location: Russia

Re: Tutorial: Software 3d and matrix manipulations.

Postby podvornyak » Jun 26, 2013 18:19

I deside to stop creation of "one more software 3d engine". Instead i'll try to get something new, cause my mind is full of inspiring ideas for a long time. No time to waste on old crap.

Exampe for now. Use W and S to move forward and backward for more impessive results. Anyway all keys and mouse events is enabled.

Code: Select all

' nblJIG 2013

' Esc - exit.
' W - move forward.
' S - move backward
' A - strafe left
' D - strafe right
' C - strafe down
' SPACE - strafe up
' Q - tilt left
' E - tilt right
' SHIFT - increase movment speed


'-----------------
' base definations
'-----------------

dim shared as integer screen_width = 1000, screen_height = 1000, screen_bit = 32, _
               screen_width_half , screen_height_half, screen_size, _
               fps_count, fps_value

   screen_width_half = screen_width/2
   screen_height_half = screen_height/2
   screen_size = screen_width*screen_height

dim as double time_last, time_current, time_frame

dim as integer mouse_x, mouse_y, mouse_buttion, mouse_wheel, _
            mouse_focus, mouse_focus_last, event_check

dim as single move_shift

'-------------------
' matrix definations
'-------------------

dim shared as single ptr matrix_single: matrix_single = new single[16]
matrix_single[0]= 1: matrix_single[1]= 0: matrix_single[2]= 0: matrix_single[3]= 0
matrix_single[4]= 0: matrix_single[5]= 1: matrix_single[6]= 0: matrix_single[7]= 0
matrix_single[8]= 0: matrix_single[9]= 0: matrix_single[10]=1: matrix_single[11]=0
matrix_single[12]=0: matrix_single[13]=0: matrix_single[14]=0: matrix_single[15]=1

dim shared as single ptr matrix_global: matrix_global = new single[16]
dim shared as single ptr matrix_geometry: matrix_geometry = new single[16]

dim shared as single ptr matrix_rotate_x: matrix_rotate_x = new single[16]
dim shared as single ptr matrix_rotate_y: matrix_rotate_y = new single[16]
dim shared as single ptr matrix_rotate_z: matrix_rotate_z = new single[16]
dim shared as single ptr matrix_translate: matrix_translate = new single[16]
dim shared as single ptr matrix_buffer: matrix_buffer = new single[16]
dim shared as single ptr matrix_result_1: matrix_result_1 = new single[16]
dim shared as single ptr matrix_result_2: matrix_result_2 = new single[16]

sub matrix_copy(a as single ptr, b as single ptr)
   b[0]=a[0]:   b[1]=a[1]:   b[2]=a[2]:   b[3]=a[3]
   b[4]=a[4]:   b[5]=a[5]:   b[6]=a[6]:   b[7]=a[7]
   b[8]=a[8]:   b[9]=a[9]:   b[10]=a[10]: b[11]=a[11]
   b[12]=a[12]: b[13]=a[13]: b[14]=a[14]: b[15]=a[15]
end sub

sub matrix_clean()
 matrix_copy(matrix_single, matrix_global)
 matrix_copy(matrix_single, matrix_geometry)
 
 matrix_copy(matrix_single, matrix_translate)
 matrix_copy(matrix_single, matrix_rotate_x)
 matrix_copy(matrix_single, matrix_rotate_y)
 matrix_copy(matrix_single, matrix_rotate_z)
 matrix_copy(matrix_single, matrix_buffer)
 matrix_copy(matrix_single, matrix_result_1)
 matrix_copy(matrix_single, matrix_result_2)
end sub

matrix_clean()

dim shared as single angle_x, angle_y, angle_z
dim shared as single ptr translate_x, translate_y, translate_z, _
                   camera_distance

translate_x = @matrix_translate  [3]
translate_y = @matrix_translate  [7]
translate_z = @matrix_translate  [11]
camera_distance = @matrix_buffer [11]

sub matrix_refill()
      matrix_rotate_x [5]    = cos(angle_x)
      matrix_rotate_x [10] = matrix_rotate_x [5]
      matrix_rotate_x [9]    = sin(angle_x)
      matrix_rotate_x [6]  = -matrix_rotate_x [9]
      matrix_rotate_y [0]    = cos(angle_y)
      matrix_rotate_y [10] = matrix_rotate_y [0]
      matrix_rotate_y [2]  = sin(angle_y)
      matrix_rotate_y [8]  = -matrix_rotate_y [2]
      matrix_rotate_z [0]      = cos(angle_z)
      matrix_rotate_z [5]      = matrix_rotate_z [0]
      matrix_rotate_z [4]   = sin(angle_z)
      matrix_rotate_z [1]   = -matrix_rotate_z [4]
end sub

sub multiply_matrix(a as single ptr, b as single ptr, c as single ptr)
   c[0] = a[0]*b[0]   + a[1]*b[4]  + a[2]*b[8]   + a[3]*b[12]
   c[1] = a[0]*b[1]   + a[1]*b[5]  + a[2]*b[9]   + a[3]*b[13]
   c[2] = a[0]*b[2]   + a[1]*b[6]  + a[2]*b[10]  + a[3]*b[14]
   c[3] = a[0]*b[3]   + a[1]*b[7]  + a[2]*b[11]  + a[3]*b[15]
   
   c[4] = a[4]*b[0]   + a[5]*b[4]  + a[6]*b[8]   + a[7]*b[12]
   c[5] = a[4]*b[1]   + a[5]*b[5]  + a[6]*b[9]   + a[7]*b[13]
   c[6] = a[4]*b[2]   + a[5]*b[6]  + a[6]*b[10]  + a[7]*b[14]
   c[7] = a[4]*b[3]   + a[5]*b[7]  + a[6]*b[11]  + a[7]*b[15]
   
   c[8] = a[8]*b[0]   + a[9]*b[4]  + a[10]*b[8]  + a[11]*b[12]
   c[9] = a[8]*b[1]   + a[9]*b[5]  + a[10]*b[9]  + a[11]*b[13]
   c[10] = a[8]*b[2]  + a[9]*b[6]  + a[10]*b[10] + a[11]*b[14]
   c[11] = a[8]*b[3]  + a[9]*b[7]  + a[10]*b[11] + a[11]*b[15]

   c[12] = a[12]*b[0] + a[13]*b[4] + a[14]*b[8]  + a[15]*b[12]
   c[13] = a[12]*b[1] + a[13]*b[5] + a[14]*b[9]  + a[15]*b[13]
   c[14] = a[12]*b[2] + a[13]*b[6] + a[14]*b[10] + a[15]*b[14]
   c[15] = a[12]*b[3] + a[13]*b[7] + a[14]*b[11] + a[15]*b[15]
end sub

sub multiply_vector(a as single ptr, b as single ptr, c as single ptr)
   c[0] = a[0]*b[0]  + a[1]*b[1]  + a[2]*b[2]  + a[3]*b[3]
   c[1] = a[4]*b[0]  + a[5]*b[1]  + a[6]*b[2]  + a[7]*b[3]
   c[2] = a[8]*b[0]  + a[9]*b[1]  + a[10]*b[2] + a[11]*b[3]
   c[3] = a[12]*b[0] + a[13]*b[1] + a[14]*b[2] + a[15]*b[3]
end sub

'---------------
' 2d definations
'---------------

type TYPE_PIXEL
   as integer x, y 'screen positiion
   as uinteger c ' color
   as ubyte v ' visable
end type

dim as TYPE_PIXEL pixel_current ' projected vertex

'storage for last one 10 kk projection of vertexes
'1000 X 1000 ~ 1 kk pixels
dim as TYPE_PIXEL ptr pixel = new TYPE_PIXEL[1000000]
dim as uinteger current_pixel = 0

dim shared as uinteger ptr frame_image ' memory for screen image
frame_image = new uinteger[screen_size]

sub frame_copy (source as uinteger ptr, target as uinteger ptr)
   for i as uinteger = 0 to screen_size-1
      *(target+i) = *(source+i)
   next i
end sub
sub frame_clean(target as uinteger ptr)
   for i as uinteger = 0 to screen_size-1
      *(target+i) = 0
   next i
end sub

'--------------
'3d definations
'--------------

type TYPE_VERTEX
   as single ptr p = new single[4] ' position
   as uinteger c ' color
end type

type TYPE_GEOMETRY
   as uinteger vn ' vertexes amount
   as TYPE_VERTEX ptr v
   declare constructor (as uinteger)
   declare destructor ()
end type
constructor TYPE_GEOMETRY (vertexes as uinteger)
   this.vn = vertexes
   if vn > 0 then this.v = new TYPE_VERTEX[this.vn]
end constructor
destructor TYPE_GEOMETRY ()
   delete[] this.v
   this.v=0
end destructor

' defining 3d octo-quad geometry storage
dim as TYPE_GEOMETRY ptr o = new TYPE_GEOMETRY(8)


'assigning values
o->v[0].p[0]=1: o->v[0].p[1]=-1: o->v[0].p[2]=-1: o->v[0].p[3]=1
o->v[1].p[0]=-1: o->v[1].p[1]=1: o->v[1].p[2]=-1: o->v[1].p[3]=1
o->v[2].p[0]=-1: o->v[2].p[1]=-1: o->v[2].p[2]=1: o->v[2].p[3]=1
o->v[3].p[0]=1: o->v[3].p[1]=1: o->v[3].p[2]=1: o->v[3].p[3]=1
o->v[4].p[0]=1: o->v[4].p[1]=1: o->v[4].p[2]=-1: o->v[4].p[3]=-1
o->v[5].p[0]=-1: o->v[5].p[1]=1: o->v[5].p[2]=1: o->v[5].p[3]=-1
o->v[6].p[0]=1: o->v[6].p[1]=-1: o->v[6].p[2]=1: o->v[6].p[3]=-1
o->v[7].p[0]=-1: o->v[7].p[1]=-1: o->v[7].p[2]=-1: o->v[7].p[3]=-1

randomize

o->v[0].c = rgb(rnd*255, rnd*255, rnd*255)
o->v[1].c = rgb(rnd*255, rnd*255, rnd*255)
o->v[2].c = rgb(rnd*255, rnd*255, rnd*255)
o->v[3].c = rgb(rnd*255, rnd*255, rnd*255)
o->v[4].c = rgb(rnd*255, rnd*255, rnd*255)
o->v[5].c = rgb(rnd*255, rnd*255, rnd*255)
o->v[6].c = rgb(rnd*255, rnd*255, rnd*255)
o->v[7].c = rgb(rnd*255, rnd*255, rnd*255)

' on demand mastrix calculation storages
dim as TYPE_VERTEX ptr cursor = new TYPE_VERTEX[2]

'-----------
' initialize
'-----------

screenres screen_width, screen_height, screen_bit, 0, 0
setmouse screen_width_half, screen_height_half, 0

dim as integer ptr frame_buffer = screenptr

matrix_global[11]=-990 ' - move view closer to geometry

time_last = timer

'------
' main
'------

while not multikey(&h01)

   ' fps
   time_current=timer
   fps_count += 1
   
   if time_current > time_last + 1 then
      time_last = time_current
      fps_value = fps_count
      fps_count = 0
   end if

   ' cleaninig
   *translate_x = 0
   *translate_y = 0
   *translate_z = 0
   angle_x = 0
   angle_y = 0
   angle_z = 0
   
   ' input events
   mouse_focus_last = mouse_focus
   mouse_focus = getmouse (mouse_x, mouse_y)
   if mouse_focus=0 then
      if mouse_x<>screen_width_half or mouse_y<>screen_height_half then
         angle_y = (screen_width_half - mouse_x)*0.001
         angle_x = (screen_height_half - mouse_y)*0.001
         setmouse(screen_width_half, screen_height_half)
         event_check = 1
      end if
      
      move_shift = 1
      if multikey(&h2a) then move_shift = 30 ' shift

      if multikey(&h1e) then *translate_x = 0.1 * move_shift:  event_check = 1 ' a
      if multikey(&h20) then *translate_x = -0.1 * move_shift: event_check = 1 ' d
      if multikey(&h2c) then event_check = 1 ' z
      if multikey(&h2d) then event_check = 1 ' x
      if multikey(&h2e) then *translate_y = 0.1 * move_shift:  event_check = 1 ' c
      if multikey(&h39) then *translate_y = -0.1 * move_shift: event_check = 1 ' space
      if multikey(&h11) then *translate_z = -0.1 * move_shift: event_check = 1 ' w
      if multikey(&h1f) then *translate_z = 0.1 * move_shift:  event_check = 1 ' s
      if multikey(&h10) then angle_z = -0.005 * move_shift:    event_check = 1 ' q
      if multikey(&h12) then angle_z = 0.005 * move_shift:     event_check = 1 ' e
   end if
   
   if mouse_focus_last <> mouse_focus then
      *translate_x = 0
      *translate_y = 0
      *translate_z = 0
      angle_x = 0
      angle_y = 0
      angle_z = 0
   end if

   ' view transformation matrix calculation
   if event_check then
      matrix_refill()
      *camera_distance=-1000      
      multiply_matrix  (matrix_buffer, matrix_translate, matrix_result_1)
      multiply_matrix  (matrix_result_1, matrix_rotate_x, matrix_result_2)
      multiply_matrix  (matrix_result_2, matrix_rotate_y, matrix_result_1)
      multiply_matrix  (matrix_result_1, matrix_rotate_z, matrix_result_2)
      
      *camera_distance=1000
      multiply_matrix  (matrix_result_2, matrix_buffer, matrix_result_1)
      multiply_matrix  (matrix_result_1, matrix_global, matrix_result_2)
      matrix_copy      (matrix_result_2, matrix_global)
      event_check = 0
   end if

   ' geometry transformation matrix calculation
   angle_x = 0.1*cos(timer)
   angle_y = 0.1*sin(timer)
   angle_z = 0.02*sin(timer)*cos(timer)
   matrix_refill()

   multiply_matrix(matrix_rotate_x, matrix_rotate_y, matrix_result_2)
   multiply_matrix(matrix_result_2, matrix_rotate_z, matrix_result_1)
   multiply_matrix(matrix_result_1, matrix_geometry, matrix_result_2)
   matrix_copy(matrix_result_2, matrix_geometry)

   'clean screen image
   frame_clean(frame_image)

   for i as integer = 0 to o->vn-1
      ' apply transformation matrixes
      multiply_vector(matrix_geometry, o->v[i].p, cursor[0].p)
      cursor[0].p[3]=1
      multiply_vector(matrix_global, cursor[0].p , cursor[1].p)
      if (cursor[1].p[0] > - 1000) then

         pixel_current.x = screen_width_half  + cursor[1].p[0]*1000/(cursor[1].p[2]+1000)
         pixel_current.y = screen_height_half - cursor[1].p[1]*1000/(cursor[1].p[2]+1000)

         if (pixel_current.x > -1 and pixel_current.x < screen_width) and (pixel_current.y > -1 and pixel_current.y < screen_height) then

            pixel[current_pixel].x = pixel_current.x
            pixel[current_pixel].y = pixel_current.y
            pixel[current_pixel].c = o->v[i].c
            pixel[current_pixel].v = 1
            current_pixel += 1
            if current_pixel > 1000000 then current_pixel = 0
         end if
      end if
   next i
   for i as integer = 0 to 1000000
      if pixel[i].v > 0 then *(frame_image+pixel[i].y*screen_width+pixel[i].x) = pixel[i].c
      pixel[i].c -= pixel[i].c/32 ' too expensive. speed doubles without that string.
      ' but still you can play with divider. High value grant long trails.
   next i

   screenlock
   frame_copy(frame_image, frame_buffer)
   draw string (0,0), str(matrix_global[3])+" "+str(matrix_global[5])+" "+str(matrix_global[11])
   draw string (0,10), "fps: "+ str(fps_value)
   draw string (0,20), "vertexes: "+ str(o->vn)

   screenunlock
   sleep 1
wend
end

Image
Next task is to get same result in 3d geometry... not just projection.
podvornyak
Posts: 148
Joined: Nov 12, 2007 11:46
Location: Russia

Re: Tutorial: Software 3d and matrix manipulations.

Postby podvornyak » Jun 26, 2013 21:06

Damn, i'm fast today. Done. Now in 3d. Taste it. Try ro use SHIFT while moving... see what happens. ^_^

Code: Select all

' nblJIG 2013

' Esc - exit.
' W - move forward.
' S - move backward
' A - strafe left
' D - strafe right
' C - strafe down
' SPACE - strafe up
' Q - tilt left
' E - tilt right
' SHIFT - increase movment speed


'-----------------
' base definations
'-----------------
dim shared as integer screen_width = 1000, screen_height = 1000, screen_bit = 32, _
               screen_width_half , screen_height_half, screen_size, _
               fps_count, fps_value

screen_width_half = screen_width/2
screen_height_half = screen_height/2
screen_size = screen_width*screen_height

dim as double time_last, time_current, time_frame

dim as integer mouse_x, mouse_y, mouse_buttion, mouse_wheel, _
            mouse_focus, mouse_focus_last, event_check

dim as single move_shift

'-------------------
' matrix definations
'-------------------

dim shared as single ptr matrix_single: matrix_single = new single[16]
matrix_single[0]= 1: matrix_single[1]= 0: matrix_single[2]= 0: matrix_single[3]= 0
matrix_single[4]= 0: matrix_single[5]= 1: matrix_single[6]= 0: matrix_single[7]= 0
matrix_single[8]= 0: matrix_single[9]= 0: matrix_single[10]=1: matrix_single[11]=0
matrix_single[12]=0: matrix_single[13]=0: matrix_single[14]=0: matrix_single[15]=1

dim shared as single ptr matrix_global: matrix_global = new single[16]
dim shared as single ptr matrix_geometry: matrix_geometry = new single[16]

dim shared as single ptr matrix_rotate_x: matrix_rotate_x = new single[16]
dim shared as single ptr matrix_rotate_y: matrix_rotate_y = new single[16]
dim shared as single ptr matrix_rotate_z: matrix_rotate_z = new single[16]
dim shared as single ptr matrix_translate: matrix_translate = new single[16]
dim shared as single ptr matrix_buffer: matrix_buffer = new single[16]
dim shared as single ptr matrix_result_1: matrix_result_1 = new single[16]
dim shared as single ptr matrix_result_2: matrix_result_2 = new single[16]

sub matrix_copy(a as single ptr, b as single ptr)
   b[0]=a[0]:   b[1]=a[1]:   b[2]=a[2]:   b[3]=a[3]
   b[4]=a[4]:   b[5]=a[5]:   b[6]=a[6]:   b[7]=a[7]
   b[8]=a[8]:   b[9]=a[9]:   b[10]=a[10]: b[11]=a[11]
   b[12]=a[12]: b[13]=a[13]: b[14]=a[14]: b[15]=a[15]
end sub

sub matrix_clean()
   matrix_copy(matrix_single, matrix_global)
   matrix_copy(matrix_single, matrix_geometry)
     
   matrix_copy(matrix_single, matrix_translate)
   matrix_copy(matrix_single, matrix_rotate_x)
   matrix_copy(matrix_single, matrix_rotate_y)
   matrix_copy(matrix_single, matrix_rotate_z)
   matrix_copy(matrix_single, matrix_buffer)
   matrix_copy(matrix_single, matrix_result_1)
   matrix_copy(matrix_single, matrix_result_2)
end sub

matrix_clean()

dim shared as single angle_x, angle_y, angle_z
dim shared as single ptr translate_x, translate_y, translate_z, _
                     camera_distance

   translate_x = @matrix_translate  [3]
   translate_y = @matrix_translate  [7]
   translate_z = @matrix_translate  [11]
   camera_distance = @matrix_buffer [11]

sub matrix_refill()
   matrix_rotate_x [5]    = cos(angle_x)
   matrix_rotate_x [10] = matrix_rotate_x [5]
   matrix_rotate_x [9]    = sin(angle_x)
   matrix_rotate_x [6]  = -matrix_rotate_x [9]
   matrix_rotate_y [0]    = cos(angle_y)
   matrix_rotate_y [10] = matrix_rotate_y [0]
   matrix_rotate_y [2]  = sin(angle_y)
   matrix_rotate_y [8]  = -matrix_rotate_y [2]
   matrix_rotate_z [0]      = cos(angle_z)
   matrix_rotate_z [5]      = matrix_rotate_z [0]
   matrix_rotate_z [4]   = sin(angle_z)
   matrix_rotate_z [1]   = -matrix_rotate_z [4]
end sub

sub multiply_matrix(a as single ptr, b as single ptr, c as single ptr)
   c[0] = a[0]*b[0]   + a[1]*b[4]  + a[2]*b[8]   + a[3]*b[12]
   c[1] = a[0]*b[1]   + a[1]*b[5]  + a[2]*b[9]   + a[3]*b[13]
   c[2] = a[0]*b[2]   + a[1]*b[6]  + a[2]*b[10]  + a[3]*b[14]
   c[3] = a[0]*b[3]   + a[1]*b[7]  + a[2]*b[11]  + a[3]*b[15]
       
   c[4] = a[4]*b[0]   + a[5]*b[4]  + a[6]*b[8]   + a[7]*b[12]
   c[5] = a[4]*b[1]   + a[5]*b[5]  + a[6]*b[9]   + a[7]*b[13]
   c[6] = a[4]*b[2]   + a[5]*b[6]  + a[6]*b[10]  + a[7]*b[14]
   c[7] = a[4]*b[3]   + a[5]*b[7]  + a[6]*b[11]  + a[7]*b[15]
       
   c[8] = a[8]*b[0]   + a[9]*b[4]  + a[10]*b[8]  + a[11]*b[12]
   c[9] = a[8]*b[1]   + a[9]*b[5]  + a[10]*b[9]  + a[11]*b[13]
   c[10] = a[8]*b[2]  + a[9]*b[6]  + a[10]*b[10] + a[11]*b[14]
   c[11] = a[8]*b[3]  + a[9]*b[7]  + a[10]*b[11] + a[11]*b[15]

   c[12] = a[12]*b[0] + a[13]*b[4] + a[14]*b[8]  + a[15]*b[12]
   c[13] = a[12]*b[1] + a[13]*b[5] + a[14]*b[9]  + a[15]*b[13]
   c[14] = a[12]*b[2] + a[13]*b[6] + a[14]*b[10] + a[15]*b[14]
   c[15] = a[12]*b[3] + a[13]*b[7] + a[14]*b[11] + a[15]*b[15]
end sub

sub multiply_vector(a as single ptr, b as single ptr, c as single ptr)
   c[0] = a[0]*b[0]  + a[1]*b[1]  + a[2]*b[2]  + a[3]*b[3]
   c[1] = a[4]*b[0]  + a[5]*b[1]  + a[6]*b[2]  + a[7]*b[3]
   c[2] = a[8]*b[0]  + a[9]*b[1]  + a[10]*b[2] + a[11]*b[3]
   c[3] = a[12]*b[0] + a[13]*b[1] + a[14]*b[2] + a[15]*b[3]
end sub

sub copy_vector(target as single ptr, source as single ptr)
   target[0] = source[0]
   target[1] = source[1]
   target[2] = source[2]
   target[3] = source[3]
end sub

'---------------
' 2d definations
'---------------

type TYPE_PIXEL
   as integer x, y 'screen positiion
   as uinteger c ' color
   as ubyte v ' visable
end type

dim as TYPE_PIXEL pixel_current ' projected vertex

dim shared as uinteger ptr frame_image ' memory for screen image
frame_image = new uinteger[screen_size]

sub frame_copy (source as uinteger ptr, target as uinteger ptr)
   for i as uinteger = 0 to screen_size-1
      *(target+i) = *(source+i)
   next i
end sub

sub frame_clean(target as uinteger ptr)
   for i as uinteger = 0 to screen_size-1
      *(target+i) = 0
   next i
end sub

'--------------
'3d definations
'--------------

type TYPE_VERTEX
   as single ptr p = new single[4] ' position
   as uinteger c ' color
end type

type TYPE_GEOMETRY
   as uinteger vn ' vertexes amount
   as TYPE_VERTEX ptr v
   declare constructor (as uinteger)
   declare destructor ()
end type
constructor TYPE_GEOMETRY (vertexes as uinteger)
   this.vn = vertexes
   if vn > 0 then this.v = new TYPE_VERTEX[this.vn]
end constructor
destructor TYPE_GEOMETRY ()
   delete[] this.v
   this.v=0
end destructor

' defining 3d octo-quad geometry storage
dim as TYPE_GEOMETRY ptr o = new TYPE_GEOMETRY(8)

'assigning values
o->v[0].p[0]=1: o->v[0].p[1]=-1: o->v[0].p[2]=-1: o->v[0].p[3]=1
o->v[1].p[0]=-1: o->v[1].p[1]=1: o->v[1].p[2]=-1: o->v[1].p[3]=1
o->v[2].p[0]=-1: o->v[2].p[1]=-1: o->v[2].p[2]=1: o->v[2].p[3]=1
o->v[3].p[0]=1: o->v[3].p[1]=1: o->v[3].p[2]=1: o->v[3].p[3]=1
o->v[4].p[0]=1: o->v[4].p[1]=1: o->v[4].p[2]=-1: o->v[4].p[3]=-1
o->v[5].p[0]=-1: o->v[5].p[1]=1: o->v[5].p[2]=1: o->v[5].p[3]=-1
o->v[6].p[0]=1: o->v[6].p[1]=-1: o->v[6].p[2]=1: o->v[6].p[3]=-1
o->v[7].p[0]=-1: o->v[7].p[1]=-1: o->v[7].p[2]=-1: o->v[7].p[3]=-1

randomize

o->v[0].c = rgb(rnd*255, rnd*255, rnd*255)
o->v[1].c = rgb(rnd*255, rnd*255, rnd*255)
o->v[2].c = rgb(rnd*255, rnd*255, rnd*255)
o->v[3].c = rgb(rnd*255, rnd*255, rnd*255)
o->v[4].c = rgb(rnd*255, rnd*255, rnd*255)
o->v[5].c = rgb(rnd*255, rnd*255, rnd*255)
o->v[6].c = rgb(rnd*255, rnd*255, rnd*255)
o->v[7].c = rgb(rnd*255, rnd*255, rnd*255)

' on demand mastrix calculation storages
dim as TYPE_VERTEX cursor

' storage for transformed geometry
dim as  TYPE_VERTEX ptr vertex = new TYPE_VERTEX [100001]
dim as uinteger current_vertex = 0
'-----------
' initialize
'-----------

screenres screen_width, screen_height, screen_bit, 0, 0
setmouse screen_width_half, screen_height_half, 0

dim as integer ptr frame_buffer = screenptr

matrix_global[11]=-990 ' - move view closer to geometry

time_last = timer

'------
' main
'------

while not multikey(&h01)

   ' fps
   time_current=timer
   fps_count += 1

   if time_current > time_last + 1 then
      time_last = time_current
      fps_value = fps_count
      fps_count = 0
   end if

   ' cleaninig
   *translate_x = 0
   *translate_y = 0
   *translate_z = 0
   angle_x = 0
   angle_y = 0
   angle_z = 0
       
   ' input events
   mouse_focus_last = mouse_focus
   mouse_focus = getmouse (mouse_x, mouse_y)
   if mouse_focus=0 then
      if mouse_x<>screen_width_half or mouse_y<>screen_height_half then
         angle_y = (screen_width_half - mouse_x)*0.001
         angle_x = (screen_height_half - mouse_y)*0.001
         setmouse(screen_width_half, screen_height_half)
         event_check = 1
      end if
         
      move_shift = 1
      if multikey(&h2a) then move_shift = 30 ' shift

      if multikey(&h1e) then *translate_x = 0.1 * move_shift:  event_check = 1 ' a
      if multikey(&h20) then *translate_x = -0.1 * move_shift: event_check = 1 ' d
      if multikey(&h2c) then event_check = 1 ' z
      if multikey(&h2d) then event_check = 1 ' x
      if multikey(&h2e) then *translate_y = 0.1 * move_shift:  event_check = 1 ' c
      if multikey(&h39) then *translate_y = -0.1 * move_shift: event_check = 1 ' space
      if multikey(&h11) then *translate_z = -0.1 * move_shift: event_check = 1 ' w
      if multikey(&h1f) then *translate_z = 0.1 * move_shift:  event_check = 1 ' s
      if multikey(&h10) then angle_z = -0.005 * move_shift:    event_check = 1 ' q
      if multikey(&h12) then angle_z = 0.005 * move_shift:     event_check = 1 ' e
   end if
       
   if mouse_focus_last <> mouse_focus then
      *translate_x = 0
      *translate_y = 0
      *translate_z = 0
      angle_x = 0
      angle_y = 0
      angle_z = 0
   end if

   if event_check then
      matrix_refill()
      *camera_distance=-1000     
      multiply_matrix  (matrix_buffer, matrix_translate, matrix_result_1)
      multiply_matrix  (matrix_result_1, matrix_rotate_x, matrix_result_2)
      multiply_matrix  (matrix_result_2, matrix_rotate_y, matrix_result_1)
      multiply_matrix  (matrix_result_1, matrix_rotate_z, matrix_result_2)
         
      *camera_distance=1000
      multiply_matrix  (matrix_result_2, matrix_buffer, matrix_result_1)
      multiply_matrix  (matrix_result_1, matrix_global, matrix_result_2)
      matrix_copy      (matrix_result_2, matrix_global)
      event_check = 0
   end if

   angle_x = 0.1*cos(timer)
   angle_y = 0.1*sin(timer)
   angle_z = 0.02*sin(timer)*cos(timer)
   *translate_x=1*cos(timer)
   *translate_x=1*sin(timer)
   *translate_x=0.2*sin(timer)*cos(timer)
   matrix_refill()

   multiply_matrix(matrix_rotate_x, matrix_rotate_y, matrix_result_2)
   multiply_matrix(matrix_result_2, matrix_rotate_z, matrix_result_1)
   multiply_matrix(matrix_result_1, matrix_translate, matrix_result_2)
   multiply_matrix(matrix_result_2, matrix_geometry, matrix_result_1)
   matrix_copy(matrix_result_1, matrix_geometry)

   frame_clean(frame_image)

   for j as integer = 0 to 100000
      multiply_vector(matrix_global, vertex[j].p , cursor.p)
         if (cursor.p[2] > - 1000) then
            pixel_current.x = screen_width_half  + cursor.p[0]*1000/(cursor.p[2]+1000)
            pixel_current.y = screen_height_half - cursor.p[1]*1000/(cursor.p[2]+1000)
         if (pixel_current.x > -1 and pixel_current.x < screen_width) and (pixel_current.y > -1 and pixel_current.y < screen_height) then
            *(frame_image+pixel_current.y*screen_width+pixel_current.x) = vertex[j].c
         end if
      end if
   next j
   for i as integer = 0 to o->vn-1
   multiply_vector(matrix_geometry, o->v[i].p, cursor.p)
   cursor.p[3]=1
   copy_vector(vertex[current_vertex].p, cursor.p)
   vertex[current_vertex].c = o->v[i].c
   current_vertex += 1
   if current_vertex > 100000 then current_vertex = 0
   next i

   screenlock
   frame_copy(frame_image, frame_buffer)
   draw string (0,0), str(matrix_global[3])+" "+str(matrix_global[5])+" "+str(matrix_global[11])
   draw string (0,10), "fps: "+ str(fps_value)
   draw string (0,20), "vertexes: "+ str(o->vn)

'   screensync
   screenunlock
   sleep 1
wend
end

The idea is - get first value in data chunk and load the rest while displaying. Also, and more important, dynamicly generate geometry basing on cheap absolute data.

Image
Oh my god! It is alive! It's going to split!.. ^_^

Return to “Game Dev”

Who is online

Users browsing this forum: No registered users and 1 guest