Paranoia2_original/utils/hlmv/studio_utils.cpp

1565 lines
43 KiB
C++

/***
*
* Copyright (c) 1998, Valve LLC. All rights reserved.
*
* This product contains software technology licensed from Id
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
* All Rights Reserved.
*
****/
// updates:
// 1-4-99 fixed file texture load and file read bug
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gl.h>
#include <GL/glu.h>
#include "StudioModel.h"
#include "GLWindow.h"
#include "ViewerSettings.h"
#include "ControlPanel.h"
#include "activity.h"
#include "stringlib.h"
#include <mx.h>
#include "mdlviewer.h"
StudioModel g_studioModel;
extern bool bUseWeaponOrigin;
extern bool bUseParanoiaFOV;
////////////////////////////////////////////////////////////////////////
void CBaseBoneSetup :: debugMsg( char *szFmt, ... )
{
static char buffer[1024];
va_list args;
int result;
va_start( args, szFmt );
result = Q_vsnprintf( buffer, 99999, szFmt, args );
va_end( args );
Sys_PrintLog( buffer );
}
mstudioanim_t *CBaseBoneSetup :: GetAnimSourceData( mstudioseqdesc_t *pseqdesc )
{
mstudioseqgroup_t *pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup;
if( pseqdesc->seqgroup == 0 )
return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqgroup->data + pseqdesc->animindex);
return (mstudioanim_t *)((byte *)g_studioModel.getAnimHeader( pseqdesc->seqgroup ) + pseqdesc->animindex);
}
void CBaseBoneSetup :: debugLine( const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest, float duration )
{
if( noDepthTest )
glDisable( GL_DEPTH_TEST );
glColor3ub( r, g, b );
glBegin( GL_LINES );
glVertex3fv( origin );
glVertex3fv( dest );
glEnd();
}
////////////////////////////////////////////////////////////////////////
static int g_tex_base = TEXTURE_COUNT;
//Mugsy - upped the maximum texture size to 512. All changes are the replacement of '256'
//with this define, MAX_TEXTURE_DIMS
#define MAX_TEXTURE_DIMS 1024
void StudioModel::UploadTexture(mstudiotexture_t *ptexture, byte *data, byte *srcpal, int name)
{
int row1[MAX_TEXTURE_DIMS], row2[MAX_TEXTURE_DIMS], col1[MAX_TEXTURE_DIMS], col2[MAX_TEXTURE_DIMS];
byte *pix1, *pix2, *pix3, *pix4;
byte *tex, *in, *out, pal[768];
int i, j;
// convert texture to power of 2
int outwidth;
for (outwidth = 1; outwidth < ptexture->width; outwidth <<= 1);
if (outwidth > MAX_TEXTURE_DIMS)
outwidth = MAX_TEXTURE_DIMS;
int outheight;
for (outheight = 1; outheight < ptexture->height; outheight <<= 1);
if (outheight > MAX_TEXTURE_DIMS)
outheight = MAX_TEXTURE_DIMS;
in = (byte *)malloc( ptexture->width * ptexture->height * 4);
if (!in) return;
if( ptexture->flags & STUDIO_NF_COLORMAP )
{
memcpy( pal, srcpal, 768 );
if( !Q_strnicmp( ptexture->name, "DM_Base", 7 ))
{
PaletteHueReplace( pal, g_viewerSettings.topcolor, PLATE_HUE_START, PLATE_HUE_END );
PaletteHueReplace( pal, g_viewerSettings.bottomcolor, SUIT_HUE_START, SUIT_HUE_END );
}
else
{
int len = Q_strlen( ptexture->name );
int low, mid, high;
char sz[32];
#if 1
// GoldSource parser
if( len == 18 || len == 22 )
{
char ch = ptexture->name[5];
if( len != 18 || ch == 'c' || ch == 'C' )
{
Q_strncpy( sz, &ptexture->name[7], 4 );
low = Q_atoi( sz );
Q_strncpy( sz, &ptexture->name[11], 4 );
mid = Q_atoi( sz );
if( len == 22 )
{
Q_strncpy( sz, &ptexture->name[15], 4 );
high = Q_atoi( sz );
}
else high = 0;
PaletteHueReplace( pal, g_viewerSettings.topcolor, low, mid );
if( high ) PaletteHueReplace( pal, g_viewerSettings.bottomcolor, mid + 1, high );
}
}
#else
// Xash3D parser
Q_strncpy( sz, ptexture->name + 7, 4 );
low = bound( 0, Q_atoi( sz ), 255 );
Q_strncpy( sz, ptexture->name + 11, 4 );
mid = bound( 0, Q_atoi( sz ), 255 );
Q_strncpy( sz, ptexture->name + 15, 4 );
high = bound( 0, Q_atoi( sz ), 255 );
PaletteHueReplace( pal, g_viewerSettings.topcolor, low, mid );
if( high ) PaletteHueReplace( pal, g_viewerSettings.bottomcolor, mid + 1, high );
#endif
}
}
else
{
memcpy( pal, srcpal, 768 );
}
// expand pixels to rgba
for (i=0 ; i < ptexture->width * ptexture->height; i++)
{
if(( ptexture->flags & STUDIO_NF_MASKED ) && data[i] == 255 )
{
in[i*4+0] = 0x00;
in[i*4+1] = 0x00;
in[i*4+2] = 0x00;
in[i*4+3] = 0x00;
}
else
{
in[i*4+0] = pal[data[i]*3+0];
in[i*4+1] = pal[data[i]*3+1];
in[i*4+2] = pal[data[i]*3+2];
in[i*4+3] = 0xFF;
}
}
tex = out = (byte *)malloc( outwidth * outheight * 4);
if (!out)
{
return;
}
for (i = 0; i < outwidth; i++)
{
col1[i] = (int) ((i + 0.25) * (ptexture->width / (float)outwidth));
col2[i] = (int) ((i + 0.75) * (ptexture->width / (float)outwidth));
}
for (i = 0; i < outheight; i++)
{
row1[i] = (int) ((i + 0.25) * (ptexture->height / (float)outheight)) * ptexture->width;
row2[i] = (int) ((i + 0.75) * (ptexture->height / (float)outheight)) * ptexture->width;
}
// scale down and convert to 32bit RGB
for (i=0 ; i<outheight ; i++)
{
for (j=0 ; j<outwidth ; j++, out += 4)
{
pix1 = &in[(row1[i] + col1[j]) * 4];
pix2 = &in[(row1[i] + col2[j]) * 4];
pix3 = &in[(row2[i] + col1[j]) * 4];
pix4 = &in[(row2[i] + col2[j]) * 4];
out[0] = (pix1[0] + pix2[0] + pix3[0] + pix4[0])>>2;
out[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2;
out[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2;
out[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2;
}
}
GLint outFormat = GL_RGB;
if( ptexture->flags & STUDIO_NF_MASKED )
outFormat = GL_RGBA;
glBindTexture( GL_TEXTURE_2D, name );
glHint( GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST );
glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE );
glTexImage2D( GL_TEXTURE_2D, 0, outFormat, outwidth, outheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex );
glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
const char *extensions = (const char *)glGetString( GL_EXTENSIONS );
// check for anisotropy support
if( Q_strstr( extensions, "GL_EXT_texture_filter_anisotropic" ))
{
float anisotropy = 1.0f;
glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropy );
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy );
}
free( tex );
free( in );
}
void StudioModel :: FreeModel( void )
{
if( g_viewerSettings.numModelChanges )
{
if( !mxMessageBox( g_GlWindow, "Model has changes. Do you wish to save them?", g_appTitle, MX_MB_YESNO | MX_MB_QUESTION ))
{
char *ptr = (char *)mxGetSaveFileName( g_GlWindow , g_viewerSettings.modelPath, "*.mdl", g_viewerSettings.modelPath );
if( ptr )
{
char filename[256];
char ext[16];
strcpy( filename, ptr );
strcpy( ext, mx_getextension( filename ));
if( mx_strcasecmp( ext, ".mdl" ))
strcat( filename, ".mdl" );
if( !g_studioModel.SaveModel( filename ))
mxMessageBox( g_GlWindow, "Error saving model.", g_appTitle, MX_MB_OK | MX_MB_ERROR);
}
}
g_viewerSettings.numModelChanges = 0; // all the settings are handled or invalidated
}
if (m_pJiggleBones)
{
delete m_pJiggleBones;
m_pJiggleBones = NULL;
}
int i;
if( m_ptexturehdr )
{
// deleting textures
int textures[MAXSTUDIOSKINS];
for (i = 0; i < m_ptexturehdr->numtextures; i++)
textures[i] = g_tex_base + i;
glDeleteTextures (m_ptexturehdr->numtextures, (const GLuint *)textures);
}
if (m_pstudiohdr)
free (m_pstudiohdr);
if (m_ptexturehdr && m_owntexmodel)
free (m_ptexturehdr);
g_boneSetup.SetStudioPointers( NULL, NULL );
g_viewerSettings.numSourceChanges = 0;
m_pstudiohdr = m_ptexturehdr = 0;
remap_textures = false;
m_owntexmodel = false;
m_numeditfields = 0;
for (i = 0; i < 32; i++)
{
if (m_panimhdr[i])
{
free (m_panimhdr[i]);
m_panimhdr[i] = 0;
}
}
}
void StudioModel::RemapTextures( void )
{
if( !remap_textures ) return;
studiohdr_t *phdr = getTextureHeader();
mstudiotexture_t *ptexture;
byte *pin = (byte *)phdr;
if (phdr && phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS)
{
ptexture = (mstudiotexture_t *)(pin + phdr->textureindex);
for (int i = 0; i < phdr->numtextures; i++)
{
if( !( ptexture[i].flags & STUDIO_NF_COLORMAP ))
continue;
UploadTexture( &ptexture[i], pin + ptexture[i].index, pin + ptexture[i].width * ptexture[i].height + ptexture[i].index, g_tex_base + i );
}
}
remap_textures = false;
}
studiohdr_t *StudioModel::LoadModel( char *modelname )
{
FILE *fp;
long size;
void *buffer;
if (!modelname)
return 0;
// load the model
if( (fp = fopen( modelname, "rb" )) == NULL)
return 0;
fseek( fp, 0, SEEK_END );
size = ftell( fp );
fseek( fp, 0, SEEK_SET );
buffer = malloc( size );
if (!buffer || size <= 0)
{
fclose (fp);
return 0;
}
fread( buffer, size, 1, fp );
fclose( fp );
byte *pin;
studiohdr_t *phdr;
mstudiotexture_t *ptexture;
pin = (byte *)buffer;
phdr = (studiohdr_t *)pin;
ptexture = (mstudiotexture_t *)(pin + phdr->textureindex);
if (strncmp ((const char *) buffer, "IDST", 4) &&
strncmp ((const char *) buffer, "IDSQ", 4))
{
if (!strncmp ((const char *) buffer, "IDPO", 4 ))
mxMessageBox( g_GlWindow, "Quake 1 models doesn't supported.", g_appTitle, MX_MB_OK | MX_MB_ERROR );
else mxMessageBox( g_GlWindow, "Unknown file format.", g_appTitle, MX_MB_OK | MX_MB_ERROR );
free (buffer);
return 0;
}
if (!strncmp ((const char *) buffer, "IDSQ", 4) && !m_pstudiohdr)
{
free (buffer);
return 0;
}
if( phdr->version != STUDIO_VERSION )
{
mxMessageBox( g_GlWindow, "Unsupported studio version.", g_appTitle, MX_MB_OK | MX_MB_ERROR );
free (buffer);
return 0;
}
if (phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS)
{
for (int i = 0; i < phdr->numtextures; i++)
{
if( !Q_strnicmp( ptexture[i].name, "DM_Base", 7 ) || !Q_strnicmp( ptexture[i].name, "remap", 5 ))
ptexture[i].flags |= STUDIO_NF_COLORMAP;
UploadTexture( &ptexture[i], pin + ptexture[i].index, pin + ptexture[i].width * ptexture[i].height + ptexture[i].index, g_tex_base + i );
}
}
m_pJiggleBones = NULL;
m_numeditfields = 0;
if (!m_pstudiohdr)
m_pstudiohdr = (studiohdr_t *)buffer;
return (studiohdr_t *)buffer;
}
bool StudioModel::PostLoadModel( char *modelname )
{
// preload textures
if (m_pstudiohdr->numtextures == 0)
{
char texturename[256];
strcpy( texturename, modelname );
strcpy( &texturename[strlen(texturename) - 4], "T.mdl" );
m_ptexturehdr = LoadModel( texturename );
if (!m_ptexturehdr)
{
FreeModel ();
return false;
}
m_owntexmodel = true;
}
else
{
m_ptexturehdr = m_pstudiohdr;
m_owntexmodel = false;
}
// preload animations
if (m_pstudiohdr->numseqgroups > 1)
{
for (int i = 1; i < m_pstudiohdr->numseqgroups; i++)
{
char seqgroupname[256];
strcpy( seqgroupname, modelname );
sprintf( &seqgroupname[strlen(seqgroupname) - 4], "%02d.mdl", i );
m_panimhdr[i] = LoadModel( seqgroupname );
if (!m_panimhdr[i])
{
FreeModel ();
return false;
}
}
}
SetSequence (0);
SetController (0, 0.0f);
SetController (1, 0.0f);
SetController (2, 0.0f);
SetController (3, 0.0f);
SetMouth (0.0f);
int n;
for (n = 0; n < m_pstudiohdr->numbodyparts; n++)
SetBodygroup (n, 0);
SetSkin (0);
char basename[64];
COM_FileBase( modelname, basename );
if( !Q_strnicmp( basename, "v_", 2 ))
{
g_MDLViewer->checkboxSet( IDC_OPTIONS_WEAPONORIGIN, true );
bUseWeaponOrigin = true;
}
else
{
g_MDLViewer->checkboxSet( IDC_OPTIONS_WEAPONORIGIN, false );
bUseWeaponOrigin = false;
}
// reset all the changes
g_viewerSettings.numModelChanges = 0;
g_viewerSettings.numSourceChanges = 0;
bUseParanoiaFOV = false;
g_boneSetup.SetStudioPointers( m_pstudiohdr, m_poseparameter );
// set poseparam sliders to their default values
g_boneSetup.CalcDefaultPoseParameters( m_poseparameter );
if( FBitSet( m_pstudiohdr->flags, STUDIO_HAS_BONEINFO ))
{
// NOTE: extended boneinfo goes immediately after bones
mstudioboneinfo_t *boneinfo = (mstudioboneinfo_t *)((byte *)m_pstudiohdr + m_pstudiohdr->boneindex + m_pstudiohdr->numbones * sizeof( mstudiobone_t ));
for( n = 0; n < m_pstudiohdr->numbones; n++ )
LoadLocalMatrix( n, &boneinfo[n] );
}
// analyze ACT_VM_ for paranoia viewmodels
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex);
for( n = 0; n < m_pstudiohdr->numseq; n++ )
{
if( pseqdesc[n].activity >= ACT_VM_NONE && pseqdesc[n].activity <= ACT_VM_RESERVED4 )
{
g_MDLViewer->checkboxSet( IDC_OPTIONS_WEAPONORIGIN, true );
bUseWeaponOrigin = bUseParanoiaFOV = true;
break; // it's P2 viewmodel!
}
}
return true;
}
void StudioModel :: LoadLocalMatrix( int bone, mstudioboneinfo_t *boneinfo )
{
// transform Valve matrix to Xash matrix
m_plocaltransform[bone][0][0] = boneinfo->poseToBone[0][0];
m_plocaltransform[bone][0][1] = boneinfo->poseToBone[1][0];
m_plocaltransform[bone][0][2] = boneinfo->poseToBone[2][0];
m_plocaltransform[bone][1][0] = boneinfo->poseToBone[0][1];
m_plocaltransform[bone][1][1] = boneinfo->poseToBone[1][1];
m_plocaltransform[bone][1][2] = boneinfo->poseToBone[2][1];
m_plocaltransform[bone][2][0] = boneinfo->poseToBone[0][2];
m_plocaltransform[bone][2][1] = boneinfo->poseToBone[1][2];
m_plocaltransform[bone][2][2] = boneinfo->poseToBone[2][2];
m_plocaltransform[bone][3][0] = boneinfo->poseToBone[0][3];
m_plocaltransform[bone][3][1] = boneinfo->poseToBone[1][3];
m_plocaltransform[bone][3][2] = boneinfo->poseToBone[2][3];
}
bool StudioModel::SaveModel ( char *modelname )
{
if (!modelname)
return false;
if (!m_pstudiohdr)
return false;
FILE *file;
file = fopen (modelname, "wb");
if (!file)
return false;
fwrite (m_pstudiohdr, sizeof (byte), m_pstudiohdr->length, file);
fclose (file);
// write texture model
if (m_owntexmodel && m_ptexturehdr)
{
char texturename[256];
strcpy( texturename, modelname );
strcpy( &texturename[strlen(texturename) - 4], "T.mdl" );
file = fopen (texturename, "wb");
if (file)
{
fwrite (m_ptexturehdr, sizeof (byte), m_ptexturehdr->length, file);
fclose (file);
}
}
// write seq groups
if (m_pstudiohdr->numseqgroups > 1)
{
for (int i = 1; i < m_pstudiohdr->numseqgroups; i++)
{
char seqgroupname[256];
strcpy( seqgroupname, modelname );
sprintf( &seqgroupname[strlen(seqgroupname) - 4], "%02d.mdl", i );
file = fopen (seqgroupname, "wb");
if (file)
{
fwrite (m_panimhdr[i], sizeof (byte), m_panimhdr[i]->length, file);
fclose (file);
}
}
}
return true;
}
void StudioModel::PaletteHueReplace( byte *palSrc, int newHue, int start, int end )
{
float r, g, b;
float maxcol, mincol;
float hue, val, sat;
int i;
hue = (float)(newHue * ( 360.0f / 255 ));
for( i = start; i <= end; i++ )
{
r = palSrc[i*3+0];
g = palSrc[i*3+1];
b = palSrc[i*3+2];
maxcol = max( max( r, g ), b ) / 255.0f;
mincol = min( min( r, g ), b ) / 255.0f;
if( maxcol == 0 ) continue;
val = maxcol;
sat = (maxcol - mincol) / maxcol;
mincol = val * (1.0f - sat);
if( hue <= 120.0f )
{
b = mincol;
if( hue < 60 )
{
r = val;
g = mincol + hue * (val - mincol) / (120.0f - hue);
}
else
{
g = val;
r = mincol + (120.0f - hue) * (val - mincol) / hue;
}
}
else if( hue <= 240.0f )
{
r = mincol;
if( hue < 180.0f )
{
g = val;
b = mincol + (hue - 120.0f) * (val - mincol) / (240.0f - hue);
}
else
{
b = val;
g = mincol + (240.0f - hue) * (val - mincol) / (hue - 120.0f);
}
}
else
{
g = mincol;
if( hue < 300.0f )
{
b = val;
r = mincol + (hue - 240.0f) * (val - mincol) / (360.0f - hue);
}
else
{
r = val;
b = mincol + (360.0f - hue) * (val - mincol) / (hue - 240.0f);
}
}
palSrc[i*3+0] = (byte)(r * 255);
palSrc[i*3+1] = (byte)(g * 255);
palSrc[i*3+2] = (byte)(b * 255);
}
}
////////////////////////////////////////////////////////////////////////
int StudioModel::GetSequence( )
{
return m_sequence;
}
int StudioModel :: SetSequence( int iSequence )
{
if (iSequence > m_pstudiohdr->numseq)
return m_sequence;
mstudioseqdesc_t *poldseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + m_sequence;
mstudioseqdesc_t *pnewseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + iSequence;
// sequence has changed, hold the previous sequence info
if( iSequence != m_sequence && !bUseWeaponOrigin && !FBitSet( pnewseqdesc->flags, STUDIO_SNAP ))
{
blend_sequence_t *seqblending;
// move current sequence into circular buffer
m_current_seqblend = (m_current_seqblend + 1) & MASK_SEQBLENDS;
seqblending = &m_seqblend[m_current_seqblend];
seqblending->blendtime = m_flTime;
seqblending->sequence = m_sequence;
seqblending->cycle = Q_min( m_cycle, 1.0f );
seqblending->fadeout = Q_min( poldseqdesc->fadeouttime / 100.0f, pnewseqdesc->fadeintime / 100.0f );
if( seqblending->fadeout <= 0.0f ) seqblending->fadeout = 0.2f; // force to default
// save current blends to right lerping from last sequence
for( int i = 0; i < 2; i++ )
seqblending->blending[i] = m_blending[i];
}
m_sequence = iSequence;
sequence_reset = true;
m_cycle = 0;
return m_sequence;
}
void StudioModel :: ExtractBbox( Vector &mins, Vector &maxs )
{
mstudioseqdesc_t *pseqdesc;
if( !m_pstudiohdr || m_pstudiohdr->numseq <= 0 )
{
mins = maxs = g_vecZero;
return;
}
m_sequence = bound( 0, m_sequence, m_pstudiohdr->numseq - 1 );
pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + m_sequence;
mins = pseqdesc->bbmin;
maxs = pseqdesc->bbmax;
}
float StudioModel :: GetDuration( int iSequence )
{
return g_boneSetup.LocalDuration( iSequence );
}
float StudioModel :: GetDuration( void )
{
return GetDuration( m_sequence );
}
void StudioModel::GetSequenceInfo( float *pflFrameRate, float *pflGroundSpeed )
{
mstudioseqdesc_t *pseqdesc;
pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + (int)m_sequence;
if (pseqdesc->numframes > 1)
{
*pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1);
*pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] );
*pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1);
}
else
{
*pflFrameRate = 256.0;
*pflGroundSpeed = 0.0;
}
}
void StudioModel :: GetMovement( float &prevCycle, Vector &vecPos, Vector &vecAngles )
{
if( !m_pstudiohdr )
{
vecPos.Init();
vecAngles.Init();
return;
}
// assume that changes < -0.5 are loops....
if( m_cycle - prevCycle < -0.5f )
{
prevCycle = prevCycle - 1.0f;
}
g_boneSetup.SeqMovement( m_sequence, prevCycle, m_cycle, vecPos, vecAngles );
prevCycle = m_cycle;
}
int StudioModel :: getNumBlendings( void )
{
if( !m_pstudiohdr ) return 0;
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + m_sequence;
return pseqdesc->numblends;
}
int StudioModel :: hasLocalBlending( void )
{
if( !m_pstudiohdr ) return 0;
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + m_sequence;
return ( pseqdesc->numblends > 1 ) && !FBitSet( pseqdesc->flags, STUDIO_BLENDPOSE );
}
float StudioModel::SetController( int iController, float flValue )
{
if (!m_pstudiohdr)
return 0.0f;
mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bonecontrollerindex);
// find first controller that matches the index
for (int i = 0; i < m_pstudiohdr->numbonecontrollers; i++, pbonecontroller++)
{
if (pbonecontroller->index == iController)
break;
}
if (i >= m_pstudiohdr->numbonecontrollers)
return flValue;
// wrap 0..360 if it's a rotational controller
if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR))
{
// ugly hack, invert value if end < start
if (pbonecontroller->end < pbonecontroller->start)
flValue = -flValue;
// does the controller not wrap?
if (pbonecontroller->start + 359.0 >= pbonecontroller->end)
{
if (flValue > ((pbonecontroller->start + pbonecontroller->end) / 2.0) + 180)
flValue = flValue - 360;
if (flValue < ((pbonecontroller->start + pbonecontroller->end) / 2.0) - 180)
flValue = flValue + 360;
}
else
{
if (flValue > 360)
flValue = flValue - (int)(flValue / 360.0) * 360.0;
else if (flValue < 0)
flValue = flValue + (int)((flValue / -360.0) + 1) * 360.0;
}
}
int setting = (int) (255 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start));
if (setting < 0) setting = 0;
if (setting > 255) setting = 255;
m_controller[iController] = setting;
return setting * (1.0 / 255.0) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start;
}
int StudioModel :: LookupPoseParameter( char const *szName )
{
if( !m_pstudiohdr )
return false;
for( int iParameter = 0; iParameter < g_boneSetup.CountPoseParameters(); iParameter++ )
{
const mstudioposeparamdesc_t *pPose = g_boneSetup.pPoseParameter( iParameter );
if( !Q_stricmp( szName, pPose->name ))
{
return iParameter;
}
}
return -1;
}
float StudioModel::SetPoseParameter( char const *szName, float flValue )
{
return SetPoseParameter( LookupPoseParameter( szName ), flValue );
}
float StudioModel::SetPoseParameter( int iParameter, float flValue )
{
if( !m_pstudiohdr )
return 0.0f;
return g_boneSetup.SetPoseParameter( iParameter, flValue, m_poseparameter[iParameter] );
}
float StudioModel::GetPoseParameter( char const *szName )
{
return GetPoseParameter( LookupPoseParameter( szName ));
}
float* StudioModel::GetPoseParameters()
{
return m_poseparameter;
}
float StudioModel::GetPoseParameter( int iParameter )
{
if( !m_pstudiohdr )
return 0.0f;
return g_boneSetup.GetPoseParameter( iParameter, m_poseparameter[iParameter] );
}
bool StudioModel::GetPoseParameterRange( int iParameter, float *pflMin, float *pflMax )
{
*pflMin = 0;
*pflMax = 0;
if( !m_pstudiohdr )
return false;
if( iParameter < 0 || iParameter >= g_boneSetup.CountPoseParameters( ))
return false;
const mstudioposeparamdesc_t *pPose = g_boneSetup.pPoseParameter( iParameter );
*pflMin = pPose->start;
*pflMax = pPose->end;
return true;
}
float StudioModel::SetMouth( float flValue )
{
if (!m_pstudiohdr)
return 0.0f;
mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bonecontrollerindex);
// find first controller that matches the mouth
for (int i = 0; i < m_pstudiohdr->numbonecontrollers; i++, pbonecontroller++)
{
if (pbonecontroller->index == STUDIO_MOUTH)
break;
}
// wrap 0..360 if it's a rotational controller
if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR))
{
// ugly hack, invert value if end < start
if (pbonecontroller->end < pbonecontroller->start)
flValue = -flValue;
// does the controller not wrap?
if (pbonecontroller->start + 359.0 >= pbonecontroller->end)
{
if (flValue > ((pbonecontroller->start + pbonecontroller->end) / 2.0) + 180)
flValue = flValue - 360;
if (flValue < ((pbonecontroller->start + pbonecontroller->end) / 2.0) - 180)
flValue = flValue + 360;
}
else
{
if (flValue > 360)
flValue = flValue - (int)(flValue / 360.0) * 360.0;
else if (flValue < 0)
flValue = flValue + (int)((flValue / -360.0) + 1) * 360.0;
}
}
int setting = (int) (64 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start));
if (setting < 0) setting = 0;
if (setting > 64) setting = 64;
m_mouth = setting;
return setting * (1.0 / 64.0) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start;
}
void StudioModel :: SetBlendValue( int iBlender, int iValue )
{
m_blending[iBlender] = bound( 0, iValue, 255 );
if( hasLocalBlending( ))
m_poseparameter[iBlender] = bound( 0.0f, (float)iValue / 255.0f, 1.0f );
}
float StudioModel::SetBlending( int iBlender, float flValue )
{
mstudioseqdesc_t *pseqdesc;
int iOutBlend = iBlender;
if (!m_pstudiohdr)
return 0.0f;
pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + (int)m_sequence;
if( pseqdesc->numblends == 4 || pseqdesc->numblends == 9 )
iBlender = 0; // grab info from first blender
if (pseqdesc->blendtype[iBlender] == 0)
return flValue;
if (pseqdesc->blendtype[iBlender] & (STUDIO_XR | STUDIO_YR | STUDIO_ZR))
{
// ugly hack, invert value if end < start
if (pseqdesc->blendend[iBlender] < pseqdesc->blendstart[iBlender])
flValue = -flValue;
// does the controller not wrap?
if (pseqdesc->blendstart[iBlender] + 359.0 >= pseqdesc->blendend[iBlender])
{
if (flValue > ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) + 180)
flValue = flValue - 360;
if (flValue < ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) - 180)
flValue = flValue + 360;
}
}
int setting = (int) (255 * (flValue - pseqdesc->blendstart[iBlender]) / (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]));
if (setting < 0) setting = 0;
if (setting > 255) setting = 255;
m_blending[iOutBlend] = setting;
if( hasLocalBlending( ))
m_poseparameter[iOutBlend] = bound( 0.0f, (float)setting / 255.0f, 1.0f );
// return setting * (1.0 / 255.0) * (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]) + pseqdesc->blendstart[iBlender];
return setting;
}
int StudioModel::SetBodygroup( int iGroup, int iValue )
{
if (!m_pstudiohdr)
return 0;
if (iGroup > m_pstudiohdr->numbodyparts)
return -1;
mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bodypartindex) + iGroup;
int iCurrent = (m_bodynum / pbodypart->base) % pbodypart->nummodels;
if (iValue >= pbodypart->nummodels)
return iCurrent;
m_bodynum = (m_bodynum - (iCurrent * pbodypart->base) + (iValue * pbodypart->base));
return m_bodynum;
}
int StudioModel::SetSkin( int iValue )
{
if (!m_pstudiohdr)
return 0;
if (iValue >= m_pstudiohdr->numskinfamilies)
{
return m_skinnum;
}
m_skinnum = iValue;
return iValue;
}
void StudioModel::ComputeWeightColor( mstudioboneweight_t *boneweights, Vector &result )
{
int numbones = 0;
for( int i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ )
{
if( boneweights->bone[i] != -1 )
numbones++;
}
if( !g_viewerSettings.studio_blendweights )
numbones = 1;
switch( numbones )
{
case 0:
result = Vector( 0.0f, 0.0f, 0.0f );
break;
case 1:
result = Vector( 0.0f, 1.0f, 0.0f );
break;
case 2:
result = Vector( 1.0f, 1.0f, 0.0f );
break;
case 3:
result = Vector( 1.0f, 0.0f, 1.0f );
break;
case 4:
result = Vector( 1.0f, 0.0f, 1.0f );
break;
default:
result = Vector( 1.0f, 1.0f, 1.0f );
break;
}
}
void StudioModel::ComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 &result )
{
float flWeight0, flWeight1, flWeight2, flWeight3;
int numbones = 0;
for( int i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ )
{
if( boneweights->bone[i] != -1 )
numbones++;
}
if( !g_viewerSettings.studio_blendweights )
numbones = 1;
if( numbones == 4 )
{
matrix3x4 &boneMat0 = m_pworldtransform[boneweights->bone[0]];
matrix3x4 &boneMat1 = m_pworldtransform[boneweights->bone[1]];
matrix3x4 &boneMat2 = m_pworldtransform[boneweights->bone[2]];
matrix3x4 &boneMat3 = m_pworldtransform[boneweights->bone[3]];
flWeight0 = boneweights->weight[0] / 255.0f;
flWeight1 = boneweights->weight[1] / 255.0f;
flWeight2 = boneweights->weight[2] / 255.0f;
flWeight3 = boneweights->weight[3] / 255.0f;
result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3;
result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3;
result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2 + boneMat3[0][2] * flWeight3;
result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2 + boneMat3[1][0] * flWeight3;
result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2 + boneMat3[1][1] * flWeight3;
result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2 + boneMat3[1][2] * flWeight3;
result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2 + boneMat3[2][0] * flWeight3;
result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2 + boneMat3[2][1] * flWeight3;
result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2 + boneMat3[2][2] * flWeight3;
result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2 + boneMat3[3][0] * flWeight3;
result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2 + boneMat3[3][1] * flWeight3;
result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2 + boneMat3[3][2] * flWeight3;
}
else if( numbones == 3 )
{
matrix3x4 &boneMat0 = m_pworldtransform[boneweights->bone[0]];
matrix3x4 &boneMat1 = m_pworldtransform[boneweights->bone[1]];
matrix3x4 &boneMat2 = m_pworldtransform[boneweights->bone[2]];
flWeight0 = boneweights->weight[0] / 255.0f;
flWeight1 = boneweights->weight[1] / 255.0f;
flWeight2 = boneweights->weight[2] / 255.0f;
result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2;
result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2;
result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2;
result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2;
result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2;
result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2;
result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2;
result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2;
result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2;
result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2;
result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2;
result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2;
}
else if( numbones == 2 )
{
matrix3x4 &boneMat0 = m_pworldtransform[boneweights->bone[0]];
matrix3x4 &boneMat1 = m_pworldtransform[boneweights->bone[1]];
flWeight0 = boneweights->weight[0] / 255.0f;
flWeight1 = boneweights->weight[1] / 255.0f;
// NOTE: Inlining here seems to make a fair amount of difference
result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1;
result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1;
result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1;
result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1;
result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1;
result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1;
result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1;
result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1;
result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1;
result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1;
result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1;
result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1;
}
else
{
result = m_pworldtransform[boneweights->bone[0]];
}
}
void StudioModel::scaleMeshes (float scale)
{
if (!m_pstudiohdr)
return;
int i, j, k;
// scale verts
int tmp = m_bodynum;
for (i = 0; i < m_pstudiohdr->numbodyparts; i++)
{
mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bodypartindex) + i;
for (j = 0; j < pbodypart->nummodels; j++)
{
SetBodygroup (i, j);
SetupModel (i);
Vector *pstudioverts = (Vector *)((byte *)m_pstudiohdr + m_pmodel->vertindex);
for (k = 0; k < m_pmodel->numverts; k++)
pstudioverts[k] *= scale;
}
}
m_bodynum = tmp;
// scale complex hitboxes
mstudiobbox_t *pbboxes = (mstudiobbox_t *) ((byte *) m_pstudiohdr + m_pstudiohdr->hitboxindex);
for (i = 0; i < m_pstudiohdr->numhitboxes; i++)
{
pbboxes[i].bbmin *= scale;
pbboxes[i].bbmax *= scale;
}
// scale bounding boxes
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex);
for (i = 0; i < m_pstudiohdr->numseq; i++)
{
pseqdesc[i].bbmin *= scale;
pseqdesc[i].bbmax *= scale;
}
// maybe scale exeposition, pivots, attachments
g_viewerSettings.numModelChanges++;
}
void StudioModel::scaleBones (float scale)
{
if (!m_pstudiohdr)
return;
mstudiobone_t *pbones = (mstudiobone_t *) ((byte *) m_pstudiohdr + m_pstudiohdr->boneindex);
for (int i = 0; i < m_pstudiohdr->numbones; i++)
{
for (int j = 0; j < 3; j++)
{
pbones[i].value[j] *= scale;
pbones[i].scale[j] *= scale;
}
}
g_viewerSettings.numModelChanges++;
}
void StudioModel::SetTopColor( int color )
{
if( g_viewerSettings.topcolor != color )
remap_textures = true;
g_viewerSettings.topcolor = color;
}
void StudioModel::SetBottomColor( int color )
{
if( g_viewerSettings.bottomcolor != color )
remap_textures = true;
g_viewerSettings.bottomcolor = color;
}
bool StudioModel::SetEditType( int type )
{
if( type < 0 || type >= m_numeditfields )
{
m_pedit = NULL;
return false;
}
m_pedit = &m_editfields[type];
// is allowed to edit size
if( m_pedit->type == TYPE_BBOX || m_pedit->type == TYPE_CBOX || m_pedit->type == TYPE_HITBOX )
return true;
return false;
}
bool StudioModel::SetEditMode( int mode )
{
char str[256];
if( mode == EDIT_MODEL && g_viewerSettings.editMode == EDIT_SOURCE && g_viewerSettings.numSourceChanges > 0 )
{
Q_strcpy( str, "we have some virtual changes for QC-code.\nApply them to real model or all the changes will be lost?" );
int ret = mxMessageBox( g_GlWindow, str, g_appTitle, MX_MB_YESNOCANCEL | MX_MB_QUESTION );
if( ret == 2 ) return false; // cancelled
if( ret == 0 ) UpdateEditFields( true );
else UpdateEditFields( false );
}
g_viewerSettings.editMode = mode;
return true;
}
void StudioModel::ReadEditField( studiohdr_t *phdr, edit_field_t *ed )
{
if( !ed || !phdr ) return;
// get initial values
switch( ed->type )
{
case TYPE_ORIGIN:
ed->origin = g_vecZero; // !!!
break;
case TYPE_BBOX:
ed->mins = phdr->min;
ed->maxs = phdr->max;
break;
case TYPE_CBOX:
ed->mins = phdr->bbmin;
ed->maxs = phdr->bbmax;
break;
case TYPE_EYEPOSITION:
ed->origin = phdr->eyeposition;
break;
case TYPE_ATTACHMENT:
{
mstudioattachment_t *pattachment = (mstudioattachment_t *) ((byte *)phdr + phdr->attachmentindex) + ed->id;
ed->origin = pattachment->org;
ed->bone = pattachment->bone;
}
break;
case TYPE_HITBOX:
{
mstudiobbox_t *phitbox = (mstudiobbox_t *) ((byte *) m_pstudiohdr + m_pstudiohdr->hitboxindex) + ed->id;
ed->hitgroup = phitbox->group;
ed->mins = phitbox->bbmin;
ed->maxs = phitbox->bbmax;
ed->bone = phitbox->bone;
}
break;
}
}
void StudioModel::WriteEditField( studiohdr_t *phdr, edit_field_t *ed )
{
if( !ed || !phdr ) return;
// get initial values
switch( ed->type )
{
case TYPE_ORIGIN:
{
mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pstudiohdr + m_pstudiohdr->boneindex);
for( int i = 0; i < m_pstudiohdr->numbones; i++, pbone++ )
{
// studioformat is allow multiple root bones...
if( pbone->parent != -1 ) continue;
pbone->value[0] += ed->origin.x;
pbone->value[1] += ed->origin.y;
pbone->value[2] += ed->origin.z;
}
ed->origin = g_vecZero;
}
break;
case TYPE_BBOX:
phdr->min = ed->mins;
phdr->max = ed->maxs;
break;
case TYPE_CBOX:
phdr->bbmin = ed->mins;
phdr->bbmax = ed->maxs;
break;
case TYPE_EYEPOSITION:
phdr->eyeposition = ed->origin;
break;
case TYPE_ATTACHMENT:
{
mstudioattachment_t *pattachment = (mstudioattachment_t *) ((byte *)phdr + phdr->attachmentindex) + ed->id;
pattachment->org = ed->origin;
pattachment->bone = ed->bone;
}
break;
case TYPE_HITBOX:
{
mstudiobbox_t *phitbox = (mstudiobbox_t *) ((byte *) m_pstudiohdr + m_pstudiohdr->hitboxindex) + ed->id;
phitbox->group = ed->hitgroup;
phitbox->bbmin = ed->mins;
phitbox->bbmax = ed->maxs;
phitbox->bone = ed->bone;
}
break;
default: return;
}
g_viewerSettings.numModelChanges++;
}
void StudioModel::UpdateEditFields( bool write_to_model )
{
studiohdr_t *phdr = g_studioModel.getStudioHeader ();
for( int i = 0; i < m_numeditfields; i++ )
{
edit_field_t *ed = &m_editfields[i];
if( write_to_model )
WriteEditField( phdr, ed );
else ReadEditField( phdr, ed );
}
if( write_to_model && phdr )
g_viewerSettings.numSourceChanges = 0;
}
bool StudioModel::AddEditField( int type, int id )
{
if( m_numeditfields >= MAX_EDITFIELDS )
{
mxMessageBox( g_GlWindow, "Edit fields limit exceeded.", g_appTitle, MX_MB_OK | MX_MB_ERROR );
return false;
}
studiohdr_t *phdr = g_studioModel.getStudioHeader ();
if( phdr )
{
edit_field_t *ed = &m_editfields[m_numeditfields++];
// initialize fields
ed->origin = g_vecZero;
ed->mins = g_vecZero;
ed->maxs = g_vecZero;
ed->hitgroup = 0;
ed->type = type;
ed->bone = -1;
ed->id = id;
// read initial values from studiomodel
ReadEditField( phdr, ed );
return true;
}
return false;
}
const char *StudioModel::getQCcode( void )
{
studiohdr_t *phdr = g_studioModel.getStudioHeader ();
static char str[256];
if( !m_pedit ) return "";
edit_field_t *ed = m_pedit;
Q_strncpy( str, "not available", sizeof( str ));
if( !phdr ) return str;
// get initial values
switch( ed->type )
{
case TYPE_ORIGIN:
if( g_viewerSettings.editMode == EDIT_SOURCE )
Q_snprintf( str, sizeof( str ), "$origin %g %g %g", ed->origin.y, ed->origin.x, ed->origin.z );
break;
case TYPE_BBOX:
Q_snprintf( str, sizeof( str ), "$bbox %g %g %g %g %g %g", ed->mins.x, ed->mins.y, ed->mins.z, ed->maxs.x, ed->maxs.y, ed->maxs.z );
break;
case TYPE_CBOX:
Q_snprintf( str, sizeof( str ), "$cbox %g %g %g %g %g %g", ed->mins.x, ed->mins.y, ed->mins.z, ed->maxs.x, ed->maxs.y, ed->maxs.z );
break;
case TYPE_EYEPOSITION:
Q_snprintf( str, sizeof( str ), "$eyeposition %g %g %g", ed->origin.x, ed->origin.y, ed->origin.z );
break;
case TYPE_ATTACHMENT:
{
mstudiobone_t *pbone = (mstudiobone_t *)((byte *)phdr + phdr->boneindex) + ed->bone;
mstudioattachment_t *pattachment = (mstudioattachment_t *)((byte *)phdr + phdr->attachmentindex) + ed->id;
Q_snprintf( str, sizeof( str ), "$attachment \"%s\" \"%s\" %g %g %g", pattachment->name, pbone->name, ed->origin.x, ed->origin.y, ed->origin.z );
}
break;
case TYPE_HITBOX:
{
mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pstudiohdr + m_pstudiohdr->boneindex) + ed->bone;
Q_snprintf( str, sizeof( str ), "$hbox %i \"%s\" %g %g %g %g %g %g", ed->hitgroup, pbone->name, ed->mins.x, ed->mins.y, ed->mins.z, ed->maxs.x, ed->maxs.y, ed->maxs.z );
}
break;
}
return str;
}
void StudioModel::updateModel( void )
{
if( !update_model ) return;
WriteEditField( m_pstudiohdr, m_pedit );
update_model = false;
}
void StudioModel::editPosition( float step, int type )
{
if( !m_pedit ) return;
if( g_viewerSettings.editSize )
{
switch( type )
{
case IDC_MOVE_PX:
m_pedit->mins[0] -= step;
m_pedit->maxs[0] += step;
break;
case IDC_MOVE_NX:
m_pedit->mins[0] += step;
m_pedit->maxs[0] -= step;
break;
case IDC_MOVE_PY:
m_pedit->mins[1] -= step;
m_pedit->maxs[1] += step;
break;
case IDC_MOVE_NY:
m_pedit->mins[1] += step;
m_pedit->maxs[1] -= step;
break;
case IDC_MOVE_PZ:
m_pedit->mins[2] -= step;
m_pedit->maxs[2] += step;
break;
case IDC_MOVE_NZ:
m_pedit->mins[2] += step;
m_pedit->maxs[2] -= step;
break;
}
}
else
{
if( m_pedit->type == TYPE_BBOX || m_pedit->type == TYPE_CBOX || m_pedit->type == TYPE_HITBOX )
{
switch( type )
{
case IDC_MOVE_PX:
m_pedit->mins[0] += step;
m_pedit->maxs[0] += step;
break;
case IDC_MOVE_NX:
m_pedit->mins[0] -= step;
m_pedit->maxs[0] -= step;
break;
case IDC_MOVE_PY:
m_pedit->mins[1] += step;
m_pedit->maxs[1] += step;
break;
case IDC_MOVE_NY:
m_pedit->mins[1] -= step;
m_pedit->maxs[1] -= step;
break;
case IDC_MOVE_PZ:
m_pedit->mins[2] += step;
m_pedit->maxs[2] += step;
break;
case IDC_MOVE_NZ:
m_pedit->mins[2] -= step;
m_pedit->maxs[2] -= step;
break;
}
}
else
{
switch( type )
{
case IDC_MOVE_PX: m_pedit->origin[0] += step; break;
case IDC_MOVE_NX: m_pedit->origin[0] -= step; break;
case IDC_MOVE_PY: m_pedit->origin[1] += step; break;
case IDC_MOVE_NY: m_pedit->origin[1] -= step; break;
case IDC_MOVE_PZ: m_pedit->origin[2] += step; break;
case IDC_MOVE_NZ: m_pedit->origin[2] -= step; break;
}
}
}
if( g_viewerSettings.editMode == EDIT_MODEL )
update_model = true;
else g_viewerSettings.numSourceChanges++;
}