/*** * * 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 #include #include #include #include #include "StudioModel.h" #include "GLWindow.h" #include "ViewerSettings.h" #include "ControlPanel.h" #include "activity.h" #include "stringlib.h" #include #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>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++; }