oh yeah mr crabs
[carveJwlIkooP6JGAAIwe30JlM.git] / model.h
diff --git a/model.h b/model.h
index 9788dc1446c080aaf15f71a00beee5fb1b163c8d..faa9f5fd8988483bcc774ba4d70667b1a53d4f2e 100644 (file)
--- a/model.h
+++ b/model.h
@@ -1,38 +1,63 @@
+/*
+ * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+ */
+
 #ifndef MODEL_H
 #define MODEL_H
 
-#include "vg/vg.h"
+#include "common.h"
 
-typedef struct model model;
 typedef struct glmesh glmesh;
-typedef struct submodel submodel;
-typedef struct model_vert model_vert;
-typedef struct model_marker model_marker;
-typedef struct sdf_primative sdf_primative;
-typedef enum esdf_type esdf_type;
 
-#pragma pack(push,1)
-struct model
+typedef struct mdl_vert mdl_vert;
+typedef struct mdl_submesh mdl_submesh;
+typedef struct mdl_material mdl_material;
+typedef struct mdl_node mdl_node;
+typedef struct mdl_header mdl_header;
+typedef struct mdl_animation mdl_animation;
+typedef struct mdl_keyframe mdl_keyframe;
+
+#define MDL_SIZE_MAX          0x1000000
+#define MDL_VERT_MAX          1000000
+#define MDL_INDICE_MAX        1000000
+#define MDL_MATERIAL_MAX      32
+#define MDL_NODE_MAX          4000
+#define MDL_SUBMESH_MAX       8000
+#define MDL_STRING_LENGTH_MAX 64
+
+enum classtype
 {
-   u32 identifier;
-
-   u32 vertex_count,
-       indice_count,
-       layer_count,
-       marker_count;
+   k_classtype_none = 0,
+   k_classtype_gate = 1,
+   k_classtype_block = 2,
+   k_classtype_spawn = 3,
+   k_classtype_water = 4,
+   k_classtype_car_path = 5,
+   k_classtype_instance = 6,
+   k_classtype_capsule = 7,
+   k_classtype_route_node = 8,
+   k_classtype_route = 9,
+   k_classtype_bone = 10,
+   k_classtype_skeleton = 11,
+   k_classtype_skin = 12,
+   k_classtype_achievement_box = 13,
+   k_classtype_audio = 14
 };
 
-struct sdf_primative
+
+#pragma pack(push,1)
+
+struct mdl_vert
 {
-   v4f origin;   /* xyz, yaw */
-   /* Cone: 
-       x  base scale
-       y  height 
-   */
-   v4f info;
+   v3f co,
+       norm;
+   v2f uv;
+   u8  colour[4];
+   u16 weights[4];
+   u8  groups[4];
 };
 
-struct submodel
+struct mdl_submesh
 {
    u32 indice_start,
        indice_count,
@@ -40,86 +65,218 @@ struct submodel
        vertex_count;
    
    boxf bbx;
-   v3f pivot;
-   sdf_primative sdf;
+   u32 material_id;
+};
 
-   enum esdf_type
-   {
-      k_sdf_none = 0,
-      k_sdf_cone,
-      k_sdf_sphere,
-      k_sdf_box
-   }
-   sdf_type;
+struct mdl_material
+{
+   u32 pstr_name;
+};
 
-   char name[32];
+struct mdl_node
+{
+   v3f co;
+   v4f q;
+   v3f s;
+   
+   union{ u32 submesh_start, sub_uid; };
+
+   u32 
+       submesh_count,
+       classtype,
+       offset,
+       parent,
+       pstr_name;
 };
 
-struct model_marker
+struct mdl_keyframe
 {
    v3f co;
    v4f q;
    v3f s;
-   char name[32];
 };
 
-struct model_vert
+struct mdl_animation
 {
-   v3f co,
-       norm;
-   v4f colour;
-   v2f uv;
+   u32 pstr_name,
+       length;
+
+   float rate;
+
+   u32 offset;
+};
+
+struct mdl_header
+{
+   u32 identifier, version, file_length;
+
+   u32 vertex_count, vertex_offset,
+       indice_count, indice_offset,
+       submesh_count, submesh_offset,
+       material_count, material_offset,
+       node_count, node_offset,
+       anim_count, anim_offset,
+       strings_offset, entdata_offset, animdata_offset;
 };
+
+/* 
+ * Entity data structures
+ */
+
+struct classtype_block
+{
+   boxf bbx;
+};
+
+struct classtype_gate
+{
+   u32 target;
+   v3f dims;
+};
+
+struct classtype_spawn
+{
+   u32 target;
+};
+
+struct classtype_water
+{
+   u32 temp;
+};
+
+struct classtype_car_path
+{
+   u32 target, target1;
+};
+
+struct classtype_instance
+{
+   u32 pstr_file;
+};
+
+struct classtype_capsule
+{
+   float height, radius;
+};
+
+struct classtype_route_node
+{
+   u32 target, target1;
+};
+
+struct classtype_route
+{
+   u32 id_start;
+   v3f colour;
+};
+
+struct classtype_bone
+{
+   u32 deform,
+       ik_target,
+       ik_pole,
+       collider,
+       use_limits;
+
+   v3f angle_limits[2];
+   boxf hitbox;
+};
+
+struct classtype_skeleton
+{
+   u32 channels,
+       ik_count,
+       collider_count,
+       anim_start,
+       anim_count;
+};
+
+struct classtype_skin
+{
+   u32 skeleton;
+};
+
+struct classtype_achievement_box
+{
+   u32 pstr_name,
+       trigger;
+};
+
+struct classtype_audio
+{
+   u32 pstr_file,
+       flags;
+
+   float volume;
+};
+
 #pragma pack(pop)
 
+/*
+ * Simple mesh interface for OpenGL
+ */
+
 struct glmesh
 {
    GLuint vao, vbo, ebo;
    u32 indice_count;
+   u32 loaded;
 };
 
-#define VERTEX_STANDARD_ATTRIBUTES                    \
-       "layout (location=0) in vec3 a_co;"                \
-   "layout (location=1) in vec3 a_norm;"              \
-   "layout (location=2) in vec4 a_colour;"            \
-   "layout (location=3) in vec2 a_uv;"
-
 static void mesh_upload( glmesh *mesh,
-      model_vert *verts, u32 vert_count,
-      u32 *indices, u32 indice_count )
+                         mdl_vert *verts, u32 vert_count,
+                         u32 *indices, u32 indice_count )
 {
+   //assert( mesh->loaded == 0 );
+
    glGenVertexArrays( 1, &mesh->vao );
    glGenBuffers( 1, &mesh->vbo );
    glGenBuffers( 1, &mesh->ebo );
    glBindVertexArray( mesh->vao );
 
+   size_t stride = sizeof(mdl_vert);
+
    glBindBuffer( GL_ARRAY_BUFFER, mesh->vbo );
-   glBufferData( GL_ARRAY_BUFFER, vert_count*sizeof(model_vert), 
-         verts, GL_STATIC_DRAW );
+   glBufferData( GL_ARRAY_BUFFER, vert_count*stride, verts, GL_STATIC_DRAW );
 
    glBindVertexArray( mesh->vao );
    glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mesh->ebo );
    glBufferData( GL_ELEMENT_ARRAY_BUFFER, indice_count*sizeof(u32),
          indices, GL_STATIC_DRAW );
    
-   glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 
-         sizeof(model_vert), (void*)0 );
+   /* 0: coordinates */
+   glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0 );
    glEnableVertexAttribArray( 0 );
 
+   /* 1: normal */
    glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, 
-         sizeof(model_vert), (void *)offsetof(model_vert, norm) );
+         stride, (void *)offsetof(mdl_vert, norm) );
    glEnableVertexAttribArray( 1 );
 
-   glVertexAttribPointer( 2, 4, GL_FLOAT, GL_FALSE, 
-         sizeof(model_vert), (void *)offsetof(model_vert, colour) );
+   /* 2: uv */
+   glVertexAttribPointer( 2, 2, GL_FLOAT, GL_FALSE, 
+         stride, (void *)offsetof(mdl_vert, uv) );
    glEnableVertexAttribArray( 2 );
 
-   glVertexAttribPointer( 3, 2, GL_FLOAT, GL_FALSE, 
-         sizeof(model_vert), (void *)offsetof(model_vert, uv) );
+   /* 3: colour */
+   glVertexAttribPointer( 3, 4, GL_UNSIGNED_BYTE, GL_TRUE, 
+         stride, (void *)offsetof(mdl_vert, colour) );
    glEnableVertexAttribArray( 3 );
-   
-   VG_CHECK_GL();
+
+   /* 4: weights */
+   glVertexAttribPointer( 4, 4, GL_UNSIGNED_SHORT, GL_TRUE, 
+         stride, (void *)offsetof(mdl_vert, weights) );
+   glEnableVertexAttribArray( 4 );
+
+   /* 5: groups */
+   glVertexAttribIPointer( 5, 4, GL_UNSIGNED_BYTE,
+         stride, (void *)offsetof(mdl_vert, groups) );
+   glEnableVertexAttribArray( 5 );
+
+   VG_CHECK_GL_ERR();
+
    mesh->indice_count = indice_count;
+   mesh->loaded = 1;
 }
 
 static void mesh_bind( glmesh *mesh )
@@ -138,79 +295,235 @@ static void mesh_draw( glmesh *mesh )
    mesh_drawn( 0, mesh->indice_count );
 }
 
+static void mesh_free( glmesh *mesh )
+{
+   if( mesh->loaded )
+   {
+      glDeleteVertexArrays( 1, &mesh->vao );
+      glDeleteBuffers( 1, &mesh->ebo );
+      glDeleteBuffers( 1, &mesh->vbo );
+   }
+}
+
+
 /*
- * Helper functions for file offsets
- * TODO: Revise this
+ * Model implementation
  */
-static submodel *model_get_submodel( model *mdl, int id )
+
+static mdl_header *mdl_load( const char *path )
 {
-   return ((submodel*)(mdl+1)) + id;
+   i64 size;
+   mdl_header *header = vg_asset_read_s( path, &size );
+
+   /* 
+    * Check file is valid
+    */
+   if( !header )
+   {
+      vg_error( "Could not open '%s'\n", path );
+      return NULL;
+   }
+
+   if( size < sizeof(mdl_header) )
+   {
+      vg_free( header );
+      vg_error( "Invalid file '%s' (too small for header)\n", path );
+      return NULL;
+   }
+
+   if( header->file_length != size )
+   {
+      vg_error( "Invalid file '%s'"
+                "(wrong .file_length, %ub != real file size %ub)\n", 
+                path, header->file_length, size );
+      vg_free( header );
+      return NULL;
+   }
+
+   /*
+    * Validate offsets and memory sections, to ensure all arrays are in-bounds,
+    * and that they do not overlap.
+    */
+
+   struct memregion
+   {
+      const char *desc;
+      u32 count, max_count, size, offset;
+   }
+   regions[] = {
+      { 
+         "Vertices",
+         header->vertex_count, MDL_VERT_MAX, 
+         sizeof(mdl_vert), header->vertex_offset 
+      },
+      {
+         "Indices",
+         header->indice_count, MDL_INDICE_MAX,
+         sizeof(u32), header->indice_offset 
+      },
+      { 
+         "Submesh",
+         header->submesh_count, MDL_SUBMESH_MAX,
+         sizeof(mdl_submesh), header->submesh_offset 
+      },
+      { 
+         "Materials",
+         header->material_count, MDL_MATERIAL_MAX,
+         sizeof(mdl_material), header->material_offset
+      },
+      { 
+         "Nodes",
+         header->node_count, MDL_NODE_MAX,
+         sizeof(mdl_node), header->node_count
+      }
+   };
+
+   for( int i=0; i<vg_list_size(regions); i++ )
+   {
+      struct memregion *ri = &regions[i];
+
+      if( ri->count == 0 )
+         continue;
+
+      if( ri->count > ri->max_count )
+      {
+         vg_free( header );
+         vg_error( "'%s': '%s' buffer exceeds the maximum (%u/%u)\n",
+               path, ri->desc, ri->count, ri->max_count );
+         return NULL;
+      }
+
+      if( ri->offset >= header->file_length )
+      {
+         vg_free( header );
+         vg_error( "'%s': '%s' buffer offset is out of range\n",
+               path, ri->desc );
+         return NULL;
+      }
+
+      if( ri->offset + ri->size*ri->count > header->file_length )
+      {
+         vg_free( header );
+         vg_error( "'%s': '%s' buffer size is out of range\n",
+               path, ri->desc );
+         return NULL;
+      }
+
+      for( int j=0; j<vg_list_size(regions); j++ )
+      {
+         struct memregion *rj = &regions[j];
+         if( rj->count == 0 )
+            continue;
+
+         if( ri->offset >= rj->offset && 
+               (ri->offset+ri->size*ri->count < rj->offset+rj->size*rj->count))
+         {
+            vg_free( header );
+            vg_error( "'%s': '%s' buffer overlaps '%s'\n",
+                  path, ri->desc, rj->desc );
+            return NULL;
+         }
+      }
+   }
+
+   /*
+    * Pointer validation TODO(workshop)
+    */
+
+   /*
+    * strings TODO(workshop)
+    */
+
+   return header;
 }
 
-static model_marker *model_get_marker( model *mdl, int id )
+static void *mdl_baseptr( mdl_header *mdl, u32 offset )
 {
-   return ((model_marker*)model_get_submodel(mdl,mdl->layer_count)) + id;
+   return (void *)mdl + offset;
 }
 
-static model_vert *model_vertex_base( model *mdl )
+static const char *mdl_pstr( mdl_header *mdl, u32 pstr )
 {
-   return (model_vert *)model_get_marker( mdl, mdl->marker_count );
+   return (const char *)(mdl_baseptr( mdl, mdl->strings_offset )) + pstr;
 }
 
-static u32 *model_indice_base( model *mdl )
+static mdl_node *mdl_node_from_id( mdl_header *mdl, u32 id )
 {
-   return (u32 *)(model_vertex_base( mdl ) + mdl->vertex_count);
+   return ((mdl_node *)mdl_baseptr( mdl, mdl->node_offset )) + id;
 }
 
-static model_vert *submodel_vert_data( model *mdl, submodel *sub )
+static mdl_node *mdl_node_from_name( mdl_header *mdl, const char *name )
 {
-   return model_vertex_base(mdl) + sub->vertex_start;
+   for( int i=0; i<mdl->node_count; i++ )
+   {
+      mdl_node *pnode = mdl_node_from_id( mdl, i );
+      
+      if( !strcmp( name, mdl_pstr( mdl, pnode->pstr_name )) )
+         return pnode;
+   }
+   
+   return NULL;
 }
 
-static u32 *submodel_indice_data( model *mdl, submodel *sub )
+static mdl_submesh *mdl_submesh_from_id( mdl_header *mdl, u32 id )
 {
-   return model_indice_base(mdl) + sub->indice_start;
+   if( id >= mdl->submesh_count )
+      return NULL;
+
+   return ((mdl_submesh *)mdl_baseptr( mdl, mdl->submesh_offset )) + id;
 }
 
-static submodel *submodel_get( model *mdl, const char *name )
+static mdl_submesh *mdl_node_submesh( mdl_header *mdl, mdl_node *node, u32 i )
 {
-   for( int i=0; i<mdl->layer_count; i++ )
-   {
-      submodel *pmdl =model_get_submodel(mdl,i);
+   if( i >= node->submesh_count )
+      return NULL;
 
-      if( !strcmp( pmdl->name, name ) )
-         return pmdl;
-   }
-   
-   return NULL;
+   return mdl_submesh_from_id( mdl, node->submesh_start+i );
 }
 
-static model_marker *model_marker_get( model *mdl, const char *name )
+static u32 *mdl_submesh_indices( mdl_header *mdl, mdl_submesh *sm )
 {
-   for( int i=0; i<mdl->marker_count; i++ )
-   {
-      model_marker *mk = model_get_marker( mdl,i );
+   return ((u32 *)mdl_baseptr( mdl, mdl->indice_offset )) + sm->indice_start;
+}
 
-      if( !strcmp( mk->name, name ) )
-         return mk;
-   }
-   
-   return NULL;
+static mdl_vert *mdl_submesh_vertices( mdl_header *mdl, mdl_submesh *sm )
+{
+   return ((mdl_vert *)mdl_baseptr(mdl,mdl->vertex_offset)) + sm->vertex_start;
 }
 
-static void submodel_draw( submodel *sm )
+static mdl_material *mdl_material_from_id( mdl_header *mdl, u32 id )
 {
-   mesh_drawn( sm->indice_start, sm->indice_count );
+   return ((mdl_material *)mdl_baseptr(mdl,mdl->material_offset)) + id;
+}
+
+static mdl_animation *mdl_animation_from_id( mdl_header *mdl, u32 id )
+{
+   return ((mdl_animation *)mdl_baseptr(mdl,mdl->anim_offset)) + id;
+}
+
+static void mdl_node_transform( mdl_node *pnode, m4x3f transform )
+{
+   q_m3x3( pnode->q, transform );
+   v3_muls( transform[0], pnode->s[0], transform[0] );
+   v3_muls( transform[1], pnode->s[1], transform[1] );
+   v3_muls( transform[2], pnode->s[2], transform[2] );
+   v3_copy( pnode->co, transform[3] );
+}
+
+static void mdl_unpack_submesh( mdl_header *mdl, glmesh *mesh, mdl_submesh *sm )
+{
+   mesh_upload( mesh, mdl_submesh_vertices( mdl, sm ), sm->vertex_count,
+                      mdl_submesh_indices( mdl, sm ), sm->indice_count );
 }
 
-static void model_unpack( model *model, glmesh *mesh )
+static void mdl_unpack_glmesh( mdl_header *mdl, glmesh *mesh )
 {
-   u32 offset = model_get_submodel( model, 0 )->vertex_count;
+   u32 offset = mdl_submesh_from_id( mdl, 0 )->vertex_count;
 
-   for( int i=1; i<model->layer_count; i++ )
+   for( int i=1; i< mdl->submesh_count; i++ )
    {
-      submodel *sm = model_get_submodel( model, i );
-      u32 *indices = submodel_indice_data( model, sm );
+      mdl_submesh *sm = mdl_submesh_from_id( mdl, i );
+      u32 *indices =    mdl_submesh_indices( mdl, sm );
 
       for( u32 j=0; j<sm->indice_count; j++ )
          indices[j] += offset;
@@ -218,15 +531,64 @@ static void model_unpack( model *model, glmesh *mesh )
       offset += sm->vertex_count;
    }
 
-   mesh_upload( mesh, model_vertex_base( model ), model->vertex_count,
-                       model_indice_base( model ), model->indice_count );
+   mdl_vert *vertex_base = mdl_baseptr( mdl, mdl->vertex_offset );
+   u32 *indice_base = mdl_baseptr( mdl, mdl->indice_offset );
+
+   mesh_upload( mesh, vertex_base, mdl->vertex_count,
+                      indice_base, mdl->indice_count ); 
 }
 
-static void mesh_free( glmesh *mesh )
+static void mdl_draw_submesh( mdl_submesh *sm )
+{
+   mesh_drawn( sm->indice_start, sm->indice_count );
+}
+
+static void *mdl_get_entdata( mdl_header *mdl, mdl_node *pnode )
 {
-   glDeleteVertexArrays( 1, &mesh->vao );
-   glDeleteBuffers( 1, &mesh->ebo );
-   glDeleteBuffers( 1, &mesh->vbo );
+   return mdl_baseptr( mdl, mdl->entdata_offset ) + pnode->offset;
 }
 
+static mdl_keyframe *mdl_get_animdata( mdl_header *mdl, mdl_animation *anim )
+{
+   return mdl_baseptr( mdl, mdl->animdata_offset ) + anim->offset;
+}
+
+static void mdl_link_materials( mdl_header *root, mdl_header *child )
+{
+   u32 lookup[MDL_MATERIAL_MAX];
+   
+   for( int i=0; i<child->material_count; i++ )
+   {
+      mdl_material *mi = mdl_material_from_id( child, i );
+      const char *si = mdl_pstr( child, mi->pstr_name );
+
+      lookup[i] = 0;
+
+      for( int j=0; j<root->material_count; j++ )
+      {
+         mdl_material *mj = mdl_material_from_id( root, j );
+         const char *sj = mdl_pstr( root, mj->pstr_name );
+         
+         if( !strcmp( si, sj ) )
+         {
+            lookup[i] = j;
+            break;
+         }
+      }
+
+      if( lookup[i] == 0 && i != 0 )
+      {
+         vg_warn( "Could not link material '%s' (not present in root model)\n", 
+                   si );
+      }
+   }
+
+   for( int i=0; i<child->submesh_count; i++ )
+   {
+      mdl_submesh *sm = mdl_submesh_from_id( child, i );
+      sm->material_id = lookup[sm->material_id];
+   }
+}
+
+
 #endif