utils: add studio model decompiler.
This commit is contained in:
parent
3d9c482eae
commit
7a58813254
16 changed files with 2032 additions and 1 deletions
|
@ -71,11 +71,14 @@ Studio models are position independent, so the cache manager can move them.
|
||||||
#define STUDIO_NF_CHROME 0x0002
|
#define STUDIO_NF_CHROME 0x0002
|
||||||
#define STUDIO_NF_FULLBRIGHT 0x0004
|
#define STUDIO_NF_FULLBRIGHT 0x0004
|
||||||
#define STUDIO_NF_NOMIPS 0x0008 // ignore mip-maps
|
#define STUDIO_NF_NOMIPS 0x0008 // ignore mip-maps
|
||||||
|
#define STUDIO_NF_NOSMOOTH 0x0010 // don't smooth tangent space
|
||||||
#define STUDIO_NF_ADDITIVE 0x0020 // rendering with additive mode
|
#define STUDIO_NF_ADDITIVE 0x0020 // rendering with additive mode
|
||||||
#define STUDIO_NF_MASKED 0x0040 // use texture with alpha channel
|
#define STUDIO_NF_MASKED 0x0040 // use texture with alpha channel
|
||||||
#define STUDIO_NF_NORMALMAP 0x0080 // indexed normalmap
|
#define STUDIO_NF_NORMALMAP 0x0080 // indexed normalmap
|
||||||
|
|
||||||
|
#define STUDIO_NF_SOLID 0x0800
|
||||||
|
#define STUDIO_NF_TWOSIDE 0x1000 // render mesh as twosided
|
||||||
|
|
||||||
#define STUDIO_NF_COLORMAP (1<<30) // internal system flag
|
#define STUDIO_NF_COLORMAP (1<<30) // internal system flag
|
||||||
#define STUDIO_NF_UV_COORDS (1<<31) // using half-float coords instead of ST
|
#define STUDIO_NF_UV_COORDS (1<<31) // using half-float coords instead of ST
|
||||||
|
|
||||||
|
|
|
@ -890,6 +890,37 @@ void COM_ReplaceExtension( char *path, const char *extension )
|
||||||
COM_DefaultExtension( path, extension );
|
COM_DefaultExtension( path, extension );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
COM_RemoveLineFeed
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
void COM_RemoveLineFeed( char *str )
|
||||||
|
{
|
||||||
|
while( *str != '\0' )
|
||||||
|
{
|
||||||
|
if( *str == '\r' || *str == '\n' )
|
||||||
|
*str = '\0';
|
||||||
|
|
||||||
|
++str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
COM_PathSlashFix
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
void COM_PathSlashFix( char *path )
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
len = Q_strlen( path );
|
||||||
|
|
||||||
|
if( path[len - 1] != '\\' || path[len - 1] != '/' )
|
||||||
|
Q_strcpy( &path[len], "/" );
|
||||||
|
}
|
||||||
|
|
||||||
int matchpattern( const char *in, const char *pattern, qboolean caseinsensitive )
|
int matchpattern( const char *in, const char *pattern, qboolean caseinsensitive )
|
||||||
{
|
{
|
||||||
return matchpattern_with_separator( in, pattern, caseinsensitive, "/\\:", false );
|
return matchpattern_with_separator( in, pattern, caseinsensitive, "/\\:", false );
|
||||||
|
|
|
@ -82,6 +82,8 @@ void COM_ReplaceExtension( char *path, const char *extension );
|
||||||
void COM_ExtractFilePath( const char *path, char *dest );
|
void COM_ExtractFilePath( const char *path, char *dest );
|
||||||
const char *COM_FileWithoutPath( const char *in );
|
const char *COM_FileWithoutPath( const char *in );
|
||||||
void COM_StripExtension( char *path );
|
void COM_StripExtension( char *path );
|
||||||
|
void COM_RemoveLineFeed( char *str );
|
||||||
|
void COM_PathSlashFix( char *path );
|
||||||
#define COM_CheckString( string ) ( ( !string || !*string ) ? 0 : 1 )
|
#define COM_CheckString( string ) ( ( !string || !*string ) ? 0 : 1 )
|
||||||
int matchpattern( const char *in, const char *pattern, qboolean caseinsensitive );
|
int matchpattern( const char *in, const char *pattern, qboolean caseinsensitive );
|
||||||
int matchpattern_with_separator( const char *in, const char *pattern, qboolean caseinsensitive, const char *separators, qboolean wildcard_least_one );
|
int matchpattern_with_separator( const char *in, const char *pattern, qboolean caseinsensitive, const char *separators, qboolean wildcard_least_one );
|
||||||
|
|
52
utils/mdldec/Makefile
Normal file
52
utils/mdldec/Makefile
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# Makefile for mdldec
|
||||||
|
# Copyright (c) nekonomicon 2020
|
||||||
|
|
||||||
|
MODULE = mdldec
|
||||||
|
|
||||||
|
CC ?= gcc
|
||||||
|
CFLAGS ?= -O3 -pipe -DHAVE_TGMATH_H
|
||||||
|
LDFLAGS ?= -Wl,--no-undefined
|
||||||
|
|
||||||
|
SYS = $(shell $(CC) -dumpmachine)
|
||||||
|
|
||||||
|
ifneq (, $(findstring mingw, $(SYS)))
|
||||||
|
EXT = .exe
|
||||||
|
else
|
||||||
|
EXT =
|
||||||
|
endif
|
||||||
|
|
||||||
|
APP = $(MODULE)$(EXT)
|
||||||
|
|
||||||
|
SRC = mdldec.c \
|
||||||
|
qc.c \
|
||||||
|
smd.c \
|
||||||
|
texture.c \
|
||||||
|
utils.c \
|
||||||
|
../../public/xash3d_mathlib.c \
|
||||||
|
../../public/matrixlib.c \
|
||||||
|
../../public/crtlib.c
|
||||||
|
|
||||||
|
INCLUDE = -I. \
|
||||||
|
-I../../common \
|
||||||
|
-I../../engine \
|
||||||
|
-I../../engine/common \
|
||||||
|
-I../../engine/common/imagelib \
|
||||||
|
-I../../public
|
||||||
|
|
||||||
|
LIBS = -lm
|
||||||
|
|
||||||
|
OBJS = $(SRC:%.c=%.o)
|
||||||
|
|
||||||
|
all: $(APP)
|
||||||
|
|
||||||
|
$(APP): $(OBJS)
|
||||||
|
$(CC) $(LDFLAGS) -o $(APP) $(OBJS) $(LIBS)
|
||||||
|
|
||||||
|
.c.o:
|
||||||
|
$(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) $(OBJS)
|
||||||
|
$(RM) $(APP)
|
302
utils/mdldec/mdldec.c
Normal file
302
utils/mdldec/mdldec.c
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
/*
|
||||||
|
mdldec.c - Half-Life Studio Model Decompiler
|
||||||
|
Copyright (C) 2020 Andrey Akhmichin
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "const.h"
|
||||||
|
#include "com_model.h"
|
||||||
|
#include "crtlib.h"
|
||||||
|
#include "studio.h"
|
||||||
|
#include "qc.h"
|
||||||
|
#include "smd.h"
|
||||||
|
#include "texture.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
char destdir[MAX_SYSPATH];
|
||||||
|
char modelfile[MAX_SYSPATH];
|
||||||
|
studiohdr_t *model_hdr;
|
||||||
|
studiohdr_t *texture_hdr;
|
||||||
|
studiohdr_t **anim_hdr;
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
SequenceNameFix
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void SequenceNameFix( void )
|
||||||
|
{
|
||||||
|
int i, j, counter;
|
||||||
|
qboolean hasduplicates = false;
|
||||||
|
mstudioseqdesc_t *seqdesc, *seqdesc1;
|
||||||
|
|
||||||
|
for( i = 0; i < model_hdr->numseq; i++ )
|
||||||
|
{
|
||||||
|
seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + i;
|
||||||
|
|
||||||
|
counter = 1;
|
||||||
|
|
||||||
|
for( j = 0; j < model_hdr->numseq; j++ )
|
||||||
|
{
|
||||||
|
seqdesc1 = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + j;
|
||||||
|
|
||||||
|
if( j != i && !Q_strncmp( seqdesc1->label, seqdesc->label, sizeof( seqdesc1->label ) ) )
|
||||||
|
Q_snprintf( seqdesc1->label, sizeof( seqdesc1->label ), "%s_%i", seqdesc1->label, ++counter );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( counter > 1 )
|
||||||
|
{
|
||||||
|
printf( "WARNING: Sequence name \"%s\" is repeated %i times.\n", seqdesc->label, counter );
|
||||||
|
|
||||||
|
Q_snprintf( seqdesc->label, sizeof( seqdesc->label ), "%s_1", seqdesc->label );
|
||||||
|
|
||||||
|
hasduplicates = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( hasduplicates )
|
||||||
|
puts( "WARNING: Added numeric suffix to repeated sequence name(s)." );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
BoneNameFix
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void BoneNameFix( void )
|
||||||
|
{
|
||||||
|
int i, counter = 0;
|
||||||
|
mstudiobone_t *bone;
|
||||||
|
|
||||||
|
for( i = 0; i < model_hdr->numbones; i++ )
|
||||||
|
{
|
||||||
|
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i;
|
||||||
|
|
||||||
|
if( bone->name[0] == '\0' )
|
||||||
|
Q_sprintf( bone->name, "MDLDEC_Bone%i", ++counter );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( counter )
|
||||||
|
printf( "WARNING: Gived name to %i unnamed bone(s).\n", counter );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
LoadMDL
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static qboolean LoadMDL( const char *modelname )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
size_t len;
|
||||||
|
char texturename[MAX_SYSPATH];
|
||||||
|
char seqgroupname[MAX_SYSPATH];
|
||||||
|
const char *ext;
|
||||||
|
const char id_mdlhdr[] = {'I', 'D', 'S', 'T'};
|
||||||
|
const char id_seqhdr[] = {'I', 'D', 'S', 'Q'};
|
||||||
|
|
||||||
|
printf( "MDL: %s\n", modelname );
|
||||||
|
|
||||||
|
len = Q_strlen( modelname );
|
||||||
|
|
||||||
|
if( len > MAX_SYSPATH - 3 )
|
||||||
|
{
|
||||||
|
fputs( "ERROR: Source path is too long.\n", stderr );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ext = COM_FileExtension( modelname );
|
||||||
|
|
||||||
|
if( !ext )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Source file does not have extension.\n" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( Q_strcmp( ext, "mdl" ) )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Only .mdl-files is supported.\n" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
model_hdr = (studiohdr_t *)LoadFile( modelname );
|
||||||
|
|
||||||
|
if( !model_hdr )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Can't open %s\n", modelname );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( memcmp( &model_hdr->ident, id_mdlhdr, sizeof( id_mdlhdr ) ) )
|
||||||
|
{
|
||||||
|
if( !memcmp( &model_hdr->ident, id_seqhdr, sizeof( id_seqhdr ) ) )
|
||||||
|
fprintf( stderr, "ERROR: %s is not a main HL model file.\n", modelname );
|
||||||
|
else
|
||||||
|
fprintf( stderr, "ERROR: %s is not a valid HL model file.\n", modelname );
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( model_hdr->version != STUDIO_VERSION )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: %s has unknown Studio MDL format version.\n", modelname );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !model_hdr->numbodyparts )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: %s is not a main HL model file.\n", modelname );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( destdir[0] != '\0' )
|
||||||
|
{
|
||||||
|
if( !IsFileExists( destdir ) )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Couldn't find directory %s\n", destdir );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
COM_PathSlashFix( destdir );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
COM_ExtractFilePath( modelname, destdir );
|
||||||
|
|
||||||
|
len -= 4; // path length without extension
|
||||||
|
|
||||||
|
if( !model_hdr->numtextures )
|
||||||
|
{
|
||||||
|
Q_strcpy( texturename, modelname );
|
||||||
|
Q_strcpy( &texturename[len], "t.mdl" );
|
||||||
|
|
||||||
|
texture_hdr = (studiohdr_t *)LoadFile( texturename );
|
||||||
|
|
||||||
|
if( !texture_hdr )
|
||||||
|
{
|
||||||
|
#if !XASH_WIN32
|
||||||
|
// dirty hack for casesensetive filesystems
|
||||||
|
texturename[len] = 'T';
|
||||||
|
|
||||||
|
texture_hdr = (studiohdr_t *)LoadFile( texturename );
|
||||||
|
|
||||||
|
if( !texture_hdr )
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Can't open external textures file %s\n", texturename );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( memcmp( &texture_hdr->ident, id_mdlhdr, sizeof( id_mdlhdr ) ) )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: %s is not a valid external textures file.\n", texturename );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
texture_hdr = model_hdr;
|
||||||
|
|
||||||
|
anim_hdr = malloc( sizeof( studiohdr_t* ) * model_hdr->numseqgroups );
|
||||||
|
|
||||||
|
if( !anim_hdr )
|
||||||
|
{
|
||||||
|
fputs( "ERROR: Couldn't allocate memory for sequences.\n", stderr );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
anim_hdr[0] = model_hdr;
|
||||||
|
|
||||||
|
if( model_hdr->numseqgroups > 1 )
|
||||||
|
{
|
||||||
|
Q_strcpy( seqgroupname, modelname );
|
||||||
|
|
||||||
|
for( i = 1; i < model_hdr->numseqgroups; i++ )
|
||||||
|
{
|
||||||
|
Q_sprintf( &seqgroupname[len], "%02d.mdl", i );
|
||||||
|
|
||||||
|
anim_hdr[i] = (studiohdr_t *)LoadFile( seqgroupname );
|
||||||
|
|
||||||
|
if( !anim_hdr[i] )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Can't open sequence file %s\n", seqgroupname );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( memcmp( &anim_hdr[i]->ident, id_seqhdr, sizeof( id_seqhdr ) ) )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: %s is not a valid sequence file.\n", seqgroupname );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
COM_FileBase( modelname, modelfile );
|
||||||
|
|
||||||
|
SequenceNameFix();
|
||||||
|
|
||||||
|
BoneNameFix();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
ShowHelp
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void ShowHelp( const char *app_name )
|
||||||
|
{
|
||||||
|
printf( "usage: %s source_file\n", app_name );
|
||||||
|
printf( " %s source_file target_directory\n", app_name );
|
||||||
|
}
|
||||||
|
|
||||||
|
int main( int argc, char *argv[] )
|
||||||
|
{
|
||||||
|
puts( "\nHalf-Life Studio Model Decompiler " APP_VERSION );
|
||||||
|
puts( "Copyright Flying With Gauss 2020 (c) " );
|
||||||
|
puts( "--------------------------------------------------" );
|
||||||
|
|
||||||
|
if( argc == 1 )
|
||||||
|
{
|
||||||
|
ShowHelp( argv[0] );
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else if( argc == 3 )
|
||||||
|
{
|
||||||
|
if( Q_strlen( argv[2] ) > MAX_SYSPATH - 1 )
|
||||||
|
{
|
||||||
|
fputs( "ERROR: Destination path is too long.\n", stderr );
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_strcpy( destdir, argv[2] );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !LoadActivityList( argv[0] ) || !LoadMDL( argv[1] ) )
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
WriteQCScript();
|
||||||
|
WriteSMD();
|
||||||
|
WriteTextures();
|
||||||
|
|
||||||
|
puts( "Done." );
|
||||||
|
|
||||||
|
end:
|
||||||
|
puts( "--------------------------------------------------" );
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
26
utils/mdldec/mdldec.h
Normal file
26
utils/mdldec/mdldec.h
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
mdldec.h - Half-Life Studio Model Decompiler
|
||||||
|
Copyright (C) 2020 Andrey Akhmichin
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef MDLDEC_H
|
||||||
|
#define MDLDEC_H
|
||||||
|
|
||||||
|
extern char destdir[MAX_SYSPATH];
|
||||||
|
extern char modelfile[MAX_SYSPATH];
|
||||||
|
extern studiohdr_t *model_hdr;
|
||||||
|
extern studiohdr_t *texture_hdr;
|
||||||
|
extern studiohdr_t **anim_hdr;
|
||||||
|
|
||||||
|
#endif // MDLDEC_H
|
||||||
|
|
649
utils/mdldec/qc.c
Normal file
649
utils/mdldec/qc.c
Normal file
|
@ -0,0 +1,649 @@
|
||||||
|
/*
|
||||||
|
qc.c - Quake C script writer
|
||||||
|
Copyright (C) 2020 Andrey Akhmichin
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "eiface.h"
|
||||||
|
#include "studio.h"
|
||||||
|
#include "crtlib.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "mdldec.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "qc.h"
|
||||||
|
|
||||||
|
static char **activity_names;
|
||||||
|
static int activity_count;
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
LoadActivityList
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
qboolean LoadActivityList( const char *appname )
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
const char *p;
|
||||||
|
char path[MAX_SYSPATH];
|
||||||
|
char buf[256];
|
||||||
|
|
||||||
|
fp = fopen( ACTIVITIES_FILE, "r" );
|
||||||
|
|
||||||
|
if( !fp )
|
||||||
|
{
|
||||||
|
p = getenv( "MDLDEC_ACT_PATH" );
|
||||||
|
|
||||||
|
if( !p )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Couldn't find file " ACTIVITIES_FILE ".\n" \
|
||||||
|
"Place " ACTIVITIES_FILE " beside %s or set MDLDEC_ACT_PATH environment variable.\n", appname );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_strncpy( path, p, MAX_SYSPATH - 1 );
|
||||||
|
|
||||||
|
COM_PathSlashFix( path );
|
||||||
|
|
||||||
|
Q_strncat( path, ACTIVITIES_FILE, MAX_SYSPATH );
|
||||||
|
|
||||||
|
fp = fopen( path, "r" );
|
||||||
|
|
||||||
|
if( !fp )
|
||||||
|
{
|
||||||
|
fputs( "ERROR: Can't open file " ACTIVITIES_FILE ".\n", stderr );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while( fgets( buf, sizeof( buf ), fp ) )
|
||||||
|
{
|
||||||
|
activity_names = realloc( activity_names, sizeof( char* ) * ++activity_count );
|
||||||
|
|
||||||
|
if( !activity_names )
|
||||||
|
{
|
||||||
|
fputs( "ERROR: Couldn't allocate memory for activities strings.\n", stderr );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
COM_RemoveLineFeed( buf );
|
||||||
|
|
||||||
|
activity_names[activity_count - 1] = strdup( buf );
|
||||||
|
|
||||||
|
if( !activity_names[activity_count - 1] )
|
||||||
|
{
|
||||||
|
fputs( "ERROR: Couldn't allocate memory for activities strings.\n", stderr );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose( fp );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
FindActivityName
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static const char *FindActivityName( int type )
|
||||||
|
{
|
||||||
|
if( type >= 0 && type < activity_count )
|
||||||
|
return activity_names[type];
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
GetMotionTypeString
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void GetMotionTypeString( int type, char *str, qboolean is_composite )
|
||||||
|
{
|
||||||
|
const char *p = NULL;
|
||||||
|
|
||||||
|
str[0] = '\0';
|
||||||
|
|
||||||
|
if( is_composite )
|
||||||
|
{
|
||||||
|
if( type & STUDIO_X )
|
||||||
|
Q_strcat( str, " X" );
|
||||||
|
|
||||||
|
if( type & STUDIO_Y )
|
||||||
|
Q_strcat( str, " Y" );
|
||||||
|
|
||||||
|
if( type & STUDIO_Z )
|
||||||
|
Q_strcat( str, " Z" );
|
||||||
|
|
||||||
|
if( type & STUDIO_XR )
|
||||||
|
Q_strcat( str, " XR" );
|
||||||
|
|
||||||
|
if( type & STUDIO_YR )
|
||||||
|
Q_strcat( str, " YR" );
|
||||||
|
|
||||||
|
if( type & STUDIO_ZR )
|
||||||
|
Q_strcat( str, " ZR" );
|
||||||
|
|
||||||
|
if( type & STUDIO_LX )
|
||||||
|
Q_strcat( str, " LX" );
|
||||||
|
|
||||||
|
if( type & STUDIO_LY )
|
||||||
|
Q_strcat( str, " LY" );
|
||||||
|
|
||||||
|
if( type & STUDIO_LZ )
|
||||||
|
Q_strcat( str, " LZ" );
|
||||||
|
|
||||||
|
if( type & STUDIO_AX )
|
||||||
|
Q_strcat( str, " AX" );
|
||||||
|
|
||||||
|
if( type & STUDIO_AY )
|
||||||
|
Q_strcat( str, " AY" );
|
||||||
|
|
||||||
|
if( type & STUDIO_AZ )
|
||||||
|
Q_strcat( str, " AZ" );
|
||||||
|
|
||||||
|
if( type & STUDIO_AXR )
|
||||||
|
Q_strcat( str, " AXR" );
|
||||||
|
|
||||||
|
if( type & STUDIO_AYR )
|
||||||
|
Q_strcat( str, " AYR" );
|
||||||
|
|
||||||
|
if( type & STUDIO_AZR )
|
||||||
|
Q_strcat( str, " AZR" );
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
type &= STUDIO_TYPES;
|
||||||
|
|
||||||
|
switch( type )
|
||||||
|
{
|
||||||
|
case STUDIO_X: p = "X"; break;
|
||||||
|
case STUDIO_Y: p = "Y"; break;
|
||||||
|
case STUDIO_Z: p = "Z"; break;
|
||||||
|
case STUDIO_XR: p = "XR"; break;
|
||||||
|
case STUDIO_YR: p = "YR"; break;
|
||||||
|
case STUDIO_ZR: p = "ZR"; break;
|
||||||
|
case STUDIO_LX: p = "LX"; break;
|
||||||
|
case STUDIO_LY: p = "LY"; break;
|
||||||
|
case STUDIO_LZ: p = "LZ"; break;
|
||||||
|
case STUDIO_AX: p = "AX"; break;
|
||||||
|
case STUDIO_AY: p = "AY"; break;
|
||||||
|
case STUDIO_AZ: p = "AZ"; break;
|
||||||
|
case STUDIO_AXR: p = "AXR"; break;
|
||||||
|
case STUDIO_AYR: p = "AYR"; break;
|
||||||
|
case STUDIO_AZR: p = "AZR"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( p )
|
||||||
|
Q_strcpy( str, p );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteTextureRenderMode
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteTextureRenderMode( FILE *fp )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
mstudiotexture_t *texture;
|
||||||
|
|
||||||
|
for( i = 0; i < texture_hdr->numtextures; i++ )
|
||||||
|
{
|
||||||
|
texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + i;
|
||||||
|
|
||||||
|
if( texture->flags & STUDIO_NF_FLATSHADE )
|
||||||
|
fprintf( fp,"$texrendermode \"%s\" \"flatshade\" \n", texture->name ); // sven-coop extension
|
||||||
|
|
||||||
|
if( texture->flags & STUDIO_NF_CHROME )
|
||||||
|
fprintf( fp, "$texrendermode \"%s\" \"chrome\" \n", texture->name ); // sven-coop extension/may be added in HLMV
|
||||||
|
|
||||||
|
if( texture->flags & STUDIO_NF_FULLBRIGHT )
|
||||||
|
fprintf( fp, "$texrendermode \"%s\" \"fullbright\" \n", texture->name ); // sven-coop extension/xash3d extension
|
||||||
|
|
||||||
|
if( texture->flags & STUDIO_NF_NOMIPS )
|
||||||
|
fprintf( fp, "$texrendermode \"%s\" \"nomips\" \n", texture->name ); // sven-coop extension
|
||||||
|
|
||||||
|
if( texture->flags & STUDIO_NF_NOSMOOTH )
|
||||||
|
{
|
||||||
|
fprintf( fp, "$texrendermode \"%s\" \"alpha\" \n", texture->name ); // sven-coop extension
|
||||||
|
fprintf( fp, "$texrendermode \"%s\" \"nosmooth\" \n", texture->name ); // xash3d extension
|
||||||
|
}
|
||||||
|
|
||||||
|
if( texture->flags & STUDIO_NF_ADDITIVE )
|
||||||
|
fprintf( fp, "$texrendermode \"%s\" \"additive\" \n", texture->name );
|
||||||
|
|
||||||
|
if( texture->flags & STUDIO_NF_MASKED )
|
||||||
|
fprintf( fp, "$texrendermode \"%s\" \"masked\" \n", texture->name );
|
||||||
|
|
||||||
|
if( texture->flags & ( STUDIO_NF_MASKED | STUDIO_NF_SOLID ) )
|
||||||
|
fprintf( fp, "$texrendermode \"%s\" \"masked_solid\" \n", texture->name ); // xash3d extension
|
||||||
|
|
||||||
|
if( texture->flags & STUDIO_NF_TWOSIDE )
|
||||||
|
fprintf( fp, "$texrendermode \"%s\" \"twoside\" \n", texture->name );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteSkinFamilyInfo
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteSkinFamilyInfo( FILE *fp )
|
||||||
|
{
|
||||||
|
int i, j, k;
|
||||||
|
short *skinref, index;
|
||||||
|
mstudiotexture_t *texture;
|
||||||
|
|
||||||
|
if( texture_hdr->numskinfamilies < 2 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
fprintf( fp, "\n// %i skin families\n", texture_hdr->numskinfamilies );
|
||||||
|
|
||||||
|
fputs( "$texturegroup skinfamilies \n{\n", fp );
|
||||||
|
|
||||||
|
skinref = (short *)( (byte *)texture_hdr + texture_hdr->skinindex );
|
||||||
|
|
||||||
|
for( i = 0; i < texture_hdr->numskinfamilies; ++i )
|
||||||
|
{
|
||||||
|
fputs( "{", fp );
|
||||||
|
|
||||||
|
for( j = 0; j < texture_hdr->numskinref; ++j )
|
||||||
|
{
|
||||||
|
index = *( skinref + i * texture_hdr->numskinref + j );
|
||||||
|
|
||||||
|
for( k = 0; k < texture_hdr->numskinfamilies; ++k )
|
||||||
|
{
|
||||||
|
if( index == *( skinref + k * texture_hdr->numskinref + j ) )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + index;
|
||||||
|
|
||||||
|
fprintf( fp, " \"%s\" ", texture->name );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs( "}\n", fp );
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs( "}\n", fp );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteAttachmentInfo
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteAttachmentInfo( FILE *fp )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
mstudioattachment_t *attachment;
|
||||||
|
mstudiobone_t *bone;
|
||||||
|
|
||||||
|
if( !model_hdr->numattachments )
|
||||||
|
return;
|
||||||
|
|
||||||
|
fprintf( fp, "\n// %i attachment(s)\n", model_hdr->numattachments );
|
||||||
|
|
||||||
|
for( i = 0; i < model_hdr->numattachments; ++i )
|
||||||
|
{
|
||||||
|
attachment = (mstudioattachment_t *)( (byte *)model_hdr + model_hdr->attachmentindex ) + i;
|
||||||
|
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + attachment->bone;
|
||||||
|
|
||||||
|
fprintf( fp, "$attachment %i \"%s\" %f %f %f\n", i, bone->name, attachment->org[0], attachment->org[1], attachment->org[2] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteBodyGroupInfo
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteBodyGroupInfo( FILE *fp )
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
mstudiobodyparts_t *bodypart;
|
||||||
|
mstudiomodel_t *model;
|
||||||
|
char modelname[64];
|
||||||
|
|
||||||
|
fputs( "\n// reference mesh(es)\n", fp );
|
||||||
|
|
||||||
|
for( i = 0; i < model_hdr->numbodyparts; ++i )
|
||||||
|
{
|
||||||
|
bodypart = (mstudiobodyparts_t *) ( (byte *)model_hdr + model_hdr->bodypartindex ) + i;
|
||||||
|
|
||||||
|
if( bodypart->nummodels == 1 )
|
||||||
|
{
|
||||||
|
model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex );
|
||||||
|
|
||||||
|
COM_FileBase( model->name, modelname );
|
||||||
|
|
||||||
|
fprintf( fp, "$body \"%s\" \"%s\"\n\n", bodypart->name, model->name );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf( fp, "$bodygroup \"%s\"\n", bodypart->name );
|
||||||
|
|
||||||
|
fputs( "{\n", fp );
|
||||||
|
|
||||||
|
for( j = 0; j < bodypart->nummodels; ++j )
|
||||||
|
{
|
||||||
|
model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ) + j;
|
||||||
|
|
||||||
|
if( !Q_strncmp( model->name, "blank", 5 ) )
|
||||||
|
{
|
||||||
|
fputs( "blank\n", fp );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
COM_FileBase( model->name, modelname );
|
||||||
|
|
||||||
|
fprintf( fp, "studio \"%s\"\n", modelname );
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs( "}\n\n" , fp );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteControllerInfo
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteControllerInfo( FILE *fp )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
mstudiobonecontroller_t *bonecontroller;
|
||||||
|
mstudiobone_t *bone;
|
||||||
|
char motion_types[64];
|
||||||
|
|
||||||
|
if( !model_hdr->numbonecontrollers )
|
||||||
|
return;
|
||||||
|
|
||||||
|
fprintf( fp, "\n// %i bone controller(s)\n", model_hdr->numbonecontrollers );
|
||||||
|
|
||||||
|
for( i = 0; i < model_hdr->numbonecontrollers; ++i )
|
||||||
|
{
|
||||||
|
bonecontroller = (mstudiobonecontroller_t *)( (byte *)model_hdr + model_hdr->bonecontrollerindex ) + i;
|
||||||
|
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + bonecontroller->bone;
|
||||||
|
|
||||||
|
GetMotionTypeString( bonecontroller->type & ~STUDIO_RLOOP, motion_types, false );
|
||||||
|
|
||||||
|
fprintf( fp, "$controller %i \"%s\" %s %f %f\n",
|
||||||
|
bonecontroller->index, bone->name, motion_types,
|
||||||
|
bonecontroller->start, bonecontroller->end );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteHitBoxInfo
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteHitBoxInfo( FILE *fp )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
mstudiobbox_t *hitbox;
|
||||||
|
mstudiobone_t *bone;
|
||||||
|
|
||||||
|
if( !model_hdr->numhitboxes )
|
||||||
|
return;
|
||||||
|
|
||||||
|
fprintf( fp, "\n// %i hit box(es)\n", model_hdr->numhitboxes );
|
||||||
|
|
||||||
|
for( i = 0; i < model_hdr->numhitboxes; i++ )
|
||||||
|
{
|
||||||
|
hitbox = (mstudiobbox_t *)( (byte *)model_hdr + model_hdr->hitboxindex ) + i;
|
||||||
|
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + hitbox->bone;
|
||||||
|
|
||||||
|
fprintf( fp, "$hbox %i \"%s\" %f %f %f %f %f %f\n",
|
||||||
|
hitbox->group, bone->name,
|
||||||
|
hitbox->bbmin[0], hitbox->bbmin[1], hitbox->bbmin[2],
|
||||||
|
hitbox->bbmax[0], hitbox->bbmax[1], hitbox->bbmax[2] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteSequenceInfo
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteSequenceInfo( FILE *fp )
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
const char *activity;
|
||||||
|
char motion_types[256];
|
||||||
|
mstudioevent_t *event;
|
||||||
|
mstudioseqdesc_t *seqdesc;
|
||||||
|
|
||||||
|
if( model_hdr->numseqgroups > 1 )
|
||||||
|
fputs( "\n$sequencegroupsize 64\n", fp );
|
||||||
|
|
||||||
|
if( model_hdr->numseq > 0 )
|
||||||
|
fprintf( fp, "\n// %i animation sequence(s)\n", model_hdr->numseq );
|
||||||
|
|
||||||
|
for( i = 0; i < model_hdr->numseq; ++i )
|
||||||
|
{
|
||||||
|
seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + i;
|
||||||
|
|
||||||
|
fprintf( fp, "$sequence \"%s\" ", seqdesc->label );
|
||||||
|
|
||||||
|
if( seqdesc->numblends > 1 )
|
||||||
|
{
|
||||||
|
if( seqdesc->numblends > 2 )
|
||||||
|
{
|
||||||
|
fputs( "{\n", fp );
|
||||||
|
|
||||||
|
for( j = 0; j < seqdesc->numblends; j++ )
|
||||||
|
{
|
||||||
|
fputs( " ", fp );
|
||||||
|
|
||||||
|
fprintf( fp, "\"%s_blend%i\" ", seqdesc->label, j + 1 );
|
||||||
|
|
||||||
|
fputs( "\n", fp );
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs( " ", fp );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf( fp, "\"%s_blend1\" ", seqdesc->label );
|
||||||
|
fprintf( fp, "\"%s_blend2\" ", seqdesc->label );
|
||||||
|
}
|
||||||
|
|
||||||
|
GetMotionTypeString( seqdesc->blendtype[0], motion_types, false );
|
||||||
|
|
||||||
|
fprintf( fp, "blend %s %.0f %.0f",
|
||||||
|
motion_types, seqdesc->blendstart[0], seqdesc->blendend[0] );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf( fp, "\"%s\"", seqdesc->label );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( seqdesc->motiontype )
|
||||||
|
{
|
||||||
|
GetMotionTypeString( seqdesc->motiontype, motion_types, true );
|
||||||
|
|
||||||
|
fprintf( fp, "%s", motion_types );
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf( fp, " fps %.0f ", seqdesc->fps );
|
||||||
|
|
||||||
|
if( seqdesc->flags == 1 )
|
||||||
|
fputs( "loop ", fp );
|
||||||
|
|
||||||
|
if( seqdesc->activity )
|
||||||
|
{
|
||||||
|
activity = FindActivityName( seqdesc->activity );
|
||||||
|
|
||||||
|
if( activity )
|
||||||
|
{
|
||||||
|
fprintf( fp, "%s %i ", activity, seqdesc->actweight );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf( "WARNING: Sequence %s has a custom activity flag (ACT_%i %i).\n",
|
||||||
|
seqdesc->label, seqdesc->activity, seqdesc->actweight );
|
||||||
|
|
||||||
|
fprintf( fp, "ACT_%i %i ", seqdesc->activity, seqdesc->actweight );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( seqdesc->entrynode && seqdesc->exitnode )
|
||||||
|
{
|
||||||
|
if( seqdesc->entrynode == seqdesc->exitnode )
|
||||||
|
fprintf( fp, "node %i ", seqdesc->entrynode );
|
||||||
|
else if( seqdesc->nodeflags )
|
||||||
|
fprintf( fp, "rtransition %i %i ", seqdesc->entrynode, seqdesc->exitnode );
|
||||||
|
else
|
||||||
|
fprintf( fp, "transition %i %i ", seqdesc->entrynode, seqdesc->exitnode );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( seqdesc->numevents > 2 )
|
||||||
|
{
|
||||||
|
fputs( "{\n ", fp );
|
||||||
|
|
||||||
|
for( j = 0; j < seqdesc->numevents; j++ )
|
||||||
|
{
|
||||||
|
if( seqdesc->numblends <= 2 )
|
||||||
|
fputs( " ", fp );
|
||||||
|
else
|
||||||
|
fputs( " ", fp );
|
||||||
|
|
||||||
|
event = (mstudioevent_t *)( (byte *)model_hdr + seqdesc->eventindex ) + j;
|
||||||
|
|
||||||
|
fprintf( fp, "{ event %i %i", event->event, event->frame );
|
||||||
|
|
||||||
|
if( event->options[0] != '\0' )
|
||||||
|
fprintf( fp, " \"%s\"", event->options );
|
||||||
|
|
||||||
|
fputs( " }\n ", fp );
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs( "}", fp );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for( j = 0; j < seqdesc->numevents; j++ )
|
||||||
|
{
|
||||||
|
event = (mstudioevent_t *)( (byte *)model_hdr + seqdesc->eventindex ) + j;
|
||||||
|
|
||||||
|
fprintf( fp, "{ event %i %i", event->event, event->frame );
|
||||||
|
|
||||||
|
if( event->options[0] != '\0')
|
||||||
|
fprintf( fp, " \"%s\"", event->options );
|
||||||
|
|
||||||
|
fputs( " } ", fp );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs( "\n", fp );
|
||||||
|
|
||||||
|
if( seqdesc->numblends > 2 )
|
||||||
|
fputs( "}\n", fp );
|
||||||
|
|
||||||
|
if( seqdesc->numpivots )
|
||||||
|
printf( "WARNING: Sequence %s uses %i foot pivots, feature not supported.\n",
|
||||||
|
seqdesc->label, seqdesc->numpivots );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteQCScript
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
void WriteQCScript( void )
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
char filename[MAX_SYSPATH];
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
len = Q_snprintf( filename, MAX_SYSPATH, "%s%s.qc", destdir, modelfile );
|
||||||
|
|
||||||
|
if( len >= MAX_SYSPATH )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Destination path is too long. Can't write %s.qc\n", modelfile );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fp = fopen( filename, "w" );
|
||||||
|
|
||||||
|
if( !fp )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Can't write %s\n", filename );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs( "/*\n", fp );
|
||||||
|
fputs( "==============================================================================\n\n", fp );
|
||||||
|
fputs( "QC script generated by Half-Life Studio Model Decompiler " APP_VERSION "\n", fp );
|
||||||
|
|
||||||
|
fprintf( fp, "Copyright Flying With Gauss %s (c) \n\n", Q_timestamp( TIME_YEAR_ONLY ) );
|
||||||
|
fprintf( fp, "%s.mdl\n\n", modelfile );
|
||||||
|
|
||||||
|
fputs( "Original internal name:\n", fp );
|
||||||
|
|
||||||
|
fprintf( fp, "\"%s\"\n\n", model_hdr->name );
|
||||||
|
|
||||||
|
fputs( "==============================================================================\n", fp );
|
||||||
|
fputs( "*/\n\n", fp );
|
||||||
|
|
||||||
|
fprintf( fp, "$modelname \"%s.mdl\"\n", modelfile );
|
||||||
|
|
||||||
|
fputs( "$cd \".\\\"\n", fp );
|
||||||
|
fputs( "$cdtexture \".\\\"\n", fp );
|
||||||
|
fputs( "$scale 1.0\n", fp );
|
||||||
|
fputs( "$cliptotextures\n", fp );
|
||||||
|
fputs( "\n", fp );
|
||||||
|
|
||||||
|
if( !model_hdr->numtextures )
|
||||||
|
fputs( "$externaltextures\n", fp );
|
||||||
|
|
||||||
|
if( model_hdr->flags != 0 )
|
||||||
|
{
|
||||||
|
fprintf( fp, "$flags %i\n", model_hdr->flags );
|
||||||
|
|
||||||
|
printf( "WARNING: This model uses the $flags keyword set to %i\n", model_hdr->flags );
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs( "\n", fp );
|
||||||
|
|
||||||
|
fprintf( fp, "$bbox %f %f %f", model_hdr->min[0], model_hdr->min[1], model_hdr->min[2] );
|
||||||
|
fprintf( fp, " %f %f %f\n", model_hdr->max[0], model_hdr->max[1], model_hdr->max[2] );
|
||||||
|
fprintf( fp, "$cbox %f %f %f", model_hdr->bbmin[0], model_hdr->bbmin[1], model_hdr->bbmin[2] );
|
||||||
|
fprintf( fp, " %f %f %f\n", model_hdr->bbmax[0], model_hdr->bbmax[1], model_hdr->bbmax[2] );
|
||||||
|
fprintf( fp, "$eyeposition %f %f %f\n", model_hdr->eyeposition[0], model_hdr->eyeposition[1], model_hdr->eyeposition[2] );
|
||||||
|
|
||||||
|
fputs( "\n", fp );
|
||||||
|
|
||||||
|
WriteBodyGroupInfo( fp );
|
||||||
|
WriteTextureRenderMode( fp );
|
||||||
|
WriteSkinFamilyInfo( fp );
|
||||||
|
WriteAttachmentInfo( fp );
|
||||||
|
WriteControllerInfo( fp );
|
||||||
|
WriteHitBoxInfo( fp );
|
||||||
|
WriteSequenceInfo( fp );
|
||||||
|
|
||||||
|
fputs( "\n// End of QC script.\n", fp );
|
||||||
|
fclose( fp );
|
||||||
|
|
||||||
|
printf( "QC Script: %s\n", filename );
|
||||||
|
}
|
||||||
|
|
25
utils/mdldec/qc.h
Normal file
25
utils/mdldec/qc.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
qc.h - Quake C script writer
|
||||||
|
Copyright (C) 2020 Andrey Akhmichin
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef QC_H
|
||||||
|
#define QC_H
|
||||||
|
|
||||||
|
#define ACTIVITIES_FILE "activities.txt"
|
||||||
|
|
||||||
|
qboolean LoadActivityList( const char *appname );
|
||||||
|
void WriteQCScript( void );
|
||||||
|
|
||||||
|
#endif // QC_H
|
||||||
|
|
103
utils/mdldec/res/activities.txt
Normal file
103
utils/mdldec/res/activities.txt
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
ACT_RESET
|
||||||
|
ACT_IDLE
|
||||||
|
ACT_GUARD
|
||||||
|
ACT_WALK
|
||||||
|
ACT_RUN
|
||||||
|
ACT_FLY
|
||||||
|
ACT_SWIM
|
||||||
|
ACT_HOP
|
||||||
|
ACT_LEAP
|
||||||
|
ACT_FALL
|
||||||
|
ACT_LAND
|
||||||
|
ACT_STRAFE_LEFT
|
||||||
|
ACT_STRAFE_RIGHT
|
||||||
|
ACT_ROLL_LEFT
|
||||||
|
ACT_ROLL_RIGHT
|
||||||
|
ACT_TURN_LEFT
|
||||||
|
ACT_TURN_RIGHT
|
||||||
|
ACT_CROUCH
|
||||||
|
ACT_CROUCHIDLE
|
||||||
|
ACT_STAND
|
||||||
|
ACT_USE
|
||||||
|
ACT_SIGNAL1
|
||||||
|
ACT_SIGNAL2
|
||||||
|
ACT_SIGNAL3
|
||||||
|
ACT_TWITCH
|
||||||
|
ACT_COWER
|
||||||
|
ACT_SMALL_FLINCH
|
||||||
|
ACT_BIG_FLINCH
|
||||||
|
ACT_RANGE_ATTACK1
|
||||||
|
ACT_RANGE_ATTACK2
|
||||||
|
ACT_MELEE_ATTACK1
|
||||||
|
ACT_MELEE_ATTACK2
|
||||||
|
ACT_RELOAD
|
||||||
|
ACT_ARM
|
||||||
|
ACT_DISARM
|
||||||
|
ACT_EAT
|
||||||
|
ACT_DIESIMPLE
|
||||||
|
ACT_DIEBACKWARD
|
||||||
|
ACT_DIEFORWARD
|
||||||
|
ACT_DIEVIOLENT
|
||||||
|
ACT_BARNACLE_HIT
|
||||||
|
ACT_BARNACLE_PULL
|
||||||
|
ACT_BARNACLE_CHOMP
|
||||||
|
ACT_BARNACLE_CHEW
|
||||||
|
ACT_SLEEP
|
||||||
|
ACT_INSPECT_FLOOR
|
||||||
|
ACT_INSPECT_WALL
|
||||||
|
ACT_IDLE_ANGRY
|
||||||
|
ACT_WALK_HURT
|
||||||
|
ACT_RUN_HURT
|
||||||
|
ACT_HOVER
|
||||||
|
ACT_GLIDE
|
||||||
|
ACT_FLY_LEFT
|
||||||
|
ACT_FLY_RIGHT
|
||||||
|
ACT_DETECT_SCENT
|
||||||
|
ACT_SNIFF
|
||||||
|
ACT_BITE
|
||||||
|
ACT_THREAT_DISPLAY
|
||||||
|
ACT_FEAR_DISPLAY
|
||||||
|
ACT_EXCITED
|
||||||
|
ACT_SPECIAL_ATTACK1
|
||||||
|
ACT_SPECIAL_ATTACK2
|
||||||
|
ACT_COMBAT_IDLE
|
||||||
|
ACT_WALK_SCARED
|
||||||
|
ACT_RUN_SCARED
|
||||||
|
ACT_VICTORY_DANCE
|
||||||
|
ACT_DIE_HEADSHOT
|
||||||
|
ACT_DIE_CHESTSHOT
|
||||||
|
ACT_DIE_GUTSHOT
|
||||||
|
ACT_DIE_BACKSHOT
|
||||||
|
ACT_FLINCH_HEAD
|
||||||
|
ACT_FLINCH_CHEST
|
||||||
|
ACT_FLINCH_STOMACH
|
||||||
|
ACT_FLINCH_LEFTARM
|
||||||
|
ACT_FLINCH_RIGHTARM
|
||||||
|
ACT_FLINCH_LEFTLEG
|
||||||
|
ACT_FLINCH_RIGHTLEG
|
||||||
|
ACT_VM_NONE
|
||||||
|
ACT_VM_DEPLOY
|
||||||
|
ACT_VM_DEPLOY_EMPTY
|
||||||
|
ACT_VM_HOLSTER
|
||||||
|
ACT_VM_HOLSTER_EMPTY
|
||||||
|
ACT_VM_IDLE1
|
||||||
|
ACT_VM_IDLE2
|
||||||
|
ACT_VM_IDLE3
|
||||||
|
ACT_VM_RANGE_ATTACK1
|
||||||
|
ACT_VM_RANGE_ATTACK2
|
||||||
|
ACT_VM_RANGE_ATTACK3
|
||||||
|
ACT_VM_MELEE_ATTACK1
|
||||||
|
ACT_VM_MELEE_ATTACK2
|
||||||
|
ACT_VM_MELEE_ATTACK3
|
||||||
|
ACT_VM_SHOOT_EMPTY
|
||||||
|
ACT_VM_START_RELOAD
|
||||||
|
ACT_VM_RELOAD
|
||||||
|
ACT_VM_RELOAD_EMPTY
|
||||||
|
ACT_VM_TURNON
|
||||||
|
ACT_VM_TURNOFF
|
||||||
|
ACT_VM_PUMP
|
||||||
|
ACT_VM_PUMP_EMPTY
|
||||||
|
ACT_VM_START_CHARGE
|
||||||
|
ACT_VM_CHARGE
|
||||||
|
ACT_VM_OVERLOAD
|
||||||
|
ACT_VM_IDLE_EMPTY
|
549
utils/mdldec/smd.c
Normal file
549
utils/mdldec/smd.c
Normal file
|
@ -0,0 +1,549 @@
|
||||||
|
/*
|
||||||
|
smd.c - Studio Model Data format writer
|
||||||
|
Copyright (C) 2020 Andrey Akhmichin
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "const.h"
|
||||||
|
#include "com_model.h"
|
||||||
|
#include "xash3d_mathlib.h"
|
||||||
|
#include "crtlib.h"
|
||||||
|
#include "studio.h"
|
||||||
|
#include "mdldec.h"
|
||||||
|
#include "smd.h"
|
||||||
|
|
||||||
|
static matrix3x4 *bonetransform;
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
CreateBoneTransformMatrices
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static qboolean CreateBoneTransformMatrices( void )
|
||||||
|
{
|
||||||
|
bonetransform = calloc( model_hdr->numbones, sizeof( matrix3x4 ) );
|
||||||
|
|
||||||
|
if( !bonetransform )
|
||||||
|
{
|
||||||
|
fputs( "ERROR: Couldn't allocate memory for bone transformation matrices!\n", stderr );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
FillBoneTransformMatrices
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void FillBoneTransformMatrices( void )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
mstudiobone_t *bone;
|
||||||
|
matrix3x4 bonematrix;
|
||||||
|
vec4_t q;
|
||||||
|
|
||||||
|
for( i = 0; i < model_hdr->numbones; i++ )
|
||||||
|
{
|
||||||
|
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i;
|
||||||
|
|
||||||
|
AngleQuaternion( &bone->value[3], q, true );
|
||||||
|
Matrix3x4_FromOriginQuat( bonematrix, q, bone->value );
|
||||||
|
|
||||||
|
if( bone->parent == -1 )
|
||||||
|
{
|
||||||
|
Matrix3x4_Copy( bonetransform[i], bonematrix );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix3x4_ConcatTransforms( bonetransform[i], bonetransform[bone->parent], bonematrix );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
RemoveBoneTransformMatrices
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void RemoveBoneTransformMatrices( void )
|
||||||
|
{
|
||||||
|
free( bonetransform );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
ClipRotations
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void ClipRotations( vec3_t angle )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for( i = 0; i < 3; i++ )
|
||||||
|
{
|
||||||
|
while( angle[i] >= M_PI_F )
|
||||||
|
angle[i] -= M_PI2_F;
|
||||||
|
|
||||||
|
while( angle[i] < -M_PI_F )
|
||||||
|
angle[i] += M_PI2_F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
ProperBoneRotationZ
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void ProperBoneRotationZ( mstudioseqdesc_t *seqdesc, vec_t *motion, int frame, float angle )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
float c, s, x, y;
|
||||||
|
float rot;
|
||||||
|
|
||||||
|
for( i = 0; i < 3; i++ )
|
||||||
|
motion[i] += frame * 1.0f / seqdesc->numframes * seqdesc->linearmovement[i];
|
||||||
|
|
||||||
|
rot = DEG2RAD( angle );
|
||||||
|
|
||||||
|
s = sin( rot );
|
||||||
|
c = cos( rot );
|
||||||
|
|
||||||
|
x = motion[0];
|
||||||
|
y = motion[1];
|
||||||
|
|
||||||
|
motion[0] = c * x - s * y;
|
||||||
|
motion[1] = s * x + c * y;
|
||||||
|
|
||||||
|
motion[5] += rot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
CalcBonePosition
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void CalcBonePosition( mstudioanim_t *anim, mstudiobone_t *bone, vec_t *motion, int frame )
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
float value;
|
||||||
|
mstudioanimvalue_t *animvalue;
|
||||||
|
|
||||||
|
for( i = 0; i < 6; i++ )
|
||||||
|
{
|
||||||
|
motion[i] = bone->value[i];
|
||||||
|
|
||||||
|
if( !anim->offset[i] )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
animvalue = (mstudioanimvalue_t *)( (byte *)anim + anim->offset[i] );
|
||||||
|
|
||||||
|
j = frame;
|
||||||
|
|
||||||
|
while( animvalue->num.total <= j )
|
||||||
|
{
|
||||||
|
j -= animvalue->num.total;
|
||||||
|
animvalue += animvalue->num.valid + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( animvalue->num.valid > j )
|
||||||
|
value = animvalue[j + 1].value;
|
||||||
|
else
|
||||||
|
value = animvalue[animvalue->num.valid].value;
|
||||||
|
|
||||||
|
motion[i] += value * bone->scale[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteNodes
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteNodes( FILE *fp )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
mstudiobone_t *bone;
|
||||||
|
|
||||||
|
fputs( "nodes\n", fp );
|
||||||
|
|
||||||
|
for( i = 0; i < model_hdr->numbones; i++ )
|
||||||
|
{
|
||||||
|
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i;
|
||||||
|
|
||||||
|
fprintf( fp, "%3i \"%s\" %i\n", i, bone->name, bone->parent );
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs( "end\n", fp );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteSkeleton
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteSkeleton( FILE *fp )
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
mstudiobone_t *bone;
|
||||||
|
|
||||||
|
fputs( "skeleton\n", fp );
|
||||||
|
fputs( "time 0\n", fp );
|
||||||
|
|
||||||
|
for( i = 0; i < model_hdr->numbones; i++ )
|
||||||
|
{
|
||||||
|
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i;
|
||||||
|
|
||||||
|
fprintf( fp, "%3i", i );
|
||||||
|
|
||||||
|
for( j = 0; j < 6; j++ )
|
||||||
|
fprintf( fp, " %f", bone->value[j] );
|
||||||
|
|
||||||
|
fputs( "\n", fp );
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs( "end\n", fp );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteTriangleInfo
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteTriangleInfo( FILE *fp, mstudiomodel_t *model, mstudiotexture_t *texture, mstudiotrivert_t **triverts, qboolean isevenstrip )
|
||||||
|
{
|
||||||
|
int i, indices[3];
|
||||||
|
int vert_index;
|
||||||
|
int norm_index;
|
||||||
|
int bone_index;
|
||||||
|
float s, t, u, v;
|
||||||
|
byte *vertbone;
|
||||||
|
vec3_t *studioverts;
|
||||||
|
vec3_t *studionorms;
|
||||||
|
vec3_t vert, norm;
|
||||||
|
|
||||||
|
if( isevenstrip )
|
||||||
|
{
|
||||||
|
indices[0] = 1;
|
||||||
|
indices[1] = 2;
|
||||||
|
indices[2] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
indices[0] = 0;
|
||||||
|
indices[1] = 1;
|
||||||
|
indices[2] = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
vertbone = ( (byte *)model_hdr + model->vertinfoindex );
|
||||||
|
studioverts = (vec3_t *)( (byte *)model_hdr + model->vertindex );
|
||||||
|
studionorms = (vec3_t *)( (byte *)model_hdr + model->normindex );
|
||||||
|
|
||||||
|
s = 1.0f / texture->width;
|
||||||
|
t = 1.0f / texture->height;
|
||||||
|
|
||||||
|
fprintf( fp, "%s\n", texture->name );
|
||||||
|
|
||||||
|
for( i = 0; i < 3; i++ )
|
||||||
|
{
|
||||||
|
vert_index = triverts[indices[i]]->vertindex;
|
||||||
|
norm_index = triverts[indices[i]]->normindex;
|
||||||
|
bone_index = vertbone[vert_index];
|
||||||
|
|
||||||
|
Matrix3x4_VectorTransform( bonetransform[bone_index], studioverts[vert_index], vert );
|
||||||
|
Matrix3x4_VectorRotate( bonetransform[bone_index], studionorms[norm_index], norm );
|
||||||
|
VectorNormalize( norm );
|
||||||
|
|
||||||
|
u = ( triverts[indices[i]]->s + 1.0f ) * s;
|
||||||
|
v = 1.0f - triverts[indices[i]]->t * t;
|
||||||
|
|
||||||
|
fprintf( fp, "%3i %f %f %f %f %f %f %f %f\n",
|
||||||
|
bone_index,
|
||||||
|
vert[0], vert[1], vert[2],
|
||||||
|
norm[0], norm[1], norm[2],
|
||||||
|
u, v );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteTriangles
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteTriangles( FILE *fp, mstudiomodel_t *model )
|
||||||
|
{
|
||||||
|
int i, j, k;
|
||||||
|
mstudiomesh_t *mesh;
|
||||||
|
mstudiotexture_t *texture;
|
||||||
|
mstudiotrivert_t *triverts[3];
|
||||||
|
short *tricmds;
|
||||||
|
|
||||||
|
fputs( "triangles\n", fp );
|
||||||
|
|
||||||
|
for( i = 0; i < model->nummesh; i++ )
|
||||||
|
{
|
||||||
|
mesh = (mstudiomesh_t *)( (byte *)model_hdr + model->meshindex ) + i;
|
||||||
|
tricmds = (short *)( (byte *)model_hdr + mesh->triindex );
|
||||||
|
texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + mesh->skinref;
|
||||||
|
|
||||||
|
while( ( j = *( tricmds++ ) ) )
|
||||||
|
{
|
||||||
|
if( j >= 0 )
|
||||||
|
{
|
||||||
|
// triangle fan
|
||||||
|
for( k = 0; j > 0; j--, k++, tricmds += 4 )
|
||||||
|
{
|
||||||
|
if( k == 0 )
|
||||||
|
{
|
||||||
|
triverts[0] = (mstudiotrivert_t *)tricmds;
|
||||||
|
}
|
||||||
|
else if( k == 1 )
|
||||||
|
{
|
||||||
|
triverts[2] = (mstudiotrivert_t *)tricmds;
|
||||||
|
}
|
||||||
|
else if( k == 2 )
|
||||||
|
{
|
||||||
|
triverts[1] = (mstudiotrivert_t *)tricmds;
|
||||||
|
|
||||||
|
WriteTriangleInfo( fp, model, texture, triverts, true );
|
||||||
|
}
|
||||||
|
else if( k % 2 )
|
||||||
|
{
|
||||||
|
triverts[0] = triverts[2];
|
||||||
|
triverts[2] = (mstudiotrivert_t *)tricmds;
|
||||||
|
|
||||||
|
WriteTriangleInfo( fp, model, texture, triverts, false );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
triverts[0] = triverts[1];
|
||||||
|
triverts[1] = (mstudiotrivert_t *)tricmds;
|
||||||
|
|
||||||
|
WriteTriangleInfo( fp, model, texture, triverts, true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// triangle strip
|
||||||
|
j = abs( j );
|
||||||
|
|
||||||
|
for( k = 0; j > 0; j--, k++, tricmds += 4 )
|
||||||
|
{
|
||||||
|
if( k == 0 )
|
||||||
|
{
|
||||||
|
triverts[0] = (mstudiotrivert_t *)tricmds;
|
||||||
|
}
|
||||||
|
else if( k == 1 )
|
||||||
|
{
|
||||||
|
triverts[2] = (mstudiotrivert_t *)tricmds;
|
||||||
|
}
|
||||||
|
else if( k == 2 )
|
||||||
|
{
|
||||||
|
triverts[1] = (mstudiotrivert_t *)tricmds;
|
||||||
|
|
||||||
|
WriteTriangleInfo( fp, model, texture, triverts, false );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
triverts[2] = triverts[1];
|
||||||
|
triverts[1] = (mstudiotrivert_t *)tricmds;
|
||||||
|
|
||||||
|
WriteTriangleInfo( fp, model, texture, triverts, false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs( "end\n", fp );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteFrameInfo
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteFrameInfo( FILE *fp, mstudioanim_t *anim, mstudioseqdesc_t *seqdesc, int frame )
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
vec_t motion[6]; // x, y, z, xr, yr, zr
|
||||||
|
mstudiobone_t *bone;
|
||||||
|
|
||||||
|
fprintf( fp, "time %i\n", frame );
|
||||||
|
|
||||||
|
for( i = 0; i < model_hdr->numbones; i++, anim++ )
|
||||||
|
{
|
||||||
|
bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i;
|
||||||
|
|
||||||
|
CalcBonePosition( anim, bone, motion, frame );
|
||||||
|
|
||||||
|
if( bone->parent == -1 )
|
||||||
|
ProperBoneRotationZ( seqdesc, motion, frame, 270.0f );
|
||||||
|
|
||||||
|
ClipRotations( &motion[3] );
|
||||||
|
|
||||||
|
fprintf( fp, "%3i ", i );
|
||||||
|
|
||||||
|
for( j = 0; j < 6; j++ )
|
||||||
|
fprintf( fp, " %f", motion[j] );
|
||||||
|
|
||||||
|
fputs( "\n", fp );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteAnimations
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteAnimations( FILE *fp, mstudioseqdesc_t *seqdesc, int blend )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
mstudioanim_t *anim;
|
||||||
|
|
||||||
|
fputs( "skeleton\n", fp );
|
||||||
|
|
||||||
|
anim = (mstudioanim_t *)( (byte *)anim_hdr[seqdesc->seqgroup] + seqdesc->animindex );
|
||||||
|
anim += blend * model_hdr->numbones;
|
||||||
|
|
||||||
|
for( i = 0; i < seqdesc->numframes; i++ )
|
||||||
|
WriteFrameInfo( fp, anim, seqdesc, i );
|
||||||
|
|
||||||
|
fputs( "end\n", fp );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteReferences
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteReferences( void )
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
size_t len;
|
||||||
|
FILE *fp;
|
||||||
|
mstudiomodel_t *model;
|
||||||
|
mstudiobodyparts_t *bodypart;
|
||||||
|
char name[64];
|
||||||
|
char filename[MAX_SYSPATH];
|
||||||
|
|
||||||
|
if( !CreateBoneTransformMatrices() )
|
||||||
|
return;
|
||||||
|
|
||||||
|
FillBoneTransformMatrices();
|
||||||
|
|
||||||
|
for( i = 0; i < model_hdr->numbodyparts; i++ )
|
||||||
|
{
|
||||||
|
bodypart = (mstudiobodyparts_t *)( (byte *)model_hdr + model_hdr->bodypartindex ) + i;
|
||||||
|
|
||||||
|
for( j = 0; j < bodypart->nummodels; j++ )
|
||||||
|
{
|
||||||
|
model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ) + j;
|
||||||
|
|
||||||
|
if( !Q_strncmp( model->name, "blank", 5 ) )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
COM_FileBase( model->name, name );
|
||||||
|
|
||||||
|
len = Q_snprintf( filename, MAX_SYSPATH, "%s%s.smd", destdir, name );
|
||||||
|
|
||||||
|
if( len >= MAX_SYSPATH )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Destination path is too long. Can't write %s.smd\n", name );
|
||||||
|
RemoveBoneTransformMatrices();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fp = fopen( filename, "w" );
|
||||||
|
|
||||||
|
if( !fp )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Can't write %s\n", filename );
|
||||||
|
RemoveBoneTransformMatrices();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs( "version 1\n", fp );
|
||||||
|
|
||||||
|
WriteNodes( fp );
|
||||||
|
WriteSkeleton( fp );
|
||||||
|
WriteTriangles( fp, model );
|
||||||
|
|
||||||
|
fclose( fp );
|
||||||
|
|
||||||
|
printf( "Reference: %s\n", filename );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveBoneTransformMatrices();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteSequences
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteSequences( void )
|
||||||
|
{
|
||||||
|
int i, j;
|
||||||
|
size_t len;
|
||||||
|
FILE *fp;
|
||||||
|
char filename[MAX_SYSPATH];
|
||||||
|
mstudioseqdesc_t *seqdesc;
|
||||||
|
|
||||||
|
for( i = 0; i < model_hdr->numseq; i++ )
|
||||||
|
{
|
||||||
|
seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + i;
|
||||||
|
|
||||||
|
for( j = 0; j < seqdesc->numblends; j++ )
|
||||||
|
{
|
||||||
|
if( seqdesc->numblends == 1 )
|
||||||
|
len = Q_snprintf( filename, MAX_SYSPATH, "%s%s.smd", destdir, seqdesc->label );
|
||||||
|
else
|
||||||
|
len = Q_snprintf( filename, MAX_SYSPATH, "%s%s_blend%i.smd", destdir, seqdesc->label, j + 1 );
|
||||||
|
|
||||||
|
if( len >= MAX_SYSPATH )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Destination path is too long. Can't write %s.smd\n", seqdesc->label );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fp = fopen( filename, "w" );
|
||||||
|
|
||||||
|
if( !fp )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Can't write %s\n", filename );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs( "version 1\n", fp );
|
||||||
|
|
||||||
|
WriteNodes( fp );
|
||||||
|
WriteAnimations( fp, seqdesc, j );
|
||||||
|
|
||||||
|
fclose( fp );
|
||||||
|
|
||||||
|
printf( "Sequence: %s\n", filename );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteSMD( void )
|
||||||
|
{
|
||||||
|
WriteReferences();
|
||||||
|
WriteSequences();
|
||||||
|
}
|
||||||
|
|
22
utils/mdldec/smd.h
Normal file
22
utils/mdldec/smd.h
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
smd.h - Studio Model Data format writer
|
||||||
|
Copyright (C) 2020 Andrey Akhmichin
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef SMD_H
|
||||||
|
#define SMD_H
|
||||||
|
|
||||||
|
void WriteSMD( void );
|
||||||
|
|
||||||
|
#endif // SMD_H
|
||||||
|
|
127
utils/mdldec/texture.c
Normal file
127
utils/mdldec/texture.c
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
texture.c - texture writer
|
||||||
|
Copyright (C) 2020 Andrey Akhmichin
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "const.h"
|
||||||
|
#include "crtlib.h"
|
||||||
|
#include "studio.h"
|
||||||
|
#include "img_bmp.h"
|
||||||
|
#include "mdldec.h"
|
||||||
|
#include "texture.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteBMP
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
static void WriteBMP( mstudiotexture_t *texture )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
FILE *fp;
|
||||||
|
const byte *p;
|
||||||
|
byte *palette, *pic, *buf;
|
||||||
|
char filename[MAX_SYSPATH], texturename[64];
|
||||||
|
rgba_t rgba_palette[256];
|
||||||
|
bmp_t bmp_hdr = {0,};
|
||||||
|
size_t texture_size, len;
|
||||||
|
|
||||||
|
COM_FileBase( texture->name, texturename );
|
||||||
|
len = Q_snprintf( filename, MAX_SYSPATH, "%s%s.bmp", destdir, texturename );
|
||||||
|
|
||||||
|
if( len >= MAX_SYSPATH )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Destination path is too long. Can't write %s.bmp\n", texturename );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fp = fopen( filename, "wb" );
|
||||||
|
|
||||||
|
if( !fp )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "ERROR: Can't write texture file %s\n", filename );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
texture_size = texture->height * texture->width;
|
||||||
|
pic = (byte *)texture_hdr + texture->index;
|
||||||
|
palette = pic + texture_size;
|
||||||
|
|
||||||
|
bmp_hdr.id[0] = 'B';
|
||||||
|
bmp_hdr.id[1] = 'M';
|
||||||
|
bmp_hdr.width = texture->width;
|
||||||
|
bmp_hdr.height = texture->height;
|
||||||
|
bmp_hdr.planes = 1;
|
||||||
|
bmp_hdr.bitsPerPixel = 8;
|
||||||
|
bmp_hdr.bitmapDataSize = texture_size;
|
||||||
|
bmp_hdr.colors = 256;
|
||||||
|
|
||||||
|
bmp_hdr.fileSize = sizeof( bmp_hdr ) + texture_size + sizeof( rgba_palette );
|
||||||
|
bmp_hdr.bitmapDataOffset = sizeof( bmp_hdr ) + sizeof( rgba_palette );
|
||||||
|
bmp_hdr.bitmapHeaderSize = BI_SIZE;
|
||||||
|
|
||||||
|
fwrite( &bmp_hdr, sizeof( bmp_hdr ), 1, fp );
|
||||||
|
|
||||||
|
p = palette;
|
||||||
|
|
||||||
|
for( i = 0; i < (int)bmp_hdr.colors; i++ )
|
||||||
|
{
|
||||||
|
rgba_palette[i][2] = *p++;
|
||||||
|
rgba_palette[i][1] = *p++;
|
||||||
|
rgba_palette[i][0] = *p++;
|
||||||
|
rgba_palette[i][3] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite( rgba_palette, sizeof( rgba_palette ), 1, fp );
|
||||||
|
|
||||||
|
buf = malloc( texture_size );
|
||||||
|
|
||||||
|
p = pic;
|
||||||
|
p += ( bmp_hdr.height - 1 ) * bmp_hdr.width;
|
||||||
|
|
||||||
|
for( i = 0; i < bmp_hdr.height; i++ )
|
||||||
|
{
|
||||||
|
memcpy( buf + bmp_hdr.width * i, p, bmp_hdr.width );
|
||||||
|
p -= bmp_hdr.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite( buf, texture_size, 1, fp );
|
||||||
|
|
||||||
|
fclose( fp );
|
||||||
|
|
||||||
|
free( buf );
|
||||||
|
|
||||||
|
printf( "Texture: %s\n", filename );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
WriteTextures
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
void WriteTextures( void )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
mstudiotexture_t *texture;
|
||||||
|
|
||||||
|
for( i = 0; i < texture_hdr->numtextures; i++ )
|
||||||
|
{
|
||||||
|
texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + i;
|
||||||
|
|
||||||
|
WriteBMP( texture );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
22
utils/mdldec/texture.h
Normal file
22
utils/mdldec/texture.h
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
texture.h - texture writer
|
||||||
|
Copyright (C) 2020 Andrey Akhmichin
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef TEXTURE_H
|
||||||
|
#define TEXTURE_H
|
||||||
|
|
||||||
|
void WriteTextures( void );
|
||||||
|
|
||||||
|
#endif // TEXTURE_H
|
||||||
|
|
86
utils/mdldec/utils.c
Normal file
86
utils/mdldec/utils.c
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
utils.c - Useful helper functions
|
||||||
|
Copyright (C) 2020 Andrey Akhmichin
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include "xash3d_types.h"
|
||||||
|
#include "crtlib.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
IsFileExists
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
qboolean IsFileExists( const char *filename )
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = stat( filename, &st );
|
||||||
|
|
||||||
|
if( ret == -1 )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
GetFileSize
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
off_t GetFileSize( FILE *fp )
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = fileno( fp );
|
||||||
|
fstat( fd, &st );
|
||||||
|
|
||||||
|
return st.st_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
============
|
||||||
|
LoadFile
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
byte *LoadFile( const char *filename )
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
byte *buf;
|
||||||
|
off_t size;
|
||||||
|
|
||||||
|
fp = fopen( filename, "rb" );
|
||||||
|
|
||||||
|
if( !fp )
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
size = GetFileSize( fp );
|
||||||
|
|
||||||
|
buf = malloc( size );
|
||||||
|
|
||||||
|
if( !buf )
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
fread( buf, size, 1, fp );
|
||||||
|
fclose( fp );
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
24
utils/mdldec/utils.h
Normal file
24
utils/mdldec/utils.h
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
utils.h - Useful helper functions
|
||||||
|
Copyright (C) 2020 Andrey Akhmichin
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#ifndef UTILS_H
|
||||||
|
#define UTILS_H
|
||||||
|
|
||||||
|
qboolean IsFileExists( const char *filename );
|
||||||
|
off_t GetFileSize( FILE *fp );
|
||||||
|
byte *LoadFile( const char *filename );
|
||||||
|
|
||||||
|
#endif // UTILS_H
|
||||||
|
|
8
utils/mdldec/version.h
Normal file
8
utils/mdldec/version.h
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
#ifndef VERSION_H
|
||||||
|
#define VERSION_H
|
||||||
|
|
||||||
|
#define APP_VERSION "1.1"
|
||||||
|
|
||||||
|
#endif // VERSION_H
|
||||||
|
|
Loading…
Add table
Reference in a new issue