Results 1 to 10 of 22

Thread: Periphery Online / MMORTS

Hybrid View

Previous Post Previous Post   Next Post Next Post
  1. #1
    I know the principles behind the skinning but the issue is loading up the data in a consistient and useful way in pascal and getting it to opengl & shaders (or a decent CPU method to deform mesh).
    There is no useful example that works with modern file formats in pascal, there are ancient quake file format examples in glscene and some GLTF examples which are super hard to integrate because they rely on certain way their shaders are done and are hard to just integrate into a custom engine pipeline, and most of these don't even have support to share animations between different meshes, if you want multiple characters you usually need to export animations for each model separatedly.

    If you could make a minimal example of this, all of pascal community could benefit from it.
    This is my game project - Top Down City:
    http://www.pascalgamedevelopment.com...y-Topic-Reboot

    My OpenAL audio wrapper with Intelligent Source Manager to use unlimited:
    http://www.pascalgamedevelopment.com...source+manager

  2. #2
    The animation is too deeply integrated with the game engine. The code is written for internal useage only, it contains many strange and specific structures and names, it will not be understandable to the general public without detailed comments.

    Right now It's too hard and not realistic to quickly make a simple animation example without the game engine itself. Sorry.


    If anyone is interested, here is the Python code I use to export an animation model from Blender to my own binary format:
    Code:
    import os
    import bpy
    import mathutils
    from struct import pack
    from mathutils import Matrix, Vector, Color
    from bpy_extras import io_utils, node_shader_utils
    
    
    
    print('*********************************************************')
    
    oo = bpy.context.scene.objects
    
    
    
    VV = []
    TT = []
    NN = []
    WW = []
    
    II = []
    MM = []
    GG = []
    
    BB = []
    KK = []
    
    ML = []
    MI = []
    MB = []
    MG = []
    
    
    arm_name = 'Armature'
    icon_a = 8
    icon_k = 0
     
     
     
    if bpy.data.objects.find(arm_name) != -1:
        armature = bpy.data.objects[arm_name]
        BB = armature.data.bones[:] 
    
    
    def bone_index( b ):
        for i in range(len(BB)):
            if b == BB[i]:
                return i
        return -1
    
    
    def get_MG( i ):
        M = ML[i] @ MB[i] @ MI[i]
        if KK[i] < 0:
            return M
        else:
            return get_MG(KK[i]) @ M
    
    
    def root_path( _f_name = '' ):
        s = os.path.dirname(bpy.data.filepath)
        return s + '\\' + _f_name
    
    
    def name_compat(name):
        if name is None:
            return 'None'
        else:
            return name.replace(' ', '_')
    
    
    def mesh_triangulate(me):
        import bmesh
        bm = bmesh.new()
        bm.from_mesh(me)
        bmesh.ops.triangulate( bm , faces = bm.faces )
        bm.to_mesh( me )
        bm.free()
    
    
    def aa_add(aa,a):
        for i in range(len(aa)):
            if a == aa[i]:
                return i
        aa.append(a)
        return len(aa)-1
    
    
    def get_mat_tex(m):
        mat_wrap = node_shader_utils.PrincipledBSDFWrapper(m)
        tex_wrap = getattr( mat_wrap , "base_color_texture" , None )
        if tex_wrap:
            image = tex_wrap.image
            if image:
                d0 = os.path.dirname(bpy.data.filepath)
                d1 = os.path.normpath( d0 + image.filepath )
                d2 = os.path.relpath( d1 , start = d0 )
                return d2
        return 'none'
    
    
    def get_w2(v):
        gg = v.groups[:]
        if len(gg) == 0:
            return ( 0 , 1.0 ) , ( 0 , 0.0 )
        if len(gg) == 1:
            return ( gg[0].group , 1.0 ) , ( 0 , 0.0 )
        if len(gg) > 2:
            gg.sort( key = lambda g: -g.weight )
        w1 = gg[0].weight
        w2 = gg[1].weight
        ww = w1 + w2
        return ( gg[0].group , w1/ww ) , ( gg[1].group , w2/ww )
    
    
    def read_mesh(o):
    
        mesh_triangulate(o)
        o.calc_normals_split()
    
        vv_count = len(VV)
        LL = o.loops
        tt = o.uv_layers.active.data[:]
        
        def loop_to_ii(L):
            return LL[L].vertex_index + vv_count , aa_add( TT , tt[L].uv ) , aa_add( NN , LL[L].normal )
        
        gg = [ [[],m,0] for m in o.materials ]
        for f in o.polygons:
            f_ii = tuple( loop_to_ii(L) for L in f.loop_indices )
            gg[f.material_index][0].append( f_ii )
        
        VV.extend( o.vertices[:] )
        
        for g in gg:
            if len(g[0]) > 0:
                g[2] = aa_add( MM , g[1] )
                GG.append(g)
    
    
    def mesh_to_aa():
        for o in oo:
            if o.type == "MESH":
                read_mesh(o.to_mesh())
        for i in range(len(MM)):
            tex_name  = get_mat_tex(MM[i])
            tex_index = aa_add( II , tex_name )
            MM[i] = MM[i] , tex_index
    
    
    def write_arm( fw ):
    
        def write_m( M ):
            t = M.to_translation()
            e = M.to_euler()
            fw( pack('3f', M[0][3] , M[1][3] , M[2][3] ) )
            fw( pack('3f', e[0] , e[1] , e[2] ) )
        
        # icon pos
        fw( pack( '2B' , icon_a , icon_k ) )
        
        # BB
        fw( pack( 'i' , len(BB) ) )
        for b in BB:
            i = bone_index( b.parent )
            KK.append(i)
            fw( pack('i', i ) )
        
        # t_pose
        for i in range(len(BB)):
            b = BB[i]
            M = b.matrix_local.copy()
            ML.append( M.copy() )
            MI.append( M.inverted().copy() )
            MB.append( None )
            write_m(M)
        
        # t_anim
        fw( pack( 'i' , len(bpy.data.actions)-1 ) )
        for i in range( 1 , len(bpy.data.actions) ):
            
            a = bpy.data.actions[i]
            cc = a.fcurves
            a_len = len( cc[0].keyframe_points )
            
            fw( pack('B',len(a.name)) )
            fw( bytes( a.name , 'ansi' ) )
            
            fw( pack( 'i' , a_len ) )   
            
            for j in range( a_len ):
                for k in range(len(BB)):
                    b = BB[k]
                    path_p = 'pose.bones["' + b.name + '"].location'
                    path_q = 'pose.bones["' + b.name + '"].rotation_quaternion'
                    pp = []
                    qq = []
                    for c in cc:
                        if c.data_path == path_p:
                            pp.append(c)
                        if c.data_path == path_q:
                            qq.append(c)
                    v = [c.keyframe_points[j].co[1] for c in pp]
                    p = mathutils.Matrix.Translation( ( v[0] , v[1] , v[2] ) )
                    v = [c.keyframe_points[j].co[1] for c in qq]
                    q = mathutils.Quaternion( ( v[0] , v[1] , v[2] , v[3] ) ) 
                    MB[k] = p @ q.to_matrix().to_4x4()
                for k in range(len(BB)):
                    M = get_MG(k)
                    write_m(M)
    
    
    
    def save_arm( f_name ):
        f = open( f_name , 'bw' )
        fw = f.write
        write_arm( fw )
        f.close()
    
    
    
    def save_to_b2m( f_name , s_format ):
    
        mesh_to_aa()
        
        f = open(f_name,'bw')
        fw = f.write
    
        # format
        fw( pack( 'B' , s_format ) )
    
        # images
        fw( pack( 'i' , len(II) ) )
        for I in II:
            fw( pack('B',len(I)) )
            fw( bytes( I , 'ansi' ) )
    
        # mat
        fw( pack( 'i' , len(MM) ) )
        for M in MM:
            fw( pack( 'i' , M[1] ) )
            s = M[0].name
            fw( pack( 'B' , len(s) ) )
            fw( bytes( s , 'ansi' ) )
    
        # VV
        fw( pack( 'i' , len(VV) ) )
        for v in VV:
            fw( pack('3f', v.co[0] , v.co[1] , v.co[2] ) )
    
        # TT
        fw( pack( 'i' , len(TT) ) )
        for t in TT:
            fw( pack('2f', t[0] , t[1] ) )
    
        # NN
        fw( pack( 'i' , len(NN) ) )
        for n in NN:
            fw( pack('3f', n[0] , n[1] , n[2] ) )
    
        # GG
        fw( pack( 'i' , len(GG) ) )
        for G in GG:
            fw( pack( 'i' , G[2] ) )
            fw( pack( 'i' , len(G[0]) ) )
            for g in G[0]:
                #fw( pack( '9i', g[0][0],g[0][1],g[0][2], g[1][0],g[1][1],g[1][2], g[2][0],g[2][1],g[2][2]  ) )
                fw( pack( '9H', g[0][0],g[0][1],g[0][2], g[1][0],g[1][1],g[1][2], g[2][0],g[2][1],g[2][2]  ) )
        
        # animation ...
        if s_format < 1:
            f.close()
            return
            
        # WW
        for v in VV:
            w = get_w2(v)
            fw( pack('<BfBf', w[0][0] , w[0][1] , w[1][0] , w[1][1] ) )
        
        if s_format == 2:
            write_arm( fw )
    
        f.close()
    
        
    save_to_b2m( root_path('m1.b2m') , 2 )
    #save_arm( 'm1.arm' )
    Last edited by rts111; 11-12-2021 at 03:44 PM.

  3. #3
    Thanks, i will try to make sense of that, it looks like a simple exporter with clearly separated structures, t-pose is also exported so each model has its own t-pose (does this cause you issues if you want to share animations between multiple modems? )
    This is my game project - Top Down City:
    http://www.pascalgamedevelopment.com...y-Topic-Reboot

    My OpenAL audio wrapper with Intelligent Source Manager to use unlimited:
    http://www.pascalgamedevelopment.com...source+manager

  4. #4
    Yes, I am using several models with the same armature, and only one animation collection for all models.

    In fact, t-pose is not used in any way when imported into the game engine. This function in the script only serves to initialize the matrices. The values did not need to be written to the file.

    In the engine, I get matrices directly from positions and rotations:
    Code:
    procedure mat4.AssignEulerTranslation( e,t :vec3 );
    var
     cy,sy,cp,sp,cr,sr :float;
     q                 :vec4;
    begin
    
     LoadIdentity;
    
     cy := cos(e.z * 0.5);
     sy := sin(e.z * 0.5);
     cp := cos(e.y * 0.5);
     sp := sin(e.y * 0.5);
     cr := cos(e.x * 0.5);
     sr := sin(e.x * 0.5);
    
     q.w := cy * cp * cr + sy * sp * sr;
     q.x := cy * cp * sr - sy * sp * cr;
     q.y := sy * cp * sr + cy * sp * cr;
     q.z := sy * cp * cr - cy * sp * sr;
    
     with q do  // m3 AssignQuaternion
     begin
      e00 := 1.0 - ( y * y + z * z ) * 2.0;
      e01 :=       ( x * y + w * z ) * 2.0;
      e02 :=       ( x * z - w * y ) * 2.0;
    
      e10 :=       ( x * y - w * z ) * 2.0;
      e11 := 1.0 - ( x * x + z * z ) * 2.0;
      e12 :=       ( y * z + w * x ) * 2.0;
    
      e20 :=       ( x * z + w * y ) * 2.0;
      e21 :=       ( y * z - w * x ) * 2.0;
      e22 := 1.0 - ( x * x + y * y ) * 2.0;
     end;
    
     e30 := t.x;
     e31 := t.y;
     e32 := t.z;
    
    end;
    For animation, I send these matrices to a simple shader like this:
    Code:
    uniform mat4 mm[32];
    attribute vec3  vv;
    attribute vec3  nn;
    attribute vec2  tt;
    attribute ivec2 bb;
    attribute vec2  ww;
    varying vec2 tex_coord;
    varying vec3 normal;
    void main( void )
    {
     ivec2 ibb = ivec2( bb );
     vec4 v1 = vec4( vv , 1.0 );
     vec4 v2 = ( mm[ibb[0]] * v1 ) * ww[0] + ( mm[ibb[1]] * v1 ) * ww[1];
     vec4 n1 = vec4( nn , 0.0 );
     vec4 n2 = ( mm[ibb[0]] * n1 ) * ww[0] + ( mm[ibb[1]] * n1 ) * ww[1];
     normal      = normalize( gl_NormalMatrix * n2.xyz );
     tex_coord   = tt;
     gl_Position = gl_ModelViewProjectionMatrix * v2;
    }

  5. #5
    I tried the game. It is really awsome! Excellent work, from an design- but also from a programming stand point. The Age of Empires controls and sounds are great, too! - I wonder, as it is MMORTS, what happens if I log off and someone attacks. I will be an easy target in this case, right?

    The source code is not available, right?

    Best regards
    Matthias

  6. #6
    Awesome. Well done!

  7. #7
    Matthias, Jonax - thanks.

    I wonder, as it is MMORTS, what happens if I log off and someone attacks. I will be an easy target in this case, right?
    Yes, at any moment any other player will be able to attack you. For protection, you can build defensive structures such as towers and walls. The wall has a lot of HP, absorbs and reflects most of the damage, and can be upgraded as long as there are enough resources.

    The source code is not available, right?
    Sorry, in order to prevent desync, the code will not be available. It's a multiplayer game, after all.

Bookmarks

Posting Permissions

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