Results 1 to 5 of 5

Thread: Generating tobogan mesh in ThinBASIC

  1. #1
    Super Moderator Petr Schreiber's Avatar
    Join Date
    Aug 2005
    Location
    Brno - Czech Republic
    Posts
    7,153
    Rep Power
    736

    Lightbulb Generating tobogan mesh in ThinBASIC

    A friend of mine approached me with a question, how would I model mesh of tobogan.

    Normal person would answer "extrude profile along curve in Blender", but coding it is more interesting task, right?

    Friend needs it done in Python, but thinBASIC has better built in graphics, so I prototype it in TB.

    The project started with generation of tobogan profile, the "U" shape.

    How to model profile?
    The profile could be approached as arc, part of circle. As circle can be generated via SIN/COS, we will take these helpers to build the part of the mesh.

    Some step needs to be decided. As thinBASIC does not allow redim of UDT arrays yet, the step is hardcoded to 16, will update it in future thinBASIC versions to something more tasteful:
    uses "math"
    
    type Point3D
      x as float32
      y as float32
      z as float32
    end type
    
    type ToboganProfile
    
        point(16)   as point3D  ' -- Will be converted to dynamic UDT array once TB supports it
        point_count as int32
        
        function _create(ellipsis_width as float32, ellipsis_height as float32, arc_angle as float32)
        
          me.point_count = ubound(me.point)
          
          float32 angle
          
          float32 min_angle = degToRad(-arc_angle/2) ' Conversion of degrees to radians is offered by math module
          float32 max_angle = degToRad( arc_angle/2)
          float32 angle_step= (max_angle - min_angle) / (me.point_count-1)
          
          int32 i
          for i = 1 to me.point_count
            angle = min_angle + (i-1) * angle_step
            
            me.point(i).x = sin(angle) * ellipsis_width/2
            me.point(i).y = -cos(angle) * ellipsis_height/2
            
            me.point(i).y += ellipsis_height/2 ' shift it up to 0,0,0
          next
          
        end function
    
    end type
    
    Now we can get list of points from the ToboganProfile. In order to simplify rendering, we can add dedicated function to connect the points of the U profile:
        function render()
          int32 i
          
          tbgl_beginPoly %GL_LINE_STRIP ' This primitive allows to just supply list of vertices and they will get connected
            for i = 1 to me.point_count
              tbgl_vertex me.point(i).x, me.point(i).y, me.point(i).z
            next
          tbgl_endPoly
          
          tbgl_pushStateProtect %TBGL_DEPTH ' Ignoring depth allows to draw over the previously rendered line strip
          tbgl_pushColor 255, 0, 0 ' PushColor allows to enable drawing color just until PopColor is called
          tbgl_pushPointSize 5 ' Again, limited scope for point size change
    
            tbgl_beginPoly %GL_POINTS
              for i = 1 to me.point_count
                tbgl_vertex me.point(i).x, me.point(i).y, me.point(i).z
              next
            tbgl_endPoly
    
          tbgl_popPointSize ' Returning the states back to previous state
          tbgl_popColor
          tbgl_popStateProtect
        end function
    
    Grid with 1x1 cells would come in handy, to see the profile size
    We can build a Grid2D type to help us draw grid easily:
    type grid2d
      
      top_x     as float32
      top_y     as float32
      grid_step as float32
      
      function _create(top_x as float32, top_y as float32, optional grid_step as float32 = 1)
        me.top_x = top_x
        me.top_y = top_y
        me.grid_step = grid_step
      end function
      
      function render()
        tbgl_pushColor 128, 128, 128
          tbgl_line(-me.top_x, 0, me.top_x, 0)
          tbgl_line(0, -me.top_y, 0, me.top_y)
        tbgl_popColor
        
        float32 i
        tbgl_pushColor 64, 64, 64
          for i = -me.top_x to me.top_x
            tbgl_line(i, -me.top_y, i, me.top_y)
          next
          
          for i = -me.top_y to me.top_y
            tbgl_line(-me.top_x, i, me.top_x, i)
          next
        tbgl_popColor
      
      end function
    
    end type
    


    Once we have these helpers, let's draw it to the screen
    uses "tbgl", "console"
    
    
    #include "ToboganProfile.tbasicu"
    #include "Grid2d.tbasicu"
    
    
    function tbMain()
      
      ' -- Create and show window
      uint32 hWnd = TBGL_createWindowEx("Tobogan profile - press ESC to quit", 512, 512, 32, %TBGL_WS_WINDOWED or %TBGL_WS_CLOSEBOX) 
      tbgl_showWindow 
    
      dim grid as grid2d(10, 10)                ' Create new grid
      dim profileA as toboganProfile(2, 1, 180) ' Create wide profile, 180°
      dim profileB as toboganProfile(1, 1, 270) ' Create profile with 270° U shape
    
      float32 frameRate
      
      ' -- Print the calculated points
      uint32 i
      printl "Profile A" in 15
      for i = 1 to profileA.point_count
        printl format$(profileA.point(i).x, "#.000"), format$(profileA.point(i).y, "#.000")
      next
      
      printl
      
      printl "Profile B" in 15
      for i = 1 to profileB.point_count
        printl format$(profileB.point(i).x, "#.000"), format$(profileB.point(i).y, "#.000")
      next
          
      ' -- Main loop
      while tbgl_isWindow(hWnd) 
        FrameRate = tbgl_getFrameRate
    
        tbgl_clearFrame
          
          ' Set the camera position
          tbgl_camera 0, 0, 5, 0, 0, 0
          
          ' Render all, in order dependent fashion
          tbgl_pushStateProtect %TBGL_DEPTH
            grid.render()
          
            profileA.render()
          
            profileB.render()
          tbgl_popStateProtect
          
        tbgl_drawFrame 
    
        ' -- ESCAPE key to exit application
        if tbgl_getWindowKeyState(hWnd, %VK_ESCAPE) then exit while
        
      wend
    
      tbgl_destroyWindow
    end function
    
    The result of the drawing can be seen in the attachment. You can also download the complete source code for this phase.


    Petr
    Attached Images Attached Images
    Attached Files Attached Files
    Last edited by Petr Schreiber; 17-03-2019 at 19:52.
    Learn 3D graphics with ThinBASIC, learn TBGL!
    Windows 10 64bit - Intel Core i5-3350P @ 3.1GHz - 16 GB RAM - NVIDIA GeForce GTX 1050 Ti 4GB

  2. #2
    Super Moderator Petr Schreiber's Avatar
    Join Date
    Aug 2005
    Location
    Brno - Czech Republic
    Posts
    7,153
    Rep Power
    736
    The original goal of the project is to generate tobogan mesh, so the next step was to organize set of profiles to a collection and generate triangle mesh.

    As the original ToboganProfile is always created at the origin of coordinates, we need to add way to place them in the space.

    This can be done by adding a simple method to the ToboganProfile type:
        function add_pos(x as float32, y as float32, z as float32)
          int32 i
          for i = 1 to me.point_count
            me.point(i).x += x
            me.point(i).y += y
            me.point(i).z += z
          next
        end function
    
    Next, we need to manage the multiple profiles somehow. I created a type called ToboganMesh for it:
    #include once "ToboganProfile.tbasicu"
    
    
    type Triangle3D
      vertices(3) as Point3D
    end type
    
    
    type ToboganMesh
    
    
      pProfiles         as uint32 ' -- Pointer to profile data in memory
      profile_count     as uint32 ' -- The total count of the profiles
      profile_capacity  as uint32 ' -- The capacity of the profile buffer, must be >= as total count of the profiles
      
      point_detail      as uint32 ' -- Overall point detail, detected from the first profile added
      
      pTriangles        as uint32 ' -- Pointer to triangles in memory
      triangle_count    as uint32 ' -- The total count of triangles
    
    
      function _create()
        me.profile_count = 0
        me.profile_capacity = 8 ' -- Some default value to avoid reallocation after first addition
        me.pProfiles = heap_alloc(sizeof(ToboganProfile) * me.profile_capacity)
      end function
      
      function add_profile(profile as ToboganProfile)
        me.profile_count += 1
        
        if me.profile_count = 1 then
          me.point_detail = profile.point_count                           ' -- The first profile determines the expected detail level for all subsequent profiles
        elseif profile.point_count <> me.point_detail then
          msgbox 0, "New profile detail does not match existing profile"  ' -- Should the detail differ from the existing one, we don't support this operation
          stop
        end if
        
        ' -- Reallocation of memory buffer to bigger size, if needed
        if me.profile_count > me.profile_capacity then
          me.profile_capacity *= 2
          me.pProfiles = heap_realloc(me.pProfiles, sizeof(ToboganProfile) * me.profile_capacity)
        end if
        
        ' -- Copy the profile binary signature to our buffer
        string memory_block = peek$(varptr(profile), sizeof(ToboganProfile))
        poke$(me.pProfiles + (me.profile_count-1) * sizeof(ToboganProfile), memory_block)
      end function
      
      function build()
        if me.profile_count < 2 then exit function  ' -- In order to build a mesh, at least 2 profiles are needed
          
        dim profiles(me.profile_count) as ToboganProfile at me.pProfiles  ' -- Overlay profile memory
        
        me.triangle_count = (me.profile_count-1) * (me.point_detail-1) * 2
        me.pTriangles = heap_alloc(sizeof(Triangle3D) * me.triangle_count)
        
        dim triangles(me.triangle_count) as Triangle3D at me.pTriangles   ' -- Overlay triangle memory
        int32 index
    
    
        ' Mesh building approach:
        '
        ' PROFILE i        PROFILE i+1
        ' POINT j   A----B POINT j
        '           |\   |
        '           | \  |
        '           |  \ |
        '           |   \| POINT j+1
        ' POINT j+1 D----C POINT j+1
        
        int32 i, j
        for i = 1 to me.profile_count-1
          for j = 1 to me.point_detail-1
            index += 1
            ' A
            triangles(index).vertices(1).x = profiles(i).point(j).x
            triangles(index).vertices(1).y = profiles(i).point(j).y
            triangles(index).vertices(1).z = profiles(i).point(j).z
    
    
            ' B
            triangles(index).vertices(2).x = profiles(i+1).point(j).x
            triangles(index).vertices(2).y = profiles(i+1).point(j).y
            triangles(index).vertices(2).z = profiles(i+1).point(j).z
    
    
            ' C
            triangles(index).vertices(3).x = profiles(i+1).point(j+1).x
            triangles(index).vertices(3).y = profiles(i+1).point(j+1).y
            triangles(index).vertices(3).z = profiles(i+1).point(j+1).z
    
    
            index += 1
            ' A
            triangles(index).vertices(1).x = profiles(i).point(j).x
            triangles(index).vertices(1).y = profiles(i).point(j).y
            triangles(index).vertices(1).z = profiles(i).point(j).z
    
    
            ' C
            triangles(index).vertices(2).x = profiles(i+1).point(j+1).x
            triangles(index).vertices(2).y = profiles(i+1).point(j+1).y
            triangles(index).vertices(2).z = profiles(i+1).point(j+1).z
    
    
            ' D
            triangles(index).vertices(3).x = profiles(i).point(j+1).x
            triangles(index).vertices(3).y = profiles(i).point(j+1).y
            triangles(index).vertices(3).z = profiles(i).point(j+1).z
          next
        next
        
      end function
      
      function render()
        if me.triangle_count = 0 then exit function 
        dim triangles(me.triangle_count) as Triangle3D at me.pTriangles ' -- Overlay array over memory block
        
        int32 i
        tbgl_pushLineWidth 2
        tbgl_pushPolygonLook %GL_LINE
        tbgl_beginpoly %TBGL_TRIANGLES
        ' -- Render triangle one by one
        ' -- Each triangle defined via 3 points
        for i = 1 to me.triangle_count
          tbgl_vertex triangles(i).vertices(1).x, triangles(i).vertices(1).y, triangles(i).vertices(1).z
          tbgl_vertex triangles(i).vertices(2).x, triangles(i).vertices(2).y, triangles(i).vertices(2).z
          tbgl_vertex triangles(i).vertices(3).x, triangles(i).vertices(3).y, triangles(i).vertices(3).z
        next
        tbgl_endpoly
        tbgl_popPolygonLook
        tbgl_popLineWidth
      end function
      
      function _destroy()
        ' -- Free the memory, if necessary
        if me.pProfiles then heap_free(me.pProfiles)
        if me.pTriangles then heap_free(me.pTriangles)
      end function
    
    
    end type
    
    This object allows us to group profiles together and to build and render mesh for them, just like this:
      dim profileA as toboganProfile(2, 1, 180) ' Create wide profile, 180°
      dim profileB as toboganProfile(1, 1, 270) ' Create profile with 270° U shape
      dim profileC as toboganProfile(2, 1, 200) ' Create wide profile, 200°
      
      ' -- Adjust profile position
      profileA.add_pos(0, 0, -1)
      profileB.add_pos(0, 0, 0)
      profileC.add_pos(0, 0, 1)
        
      ' -- Create mesh object and add profiles to it
      dim mesh as ToboganMesh()
      mesh.add_profile(profileA)
      mesh.add_profile(profileB)
      mesh.add_profile(profileC)
      
      ' -- Build the mesh
      mesh.build()
    
      ' -- Draw the mesh
      mesh.render()
    
    The complete code and illustration how it looks attached to this post again.

    The next step? Add profile rotation.


    Petr
    Attached Images Attached Images
    Attached Files Attached Files
    Last edited by Petr Schreiber; 24-03-2019 at 22:42.
    Learn 3D graphics with ThinBASIC, learn TBGL!
    Windows 10 64bit - Intel Core i5-3350P @ 3.1GHz - 16 GB RAM - NVIDIA GeForce GTX 1050 Ti 4GB

  3. #3
    Super Moderator Petr Schreiber's Avatar
    Join Date
    Aug 2005
    Location
    Brno - Czech Republic
    Posts
    7,153
    Rep Power
    736
    In order to rotate the profiles, we have to dive into the math a bit - to matrix calculus, to be specific.


    There are well know matrices for rotation along x, y and z axis, documented for example here, in section Rotation matrices:
    https://www.mathworks.com/help/phased/ref/roty.html


    The transformation is then applied like:
    final_point_column_vector = rotation_matrix * current_point_as_column_vector


    ThinBASIC offers native support for matrix calculus, so we can implement these by using the MAT statement:
    function rotate_by_matrix(angle_deg as float32, rotation_matrix() as float32)
    
    
      float32 column_vector_pos(3, 1)
      float32 column_vector_result(3, 1)
      
      int32 i
      for i = 1 to me.point_count
        column_vector_pos(1, 1) = me.point(i).x,
                                  me.point(i).y,
                                  me.point(i).z
    
    
        mat column_vector_result() = rotation_matrix() * column_vector_pos()
        
        me.point(i).x = column_vector_result(1, 1)
        me.point(i).y = column_vector_result(2, 1)
        me.point(i).z = column_vector_result(3, 1)
      next
    
    
    end function
    
    
    function rotate_x(angle_deg as float32)
      float32 angle_rad = degToRad(angle_deg)
    
    
      float32 matrix_rotation_x(3, 3) = [             1,              0,              0,
                                                      0, cos(angle_rad),-sin(angle_rad),
                                                      0, sin(angle_rad), cos(angle_rad)]
    
    
      me.rotate_by_matrix(angle_deg, matrix_rotation_x)
    end function
    
    
    function rotate_y(angle_deg as float32)
      float32 angle_rad = degToRad(angle_deg)
    
    
      float32 matrix_rotation_y(3, 3) = [ cos(angle_rad),              0, sin(angle_rad),
                                                       0,              1,              0,
                                         -sin(angle_rad),              0, cos(angle_rad)]
    
    
      me.rotate_by_matrix(angle_deg, matrix_rotation_y)
    end function
    
    
    function rotate_z(angle_deg as float32)
      float32 angle_rad = degToRad(angle_deg)
    
    
      float32 matrix_rotation_y(3, 3) = [ cos(angle_rad),-sin(angle_rad),              0,
                                          sin(angle_rad), cos(angle_rad),              0,
                                                       0,              0,              1]
    
    
      me.rotate_by_matrix(angle_deg, matrix_rotation_y)
    end function
    

    In order to construct the tobogan itself, we might prepare some function. It will take as input:
    - mesh to manipulate
    - general height of the tobogan
    - radius of the turns
    - number of turns
    - detail level of angular precision


    function build_tobogan(byRef mesh as ToboganMesh, height as float32 = 5, radius as float32 = 5, turns as float32 = 1, angle_deg_detail as float32 = 10)
      dim main_profile as ToboganProfile(1, 1, 180) ' Create wide profile, 180°
      int32 i
    
    
      float32 angle_rad
      for i = 0 to 360*turns step angle_deg_detail
        angle_rad = degToRad(i)
    
    
        main_profile.reset_to(1, 1, 180)  ' -- Recycle the same profile, this will reset the transformation to default state again
    
    
        main_profile.rotate_z(45) ' -- Roll rotation
        main_profile.rotate_y(-i) ' -- Left/Right rotation
        
        ' -- Placing the profiles along the spiral
        main_profile.add_pos(cos(angle_rad) * radius, height-i/(360*turns)*height, sin(angle_rad) * radius)
        
        ' -- Appending the profile to mesh
        mesh.add_profile(main_profile)
      next
      
      ' -- Build the mesh
      mesh.build()
    end function
    

    The complete solution can be downloaded from the attachement.




    Petr
    Attached Images Attached Images
    Attached Files Attached Files
    Learn 3D graphics with ThinBASIC, learn TBGL!
    Windows 10 64bit - Intel Core i5-3350P @ 3.1GHz - 16 GB RAM - NVIDIA GeForce GTX 1050 Ti 4GB

  4. #4
    thinBasic author ErosOlmi's Avatar
    Join Date
    Sep 2004
    Location
    Milan - Italy
    Age
    57
    Posts
    8,817
    Rep Power
    10
    Great Petr.
    Hope to give you very soon dynamic arrays inside UDTs
    Attached Images Attached Images
    Attached Files Attached Files
    Last edited by ErosOlmi; 28-04-2019 at 10:04.
    www.thinbasic.com | www.thinbasic.com/community/ | help.thinbasic.com
    Windows 10 Pro for Workstations 64bit - 32 GB - Intel(R) Xeon(R) W-10855M CPU @ 2.80GHz - NVIDIA Quadro RTX 3000

  5. #5
    Super Moderator Petr Schreiber's Avatar
    Join Date
    Aug 2005
    Location
    Brno - Czech Republic
    Posts
    7,153
    Rep Power
    736
    Thank you Eros,

    it will make the code much easier to read Yet - being able to demonstrate thinBasic can work around the limitation by custom memory allocation is also cool and not so common in interpreter land


    Petr
    Learn 3D graphics with ThinBASIC, learn TBGL!
    Windows 10 64bit - Intel Core i5-3350P @ 3.1GHz - 16 GB RAM - NVIDIA GeForce GTX 1050 Ti 4GB

Similar Threads

  1. Mesh creation in irrlicht
    By primo in forum Irrlicht
    Replies: 2
    Last Post: 10-11-2017, 16:27
  2. Cloud and mesh processing software ....
    By RobbeK in forum TBGL module by Petr Schreiber
    Replies: 3
    Last Post: 23-03-2014, 14:43
  3. Early black box preview of ray-mesh collisions
    By Petr Schreiber in forum TBGL General
    Replies: 8
    Last Post: 19-10-2008, 11:29

Members who have read this thread: 0

There are no members to list at the moment.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •