/*
- CONVEXER v0.9
+ CONVEXER v0.95
A GNU/Linux-first Source1 Hammer replacement
built with Blender, for mapmakers
Copyright (C) 2022 Harry Godden (hgn)
+LICENSE: GPLv3.0, please see COPYING and LICENSE for more information
+
Features:
- Brush decomposition into convex pieces for well defined geometry
- Freely form displacements without limits
- Compile models and model groups easily
- It runs at an ok speed!
- Light patch BSP files; remove unwanted realtime effects
- - Fastest VTF compressor (thanks to Richgel999 and stb)
+ - Bestest VTF compressor (thanks to Richgel999 and stb)
+ - Pack content automatically
Program structure:
IMPLEMENTATION
*/
-#define CXR_API
#define CXR_EPSILON 0.001
#define CXR_PLANE_SIMILARITY_MAX 0.998
#define CXR_BIG_NUMBER 1e300
#include <stdio.h>
#include <math.h>
-#include <stdint.h>
-#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
-typedef uint8_t u8;
-typedef uint16_t u16;
-typedef uint32_t u32;
-typedef uint64_t u64;
-typedef int8_t i8;
-typedef int16_t i16;
-typedef int32_t i32;
-typedef int64_t i64;
-
-typedef unsigned int uint;
-
-typedef double v2f[2];
-typedef double v3f[3];
-typedef double v4f[4];
-typedef v3f m3x3f[3];
-typedef v3f m4x3f[4];
-typedef v3f boxf[2];
-
+#include "cxr_types.h"
#include "cxr_math.h"
#include "cxr_mem.h"
+#include "cxr_log.h"
+
+#ifdef CXR_VALVE_BIN
+ #include "cxr_valve_bin.h"
+#endif
typedef struct cxr_world cxr_world;
typedef struct cxr_solid cxr_solid;
#ifdef CXR_VALVE_MAP_FILE
typedef struct cxr_vdf cxr_vdf;
typedef struct cxr_texinfo cxr_texinfo;
+ typedef struct cxr_visgroup cxr_visgroup;
typedef struct cxr_vmf_context cxr_vmf_context;
#endif /* CXR_VALVE_MAP_FILE */
struct cxr_edge
{
i32 i0, i1;
- i32 freestyle;
+ i32 freestyle, sharp;
}
*edges;
i32 index,
edge_index;
v2f uv;
+ double alpha;
}
*loops;
struct cxr_material
{
i32 res[2];
- const char *name;
+ char *name;
}
*materials;
edge_index,
index;
v2f uv;
+ float alpha;
};
struct cxr_solid
/* Simple mesh type mainly for debugging */
struct cxr_tri_mesh
{
- v3f *vertices;
+ v3f *vertices,
+ *normals;
+ v2f *uvs;
v4f *colours;
+
i32 *indices,
indices_count,
vertex_count;
double winding;
};
+struct cxr_visgroup
+{
+ const char *name;
+};
+
/*
* Simplified VDF writing interface. No allocations or nodes, just write to file
*/
*detailvbsp,
*detailmaterial;
+ cxr_visgroup *visgroups;
+ i32 visgroup_count;
+
/* Transform settings */
double scale;
v3f offset;
- i32 lightmap_scale;
+ i32 lightmap_scale,
+ visgroupid;
/* Current stats */
i32 brush_count,
k_soliderr_no_solids,
k_soliderr_degenerate_implicit,
k_soliderr_non_coplanar_vertices,
- k_soliderr_non_convex_poly
+ k_soliderr_non_convex_poly,
+ k_soliderr_bad_result,
+ k_soliderr_invalid_input
};
/*
const char *cxr_build_time = __DATE__ " @" __TIME__;
#endif
-static void (*cxr_log_func)(const char *str);
-static void (*cxr_line_func)( v3f p0, v3f p1, v4f colour );
+#if _WIN32 || _WIN64
+#if _WIN64
+#else
+#warning 32 bit is not supported in blender 3.0
+#endif
+#endif
static int cxr_range(int x, int bound)
{
#ifdef CXR_DEBUG
-static void cxr_log( const char *fmt, ... )
-{
- char buf[512];
-
- va_list args;
- va_start( args, fmt );
- vsnprintf( buf, sizeof(buf)-1, fmt, args );
- va_end(args);
-
- if( cxr_log_func )
- cxr_log_func( buf );
-
- fputs(buf,stdout);
-}
-
static void cxr_debug_line( v3f p0, v3f p1, v4f colour )
{
if( cxr_line_func )
}
fprintf( fp, "};\n" );
- fprintf( fp, "struct cxr_static_loop test_loops[] = {\n" );
+ fprintf( fp, "cxr_static_loop test_loops[] = {\n" );
for( int i=0; i<src->loop_count; i ++ )
{
- fprintf( fp, " {%d, %d},\n",
+ fprintf( fp, " {%d, %d, {%f, %f}, %f},\n",
src->loops[i].index,
- src->loops[i].edge_index);
+ src->loops[i].edge_index,
+ src->loops[i].uv[0],
+ src->loops[i].uv[1],
+ src->loops[i].alpha );
}
fprintf( fp, "};\n" );
- fprintf( fp, "struct cxr_polygon test_polys[] = {\n" );
+ fprintf( fp, "cxr_polygon test_polys[] = {\n" );
for( int i=0; i <src->poly_count; i++ )
{
fprintf( fp, " {%d, %d, {%f, %f, %f}, {%f, %f, %f}},\n",
}
fprintf( fp, "};\n" );
- fprintf( fp, "struct cxr_edge test_edges[] = {\n" );
+ fprintf( fp, "cxr_edge test_edges[] = {\n" );
for( int i=0; i<src->edge_count; i++ )
{
- fprintf( fp, " {%d, %d, %d},\n",
+ fprintf( fp, " {%d, %d, %d, %d},\n",
src->edges[i].i0,
src->edges[i].i1,
- src->edges[i].freestyle
+ src->edges[i].freestyle,
+ src->edges[i].sharp
);
}
fprintf( fp, "};\n" );
- fprintf( fp, "struct cxr_static_mesh test_mesh = {\n" );
+ fprintf( fp, "cxr_static_mesh test_mesh = {\n" );
fprintf( fp, " .vertices = test_verts,\n" );
fprintf( fp, " .loops = test_loops,\n" );
fprintf( fp, " .edges = test_edges,\n" );
{
cxr_edge *orig_edge = &mesh->edges[ orig_edge_id ];
edge.freestyle = orig_edge->freestyle;
+ edge.sharp = orig_edge->sharp;
}
else
{
edge.freestyle = 0;
+ edge.sharp = 0;
}
cxr_ab_push( &new_edges, &edge );
if( *edge == -1 )
{
*edge = i;
- break;
+ goto next;
}
}
+
+ /* Overflowed edge mapping... Duplicated faces. */
+ free( polygon_edge_map );
+ return 0;
+
+ next:;
}
}
for( int i = 0; i < mesh->abpolys.count; i ++ )
edge_tagged[lp->edge_index] = 0;
cxr_polygon *polya = &mesh->polys[ lp->poly_left ],
- *polyb = &mesh->polys[ lp->poly_right ];
+ *polyb = &mesh->polys[ lp->poly_right ];
v4f planeb;
normal_to_plane(polyb->normal, polyb->center, planeb);
struct temp_manifold *manifold
){
cxr_loop **edge_list = malloc( sizeof(*edge_list) * solid->edge_count );
+ int *temp_solid = malloc( solid->count *sizeof(int) );
+ int temp_solid_len = 0;
int init_reverse = 0;
int unique_edge_count = 0;
+
+ /* Try remove splitting faces first */
+ {
+ int split_total = 0;
+ for( int j=0; j<solid->count; j++ )
+ {
+ cxr_polygon *poly = &mesh->polys[ solid_buffer[solid->start+j] ];
+ int interior_count = 0;
+
+ for( int k=0; k<poly->loop_total; k++ )
+ {
+ cxr_loop *loop = &mesh->loops[ poly->loop_start+k ];
+
+ for( int l=0; l<solid->count; l++ )
+ if( loop->poly_right == solid_buffer[solid->start+l] )
+ {
+ interior_count ++;
+ goto next;
+ }
+
+ next:;
+ }
+
+ if( interior_count < poly->loop_total-1 )
+ {
+ split_total ++;
+ continue;
+ }
+
+ temp_solid[ temp_solid_len ++ ] = solid_buffer[solid->start+j];
+ }
+
+ if( temp_solid_len < 3 || (split_total & 0x2) /* unkown reasons */ )
+ {
+ }
+ else
+ {
+ /* Overwrite original solid */
+ for( int j=0; j<temp_solid_len; j++ )
+ solid_buffer[ solid->start+j ] = temp_solid[ j ];
- /* Gather list of unique edges */
+ solid->count = temp_solid_len;
+ }
+
+ free( temp_solid );
+ }
for( int j=0; j<solid->count; j++ )
{
cxr_polygon *poly = &mesh->polys[ solid_buffer[solid->start+j] ];
+ /* when discarding, if a face has only one loop that points outwards,
+ * we keep it */
+
+
for( int k=0; k<poly->loop_total; k++ )
{
cxr_loop *loop = &mesh->loops[ poly->loop_start+k ];
return 1;
}
-/*
- * Convexer's main algorithm
- *
- * Return the best availible convex solid from mesh, and patch the existing mesh
- * to fill the gap where the new mesh left it.
- *
- * Returns NULL if shape is already convex or empty.
- * This function will not preserve edge data such as freestyle, sharp etc.
- */
-static cxr_mesh *cxr_pull_best_solid(
- cxr_mesh *mesh,
- int preserve_more_edges,
- enum cxr_soliderr *err )
+static int cxr_reflex_err( cxr_mesh *mesh )
{
- *err = k_soliderr_none;
+ int error = 0;
+ int *reflex_check = cxr_mesh_reflex_edges( mesh );
+
+ v3f *temp = cxr_ab_ptr(mesh->p_abverts, 0);
+
+ for( int i=0; i<mesh->abedges.count; i++ )
+ {
+ if( reflex_check[i] )
+ {
+ cxr_debug_line( temp[mesh->edges[i].i0],
+ temp[mesh->edges[i].i1],
+ colour_error );
+ error ++;
+ }
+ }
+ free( reflex_check );
+ return error;
+}
+
+static int cxr_non_manifold_err( cxr_mesh *mesh )
+{
if( !cxr_mesh_link_loops(mesh) )
{
#ifdef CXR_DEBUG
for( int i=0; i<mesh->abloops.count; i++ )
{
cxr_loop *lp = &mesh->loops[i];
+ cxr_edge *edge = &mesh->edges[lp->edge_index];
+ cxr_debug_line( verts[edge->i0], verts[edge->i1], colours_random[1] );
if( lp->poly_left == -1 || lp->poly_right == -1 )
{
- cxr_edge *edge = &mesh->edges[lp->edge_index];
cxr_debug_line( verts[edge->i0], verts[edge->i1], colour_error );
}
}
#endif
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Convexer's main algorithm
+ *
+ * Return the best availible convex solid from mesh, and patch the existing mesh
+ * to fill the gap where the new mesh left it.
+ *
+ * Returns NULL if shape is already convex or empty.
+ * This function will not preserve edge data such as freestyle, sharp etc.
+ */
+static cxr_mesh *cxr_pull_best_solid(
+ cxr_mesh *mesh,
+ int preserve_more_edges,
+ enum cxr_soliderr *err )
+{
+ *err = k_soliderr_none;
+
+ if( cxr_non_manifold_err( mesh ) )
+ {
*err = k_soliderr_non_manifold;
return NULL;
}
-
+
int *edge_tagged = cxr_mesh_reflex_edges( mesh );
int *vertex_tagged = cxr_mesh_reflex_vertices( mesh );
struct temp_manifold manifold;
cxr_link_manifold( mesh, solid, solid_buffer, &manifold);
-
+
if( manifold.status == k_manifold_err )
{
*err = k_soliderr_bad_manifold;
free(solid_buffer);
free(candidates);
free(best_manifold.loops);
-
+
+ /*
+ * Do final checks on the mesh to make sure we diddn't introduce any
+ * errors
+ */
+ if( cxr_non_manifold_err( pullmesh ) || cxr_reflex_err( pullmesh ) )
+ {
+ *err = k_soliderr_bad_result;
+ return NULL;
+ }
+
return pullmesh;
}
free(candidates);
free(best_manifold.loops);
+ if( cxr_non_manifold_err( mesh ) || cxr_reflex_err( mesh ) )
+ *err = k_soliderr_bad_result;
+
return NULL;
}
lp->index = src->loops[i].index;
lp->edge_index = src->loops[i].edge_index;
v2_copy( src->loops[i].uv, lp->uv );
+ lp->alpha = src->loops[i].alpha;
}
abverts->count = src->vertex_count;
cxr_ab_free( &world->abverts );
cxr_ab_free( &world->absolids );
- free( world->materials );
+
+ if( world->materials )
+ {
+ for( int i=0; i<world->material_count; i++ )
+ free( world->materials[i].name );
+
+ free( world->materials );
+ }
free( world );
}
out->colours = malloc( sizeof(v4f)*out->vertex_count );
out->vertices = malloc( sizeof(v3f)*out->vertex_count );
out->indices = malloc( sizeof(i32)*out->indices_count );
+ out->uvs = NULL;
+ out->normals = NULL;
v3f *overts = out->vertices;
v4f *colours = out->colours;
free( mesh->colours );
free( mesh->indices );
free( mesh->vertices );
+ free( mesh->normals );
+ free( mesh->uvs );
free( mesh );
}
CXR_API cxr_world *cxr_decompose( cxr_static_mesh *src, i32 *perrcode )
{
+ /* Make sure data is in the mesh and isn't empty */
+ if( !src->edge_count || !src->loop_count || !src->poly_count )
+ {
+ cxr_log( "Error %d\n", k_soliderr_invalid_input );
+ if( perrcode )
+ *perrcode = k_soliderr_invalid_input;
+
+ return NULL;
+ }
+
u32 error = 0x00;
cxr_world *world = malloc( sizeof(*world) );
size_t dsize = sizeof(cxr_material) * src->material_count;
world->materials = malloc( dsize );
memcpy( world->materials, src->materials, dsize );
+
+ for( int i=0; i<src->material_count; i++ )
+ {
+ world->materials[i].name = malloc(strlen(src->materials[i].name) +1);
+ strcpy( world->materials[i].name, src->materials[i].name );
+ }
+ world->material_count = src->material_count;
}
else world->materials = NULL;
}
}
+ /* Collect alphas from loops. This discards hard blend information */
+ for( int i=0; i<mesh->abloops.count; i++ )
+ {
+ cxr_loop *loop = &mesh->loops[i];
+ vertinfo[loop->index].alpha = loop->alpha * 255.0;
+ }
+
v3f refv, refu, refn;
v3_zero(refv); v3_zero(refu); v3_zero(refn);
* TODO(harry): Error checking is needed here for bad input data
*/
- int dispedge[16];
+ int dispedge[17];
v2f corner_uvs[4];
int dispedge_count;
int disp_count = 0;
if( !newvert )
{
+ free( graph );
+ free( vertinfo );
return 0;
}
}
}
}
-#ifdef CXR_DEBUG
- cxr_log( "Broken displacement!\n" );
-#endif
free( graph );
free( vertinfo );
return 0;
v3_muladds( face_center, refn, 1.5, pn );
v3_muladds( face_center, refv, 1.5, pv );
v3_muladds( face_center, refu, 1.5, pu );
+
+ v3_muladds( face_center, refn, 2.0, face_center );
}
/* Create world coordinates */
v3f normals[25];
double distances[25];
+ double alphas[25];
v3f lside0, lside1, lref, vdelta, vworld;
double tx, ty;
tx = (double)k/(double)(5-1);
v3_lerp( lside0, lside1, tx, lref );
v3_muls( verts[grid[index]], ctx->scale, vworld );
- v3_add( ctx->offset, vworld, ctx->offset );
+ v3_add( ctx->offset, vworld, vworld );
v3_sub( vworld, lref, vdelta );
v3_copy( vdelta, normals[index] );
v3_normalize( normals[index] );
distances[index] = v3_dot( vdelta, normals[index] );
+
+ alphas[index] = vertinfo[grid[index]].alpha;
}
}
world_corners[side[0]] );
cxr_vdf_kv( output, "material", matptr->name );
-
cxr_vdf_kaxis( output, "uaxis",
texinfo_shared.uaxis,
texinfo_shared.offset[0],
cxr_vdf_karrdouble( output, "row", k, &distances[k*5], 5 );
cxr_vdf_edon( output );
+ cxr_vdf_node( output, "alphas" );
+ for( int k=0; k<5; k++ )
+ cxr_vdf_karrdouble( output, "row", k, &alphas[k*5], 5 );
+ cxr_vdf_edon( output );
+
/*
* TODO: This might be needed for the compilers. Opens fine in
* hammer
"\"row%d\" \"0 0 1 0 0 1 0 0 1 0 0 1 0 0 1\"\n", k );
cxr_vdf_edon( output );
- cxr_vdf_node( output, "alphas" );
- for( int k=0; k<5; k++ )
- cxr_vdf_printf( output, "\"row%d\" \"0 0 0 0 0\"\n", k );
- cxr_vdf_edon( output );
-
cxr_vdf_node( output, "triangle_tags" );
for( int k=0; k<5-1; k++ )
cxr_vdf_printf( output,
cxr_vdf_node( output, "editor");
cxr_vdf_colour255( output, "color",
colours_random[cxr_range(ctx->brush_count,8)]);
-
+
+ cxr_vdf_ki32( output, "visgroupid", ctx->visgroupid );
cxr_vdf_ki32( output, "visgroupshown",1);
cxr_vdf_ki32( output, "visgroupautoshown",1);
cxr_vdf_edon( output );
cxr_vdf_edon( output );
cxr_vdf_node( output, "visgroups" );
+
+ for( int i=0; i<ctx->visgroup_count; i++ )
+ {
+ cxr_vdf_node( output, "visgroup" );
+ cxr_vdf_kv( output, "name", ctx->visgroups[i].name );
+ cxr_vdf_ki32( output, "visgroupid", i+1 );
+ cxr_vdf_edon( output );
+ }
+
cxr_vdf_edon( output );
cxr_vdf_node( output, "viewsettings" );
if( solid->displacement )
{
- cxr_write_disp( solid->pmesh, world, ctx, output );
+ if( !cxr_write_disp( solid->pmesh, world, ctx, output ) )
+ {
+ cxr_log( "Warning: Invalid displacement\n" );
+ }
continue;
}
cxr_vdf_colour255( output, "color",
colours_random[cxr_range(ctx->brush_count,8)]);
+ cxr_vdf_ki32( output, "visgroupid", ctx->visgroupid );
cxr_vdf_ki32( output, "visgroupshown", 1 );
cxr_vdf_ki32( output, "visgroupautoshown", 1 );
cxr_vdf_edon( output );
return 1;
}
+
+#ifdef CXR_VALVE_BIN
+
+CXR_API cxr_tri_mesh *cxr_load_mdl( const char *mdlname )
+{
+ char path[1024];
+ strcpy( path, mdlname );
+ cxr_stripext( path );
+ strcat( path, ".dx90.vtx" );
+ VTXFileHeader_t *pVtxHdr = (VTXFileHeader_t *)cxr_fs_get( path );
+
+ if( !pVtxHdr )
+ return NULL;
+
+ /* .VVD */
+ strcpy( path, mdlname );
+ cxr_stripext( path );
+ strcat( path, ".vvd" );
+ vertexFileHeader_t *pVvdHdr = (vertexFileHeader_t *)cxr_fs_get( path );
+
+ if( !pVvdHdr )
+ {
+ free( pVtxHdr );
+ return 0;
+ }
+
+ /* .MDL */
+ strcpy( path, mdlname );
+ cxr_stripext( path );
+ strcat( path, ".mdl" );
+ studiohdr_t *pMdl = (studiohdr_t *)cxr_fs_get( path );
+
+ if( !pMdl )
+ {
+ free( pVtxHdr );
+ free( pVvdHdr );
+ return 0;
+ }
+
+ cxr_tri_mesh *mesh = malloc( sizeof(cxr_tri_mesh) );
+ mesh->colours = NULL;
+
+ mesh->indices_count = vtx_count_indices( pVtxHdr, pMdl );
+ mesh->indices = malloc( mesh->indices_count * sizeof( u32 ) );
+
+ mesh->vertex_count = pVvdHdr->numLodVertexes[0];
+ mesh->vertices = malloc( mesh->vertex_count * sizeof(v3f) );
+ mesh->uvs = malloc( mesh->vertex_count * sizeof(v2f) );
+ mesh->normals = malloc( mesh->vertex_count * sizeof(v3f) );
+
+#if 0
+ mesh->bounds[0][0] = pMdl->hull_min[0];
+ mesh->bounds[0][1] = pMdl->hull_min[1];
+ mesh->bounds[0][2] = pMdl->hull_min[2];
+ mesh->bounds[0][0] = pMdl->hull_max[0];
+ mesh->bounds[0][1] = pMdl->hull_max[1];
+ mesh->bounds[0][2] = pMdl->hull_max[2];
+#endif
+
+ mesh->indices_count = 0;
+
+ for( int bodyID = 0; bodyID < pVtxHdr->numBodyParts; ++bodyID )
+ {
+ /* Body parts */
+ VTXBodyPartHeader_t* pVtxBodyPart = pBodyPartVTX( pVtxHdr, bodyID );
+ mstudiobodyparts_t *pBodyPart = studiohdr_pBodypart( pMdl, bodyID );
+
+ for( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID )
+ {
+ /* models */
+ VTXModelHeader_t* pVtxModel = pModelVTX( pVtxBodyPart, modelID );
+ mstudiomodel_t *pStudioModel =
+ mstudiobodyparts_pModel( pBodyPart, modelID );
+
+ int nLod = 0;
+ VTXModelLODHeader_t *pVtxLOD = pLODVTX( pVtxModel, nLod );
+
+ for( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh )
+ {
+ /* meshes */
+ VTXMeshHeader_t* pVtxMesh = pMeshVTX( pVtxLOD, nMesh );
+ mstudiomesh_t* pMesh = studiomodel_pMesh( pStudioModel, nMesh );
+
+ for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup )
+ {
+ /* groups */
+ VTXStripGroupHeader_t* pStripGroup =
+ pStripGroupVTX( pVtxMesh, nGroup );
+
+ for ( int nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++ )
+ {
+ /* strips */
+ VTXStripHeader_t *pStrip = pStripVTX( pStripGroup, nStrip );
+
+ if ( pStrip->flags & STRIP_IS_TRILIST )
+ {
+ /* indices */
+ for ( int i = 0; i < pStrip->numIndices; i ++ )
+ {
+ u16 i1 = *pIndexVTX( pStripGroup,
+ pStrip->indexOffset + i );
+
+ mesh->indices[ mesh->indices_count ++ ] =
+ pVertexVTX( pStripGroup, i1 )->origMeshVertID +
+ pMesh->vertexoffset;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ mstudiovertex_t *vertexData = GetVertexData( pVvdHdr );
+
+ for( int i = 0; i < mesh->vertex_count; i ++ )
+ {
+ mstudiovertex_t *vert = vertexData + i;
+
+ mesh->vertices[i][0] = vert->pos[0];
+ mesh->vertices[i][1] = vert->pos[1];
+ mesh->vertices[i][2] = vert->pos[2];
+
+ mesh->normals[i][0] = vert->norm[0];
+ mesh->normals[i][1] = vert->norm[1];
+ mesh->normals[i][2] = vert->norm[2];
+
+ mesh->uvs[i][0] = vert->uv[0];
+ mesh->uvs[i][1] = vert->uv[1];
+ }
+
+ free( pVtxHdr );
+ free( pVvdHdr );
+ free( pMdl );
+
+ return mesh;
+}
+#endif /* CXR_VALVE_BIN */
+
#endif /* CXR_VALVE_MAP_FILE */
#endif /* CXR_IMPLEMENTATION */