#include "global.h" #include "Model.h" #include "ModelTypes.h" #include "RageMath.h" #include "RageDisplay.h" #include "RageUtil.h" #include "RageTextureManager.h" #include "XmlFile.h" #include "RageFile.h" #include "RageLog.h" #include "ActorUtil.h" #include "ModelManager.h" #include "Foreach.h" #include "LuaBinding.h" #include "PrefsManager.h" REGISTER_ACTOR_CLASS( Model ); static const float FRAMES_PER_SECOND = 30; static const RString DEFAULT_ANIMATION_NAME = "default"; Model::Model() { m_bTextureWrapping = true; SetUseZBuffer( true ); SetCullMode( CULL_BACK ); m_pGeometry = NULL; m_pCurAnimation = NULL; m_fDefaultAnimationRate = 1; m_fCurAnimationRate = 1; m_bLoop = true; m_bDrawCelShaded = false; m_pTempGeometry = NULL; } Model::~Model() { Clear(); } void Model::Clear() { if( m_pGeometry ) { MODELMAN->UnloadModel( m_pGeometry ); m_pGeometry = NULL; } m_vpBones.clear(); m_Materials.clear(); m_mapNameToAnimation.clear(); m_pCurAnimation = NULL; if( m_pTempGeometry ) DISPLAY->DeleteCompiledGeometry( m_pTempGeometry ); } void Model::Load( const RString &sFile ) { if( sFile == "" ) return; RString sExt = GetExtension(sFile); sExt.MakeLower(); if( sExt=="txt" ) LoadMilkshapeAscii( sFile ); } #define THROW RageException::Throw( "Parse error in \"%s\" at line %d: \"%s\".", sPath.c_str(), iLineNum, sLine.c_str() ) // TODO: Move MS3D loading into its own class. - Colby void Model::LoadMilkshapeAscii( const RString &sPath ) { LoadPieces( sPath, sPath, sPath ); } void Model::LoadPieces( const RString &sMeshesPath, const RString &sMaterialsPath, const RString &sBonesPath ) { Clear(); // TRICKY: Load materials before geometry so we can figure out whether the materials require normals. LoadMaterialsFromMilkshapeAscii( sMaterialsPath ); ASSERT( m_pGeometry == NULL ); m_pGeometry = MODELMAN->LoadMilkshapeAscii( sMeshesPath, this->MaterialsNeedNormals() ); // Validate material indices. for( unsigned i = 0; i < m_pGeometry->m_Meshes.size(); ++i ) { const msMesh *pMesh = &m_pGeometry->m_Meshes[i]; if( pMesh->nMaterialIndex >= (int) m_Materials.size() ) RageException::Throw( "Model \"%s\" mesh \"%s\" references material index %i, but there are only %i materials.", sMeshesPath.c_str(), pMesh->sName.c_str(), pMesh->nMaterialIndex, (int)m_Materials.size() ); } if( LoadMilkshapeAsciiBones( DEFAULT_ANIMATION_NAME, sBonesPath ) ) PlayAnimation( DEFAULT_ANIMATION_NAME ); // Setup temp vertices (if necessary) if( m_pGeometry->HasAnyPerVertexBones() ) { m_vTempMeshes = m_pGeometry->m_Meshes; m_pTempGeometry = DISPLAY->CreateCompiledGeometry(); m_pTempGeometry->Set( m_vTempMeshes, this->MaterialsNeedNormals() ); } } void Model::LoadFromNode( const XNode* pNode ) { RString s1, s2, s3; ActorUtil::GetAttrPath( pNode, "Meshes", s1 ); ActorUtil::GetAttrPath( pNode, "Materials", s2 ); ActorUtil::GetAttrPath( pNode, "Bones", s3 ); if( !s1.empty() || !s2.empty() || !s3.empty() ) { ASSERT( !s1.empty() && !s2.empty() && !s3.empty() ); LoadPieces( s1, s2, s3 ); } Actor::LoadFromNode( pNode ); } void Model::LoadMaterialsFromMilkshapeAscii( const RString &_sPath ) { RString sPath = _sPath; FixSlashesInPlace(sPath); const RString sDir = Dirname( sPath ); RageFile f; if( !f.Open( sPath ) ) RageException::Throw( "Model::LoadMilkshapeAscii Could not open \"%s\": %s", sPath.c_str(), f.GetError().c_str() ); RString sLine; int iLineNum = 0; while( f.GetLine( sLine ) > 0 ) { iLineNum++; if( !strncmp (sLine, "//", 2) ) continue; int nFrame; if( sscanf(sLine, "Frames: %d", &nFrame) == 1 ) { // ignore // m_pModel->nTotalFrames = nFrame; } if( sscanf(sLine, "Frame: %d", &nFrame) == 1 ) { // ignore // m_pModel->nFrame = nFrame; } // materials int nNumMaterials = 0; if( sscanf(sLine, "Materials: %d", &nNumMaterials) == 1 ) { m_Materials.resize( nNumMaterials ); char szName[256]; for( int i = 0; i < nNumMaterials; i++ ) { msMaterial& Material = m_Materials[i]; // name if( f.GetLine( sLine ) <= 0 ) THROW; if( sscanf(sLine, "\"%255[^\"]\"", szName) != 1 ) THROW; Material.sName = szName; // ambient if( f.GetLine( sLine ) <= 0 ) THROW; RageVector4 Ambient; if( sscanf(sLine, "%f %f %f %f", &Ambient[0], &Ambient[1], &Ambient[2], &Ambient[3]) != 4 ) THROW; memcpy( &Material.Ambient, &Ambient, sizeof(Material.Ambient) ); // diffuse if( f.GetLine( sLine ) <= 0 ) THROW; RageVector4 Diffuse; if( sscanf(sLine, "%f %f %f %f", &Diffuse[0], &Diffuse[1], &Diffuse[2], &Diffuse[3]) != 4 ) THROW; memcpy( &Material.Diffuse, &Diffuse, sizeof(Material.Diffuse) ); // specular if( f.GetLine( sLine ) <= 0 ) THROW; RageVector4 Specular; if( sscanf(sLine, "%f %f %f %f", &Specular[0], &Specular[1], &Specular[2], &Specular[3]) != 4 ) THROW; memcpy( &Material.Specular, &Specular, sizeof(Material.Specular) ); // emissive if( f.GetLine( sLine ) <= 0 ) THROW; RageVector4 Emissive; if( sscanf (sLine, "%f %f %f %f", &Emissive[0], &Emissive[1], &Emissive[2], &Emissive[3]) != 4 ) THROW; memcpy( &Material.Emissive, &Emissive, sizeof(Material.Emissive) ); // shininess if( f.GetLine( sLine ) <= 0 ) THROW; float fShininess; if( !StringConversion::FromString(sLine, fShininess) ) THROW; Material.fShininess = fShininess; // transparency if( f.GetLine( sLine ) <= 0 ) THROW; float fTransparency; if( !StringConversion::FromString(sLine, fTransparency) ) THROW; Material.fTransparency = fTransparency; // diffuse texture if( f.GetLine( sLine ) <= 0 ) THROW; strcpy( szName, "" ); sscanf( sLine, "\"%255[^\"]\"", szName ); RString sDiffuseTexture = szName; if( sDiffuseTexture == "" ) { Material.diffuse.LoadBlank(); } else { RString sTexturePath = sDir + sDiffuseTexture; FixSlashesInPlace( sTexturePath ); CollapsePath( sTexturePath ); if( !IsAFile(sTexturePath) ) RageException::Throw( "\"%s\" references a texture \"%s\" that does not exist.", sPath.c_str(), sTexturePath.c_str() ); Material.diffuse.Load( sTexturePath ); } // alpha texture if( f.GetLine( sLine ) <= 0 ) THROW; strcpy( szName, "" ); sscanf( sLine, "\"%255[^\"]\"", szName ); RString sAlphaTexture = szName; if( sAlphaTexture == "" ) { Material.alpha.LoadBlank(); } else { RString sTexturePath = sDir + sAlphaTexture; FixSlashesInPlace( sTexturePath ); CollapsePath( sTexturePath ); if( !IsAFile(sTexturePath) ) RageException::Throw( "\"%s\" references a texture \"%s\" that does not exist.", sPath.c_str(), sTexturePath.c_str() ); Material.alpha.Load( sTexturePath ); } } } } } bool Model::LoadMilkshapeAsciiBones( const RString &sAniName, const RString &sPath ) { m_mapNameToAnimation[sAniName] = msAnimation(); msAnimation &Animation = m_mapNameToAnimation[sAniName]; if( Animation.LoadMilkshapeAsciiBones( sAniName, sPath ) ) { m_mapNameToAnimation.erase( sAniName ); return false; } return true; } bool Model::EarlyAbortDraw() const { return m_pGeometry == NULL || m_pGeometry->m_Meshes.empty(); } void Model::DrawCelShaded() { // First pass: shell. We only want the backfaces for this. DISPLAY->SetCelShaded(1); DISPLAY->SetCullMode(CULL_FRONT); this->SetZWrite(false); // XXX: Why on earth isn't the culling working? -Colby this->Draw(); // Second pass: cel shading DISPLAY->SetCelShaded(2); DISPLAY->SetCullMode(CULL_BACK); this->SetZWrite(true); this->Draw(); DISPLAY->SetCelShaded(0); } void Model::DrawPrimitives() { Actor::SetGlobalRenderStates(); // set Actor-specified render states // Don't if we're fully transparent if( m_pTempState->diffuse[0].a < 0.001f && m_pTempState->glow.a < 0.001f ) return; DISPLAY->Scale( 1, -1, 1 ); // flip Y so positive is up ////////////////////// // render the diffuse pass ////////////////////// if( m_pTempState->diffuse[0].a > 0 ) { DISPLAY->SetTextureMode( TextureUnit_1, TextureMode_Modulate ); for( unsigned i = 0; i < m_pGeometry->m_Meshes.size(); ++i ) { const msMesh *pMesh = &m_pGeometry->m_Meshes[i]; if( pMesh->nMaterialIndex != -1 ) // has a material { // apply material msMaterial& mat = m_Materials[ pMesh->nMaterialIndex ]; RageColor Emissive = mat.Emissive; RageColor Ambient = mat.Ambient; RageColor Diffuse = mat.Diffuse; Emissive *= m_pTempState->diffuse[0]; Ambient *= m_pTempState->diffuse[0]; Diffuse *= m_pTempState->diffuse[0]; DISPLAY->SetMaterial( Emissive, Ambient, Diffuse, mat.Specular, mat.fShininess ); RageVector2 vTexTranslate = mat.diffuse.GetTextureTranslate(); if( vTexTranslate.x != 0 || vTexTranslate.y != 0 ) { DISPLAY->TexturePushMatrix(); DISPLAY->TextureTranslate( vTexTranslate.x, vTexTranslate.y ); } /* There's some common code that could be folded out here, but * it seems clearer to keep it separate. */ bool bUseMultitexture = PREFSMAN->m_bAllowMultitexture && DISPLAY->GetNumTextureUnits() >= 2; if( bUseMultitexture ) { // render the diffuse texture with texture unit 1 DISPLAY->SetTexture( TextureUnit_1, mat.diffuse.GetCurrentTexture() ? mat.diffuse.GetCurrentTexture()->GetTexHandle() : 0 ); Actor::SetTextureRenderStates(); // set Actor-specified render states DISPLAY->SetSphereEnvironmentMapping( TextureUnit_1, mat.diffuse.m_bSphereMapped ); // render the additive texture with texture unit 2 if( mat.alpha.GetCurrentTexture() ) { DISPLAY->SetTexture( TextureUnit_2, mat.alpha.GetCurrentTexture() ? mat.alpha.GetCurrentTexture()->GetTexHandle() : 0 ); Actor::SetTextureRenderStates(); // set Actor-specified render states DISPLAY->SetSphereEnvironmentMapping( TextureUnit_2, mat.alpha.m_bSphereMapped ); DISPLAY->SetTextureMode( TextureUnit_2, TextureMode_Add ); DISPLAY->SetTextureFiltering( TextureUnit_2, true ); } else { DISPLAY->SetTexture( TextureUnit_2, 0 ); // set current texture back to 0 or else texture // transform applied above isn't used. Why?!? DISPLAY->SetTexture( TextureUnit_1, mat.diffuse.GetCurrentTexture() ? mat.diffuse.GetCurrentTexture()->GetTexHandle() : 0 ); } // go DrawMesh(i); // Turn off environment mapping on tex unit 0. DISPLAY->SetSphereEnvironmentMapping( TextureUnit_1, false ); } else { // render the diffuse texture DISPLAY->SetTexture( TextureUnit_1, mat.diffuse.GetCurrentTexture() ? mat.diffuse.GetCurrentTexture()->GetTexHandle() : 0 ); Actor::SetTextureRenderStates(); // set Actor-specified render states DISPLAY->SetSphereEnvironmentMapping( TextureUnit_1, mat.diffuse.m_bSphereMapped ); DrawMesh( i ); // render the additive texture if( mat.alpha.GetCurrentTexture() ) { DISPLAY->SetTexture( TextureUnit_1, mat.alpha.GetCurrentTexture() ? mat.alpha.GetCurrentTexture()->GetTexHandle() : 0 ); Actor::SetTextureRenderStates(); // set Actor-specified render states DISPLAY->SetSphereEnvironmentMapping( TextureUnit_1, mat.alpha.m_bSphereMapped ); // UGLY: This overrides the Actor's BlendMode. DISPLAY->SetBlendMode( BLEND_ADD ); DISPLAY->SetTextureFiltering( TextureUnit_1, true ); DrawMesh( i ); } } if( vTexTranslate.x != 0 || vTexTranslate.y != 0 ) DISPLAY->TexturePopMatrix(); } else { static const RageColor emissive( 0,0,0,0 ); static const RageColor ambient( 0.2f,0.2f,0.2f,1 ); static const RageColor diffuse( 0.7f,0.7f,0.7f,1 ); static const RageColor specular( 0.2f,0.2f,0.2f,1 ); static const float shininess = 1; DISPLAY->SetMaterial( emissive, ambient, diffuse, specular, shininess ); DISPLAY->ClearAllTextures(); DISPLAY->SetSphereEnvironmentMapping( TextureUnit_1, false ); DrawMesh( i ); } DISPLAY->SetSphereEnvironmentMapping( TextureUnit_1, false ); DISPLAY->SetBlendMode( BLEND_NORMAL ); } } // render the glow pass if( m_pTempState->glow.a > 0.0001f ) { DISPLAY->SetTextureMode( TextureUnit_1, TextureMode_Glow ); for( unsigned i = 0; i < m_pGeometry->m_Meshes.size(); ++i ) { const msMesh *pMesh = &m_pGeometry->m_Meshes[i]; // apply material RageColor emissive = RageColor(0,0,0,0); RageColor ambient = RageColor(0,0,0,0); RageColor diffuse = m_pTempState->glow; RageColor specular = RageColor(0,0,0,0); float shininess = 1; DISPLAY->SetMaterial( emissive, ambient, diffuse, specular, shininess ); DISPLAY->ClearAllTextures(); if( pMesh->nMaterialIndex != -1 ) { msMaterial& mat = m_Materials[ pMesh->nMaterialIndex ]; DISPLAY->SetTexture( TextureUnit_1, mat.diffuse.GetCurrentTexture() ? mat.diffuse.GetCurrentTexture()->GetTexHandle() : 0 ); Actor::SetTextureRenderStates(); // set Actor-specified render states } else { // hey why is this otherwise empty else block here? -aj } DrawMesh( i ); } } } void Model::DrawMesh( int i ) const { const msMesh *pMesh = &m_pGeometry->m_Meshes[i]; // apply mesh-specific bone (if any) if( pMesh->m_iBoneIndex != -1 ) { DISPLAY->PushMatrix(); const RageMatrix &mat = m_vpBones[pMesh->m_iBoneIndex].m_Final; DISPLAY->PreMultMatrix( mat ); } // Draw it const RageCompiledGeometry* TempGeometry = m_pTempGeometry ? m_pTempGeometry : m_pGeometry->m_pCompiledGeometry; DISPLAY->DrawCompiledGeometry( TempGeometry, i, m_pGeometry->m_Meshes ); if( pMesh->m_iBoneIndex != -1 ) DISPLAY->PopMatrix(); } void Model::SetDefaultAnimation( RString sAnimation, float fPlayRate ) { m_sDefaultAnimation = sAnimation; m_fDefaultAnimationRate = fPlayRate; } void Model::PlayAnimation( const RString &sAniName, float fPlayRate ) { if( m_mapNameToAnimation.find(sAniName) == m_mapNameToAnimation.end() ) return; const msAnimation *pNewAnimation = &m_mapNameToAnimation[sAniName]; m_fCurFrame = 0; m_fCurAnimationRate = fPlayRate; if( m_pCurAnimation == pNewAnimation ) return; m_pCurAnimation = pNewAnimation; // setup bones m_vpBones.resize( m_pCurAnimation->Bones.size() ); for( unsigned i = 0; i < m_pCurAnimation->Bones.size(); i++ ) { const msBone *pBone = &m_pCurAnimation->Bones[i]; const RageVector3 &vRot = pBone->Rotation; RageMatrixAngles( &m_vpBones[i].m_Relative, vRot ); m_vpBones[i].m_Relative.m[3][0] = pBone->Position[0]; m_vpBones[i].m_Relative.m[3][1] = pBone->Position[1]; m_vpBones[i].m_Relative.m[3][2] = pBone->Position[2]; int nParentBone = m_pCurAnimation->FindBoneByName( pBone->sParentName ); if( nParentBone != -1 ) { RageMatrixMultiply( &m_vpBones[i].m_Absolute, &m_vpBones[nParentBone].m_Absolute, &m_vpBones[i].m_Relative ); } else { m_vpBones[i].m_Absolute = m_vpBones[i].m_Relative; } m_vpBones[i].m_Final = m_vpBones[i].m_Absolute; } // subtract out the bone's resting position for( unsigned i = 0; i < m_pGeometry->m_Meshes.size(); ++i ) { msMesh *pMesh = &m_pGeometry->m_Meshes[i]; vector &Vertices = pMesh->Vertices; for( unsigned j = 0; j < Vertices.size(); j++ ) { // int iBoneIndex = (pMesh->m_iBoneIndex!=-1) ? pMesh->m_iBoneIndex : bone; RageVector3 &pos = Vertices[j].p; int8_t bone = Vertices[j].bone; if( bone != -1 ) { pos[0] -= m_vpBones[bone].m_Absolute.m[3][0]; pos[1] -= m_vpBones[bone].m_Absolute.m[3][1]; pos[2] -= m_vpBones[bone].m_Absolute.m[3][2]; RageVector3 vTmp; RageMatrix inverse; RageMatrixTranspose( &inverse, &m_vpBones[bone].m_Absolute ); // transpose = inverse for rotation matrices RageVec3TransformNormal( &vTmp, &pos, &inverse ); pos = vTmp; } } } // Set up m_vpBones, just in case we're drawn without being Update()d. SetBones( m_pCurAnimation, m_fCurFrame, m_vpBones ); UpdateTempGeometry(); } void Model::SetPosition( float fSeconds ) { m_fCurFrame = FRAMES_PER_SECOND * fSeconds; m_fCurFrame = clamp( m_fCurFrame, 0, (float) m_pCurAnimation->nTotalFrames ); } void Model::AdvanceFrame( float fDeltaTime ) { if( m_pGeometry == NULL || m_pGeometry->m_Meshes.empty() || !m_pCurAnimation ) { return; // bail early } // LOG->Trace( "m_fCurFrame = %f", m_fCurFrame ); m_fCurFrame += FRAMES_PER_SECOND * fDeltaTime * m_fCurAnimationRate; if( m_fCurFrame < 0 || m_fCurFrame >= m_pCurAnimation->nTotalFrames ) { if( m_sDefaultAnimation != "" ) { this->PlayAnimation( m_sDefaultAnimation, m_fDefaultAnimationRate ); /* XXX: add to m_fCurFrame the wrapover from the previous * m_fCurFrame-m_pCurAnimation->nTotalFrames, so it doesn't skip */ } else if( m_bLoop ) wrap( m_fCurFrame, (float) m_pCurAnimation->nTotalFrames ); else m_fCurFrame = clamp( m_fCurFrame, 0, (float) m_pCurAnimation->nTotalFrames ); } SetBones( m_pCurAnimation, m_fCurFrame, m_vpBones ); UpdateTempGeometry(); } void Model::SetBones( const msAnimation* pAnimation, float fFrame, vector &vpBones ) { for( size_t i = 0; i < pAnimation->Bones.size(); ++i ) { const msBone *pBone = &pAnimation->Bones[i]; if( pBone->PositionKeys.size() == 0 && pBone->RotationKeys.size() == 0 ) { vpBones[i].m_Final = vpBones[i].m_Absolute; continue; } // search for the adjacent position keys const msPositionKey *pLastPositionKey = NULL, *pThisPositionKey = NULL; for( size_t j = 0; j < pBone->PositionKeys.size(); ++j ) { const msPositionKey *pPositionKey = &pBone->PositionKeys[j]; if( pPositionKey->fTime >= fFrame ) { pThisPositionKey = pPositionKey; break; } pLastPositionKey = pPositionKey; } RageVector3 vPos; if( pLastPositionKey != NULL && pThisPositionKey != NULL ) { const float s = SCALE( fFrame, pLastPositionKey->fTime, pThisPositionKey->fTime, 0, 1 ); vPos = pLastPositionKey->Position + (pThisPositionKey->Position - pLastPositionKey->Position) * s; } else if( pLastPositionKey == NULL ) vPos = pThisPositionKey->Position; else if( pThisPositionKey == NULL ) vPos = pLastPositionKey->Position; // search for the adjacent rotation keys const msRotationKey *pLastRotationKey = NULL, *pThisRotationKey = NULL; for( size_t j = 0; j < pBone->RotationKeys.size(); ++j ) { const msRotationKey *pRotationKey = &pBone->RotationKeys[j]; if( pRotationKey->fTime >= fFrame ) { pThisRotationKey = pRotationKey; break; } pLastRotationKey = pRotationKey; } RageVector4 vRot; if( pLastRotationKey != NULL && pThisRotationKey != NULL ) { const float s = SCALE( fFrame, pLastRotationKey->fTime, pThisRotationKey->fTime, 0, 1 ); RageQuatSlerp( &vRot, pLastRotationKey->Rotation, pThisRotationKey->Rotation, s ); } else if( pLastRotationKey == NULL ) { vRot = pThisRotationKey->Rotation; } else if( pThisRotationKey == NULL ) { vRot = pLastRotationKey->Rotation; } RageMatrix m; RageMatrixIdentity( &m ); RageMatrixFromQuat( &m, vRot ); m.m[3][0] = vPos[0]; m.m[3][1] = vPos[1]; m.m[3][2] = vPos[2]; RageMatrix RelativeFinal; RageMatrixMultiply( &RelativeFinal, &vpBones[i].m_Relative, &m ); int iParentBone = pAnimation->FindBoneByName( pBone->sParentName ); if( iParentBone == -1 ) vpBones[i].m_Final = RelativeFinal; else RageMatrixMultiply( &vpBones[i].m_Final, &vpBones[iParentBone].m_Final, &RelativeFinal ); } } void Model::UpdateTempGeometry() { if( m_pGeometry == NULL || m_pTempGeometry == NULL ) return; for( unsigned i = 0; i < m_pGeometry->m_Meshes.size(); ++i ) { const msMesh &origMesh = m_pGeometry->m_Meshes[i]; msMesh &tempMesh = m_vTempMeshes[i]; const vector &origVertices = origMesh.Vertices; vector &tempVertices = tempMesh.Vertices; for( unsigned j = 0; j < origVertices.size(); j++ ) { RageVector3 &tempPos = tempVertices[j].p; RageVector3 &tempNormal = tempVertices[j].n; const RageVector3 &originalPos = origVertices[j].p; const RageVector3 &originalNormal = origVertices[j].n; int8_t bone = origVertices[j].bone; if( bone == -1 ) { tempNormal = originalNormal; tempPos = originalPos; } else { RageVec3TransformNormal( &tempNormal, &originalNormal, &m_vpBones[bone].m_Final ); RageVec3TransformCoord( &tempPos, &originalPos, &m_vpBones[bone].m_Final ); } } } // send the new vertices to the graphics card m_pTempGeometry->Change( m_vTempMeshes ); } void Model::Update( float fDelta ) { Actor::Update( fDelta ); AdvanceFrame( fDelta ); for( unsigned i = 0; i < m_Materials.size(); ++i ) { m_Materials[i].diffuse.Update( fDelta ); m_Materials[i].alpha.Update( fDelta ); } } int Model::GetNumStates() const { int iMaxStates = 0; FOREACH_CONST( msMaterial, m_Materials, m ) iMaxStates = max( iMaxStates, m->diffuse.GetNumStates() ); return iMaxStates; } void Model::SetState( int iNewState ) { FOREACH( msMaterial, m_Materials, m ) { m->diffuse.SetState( iNewState ); m->alpha.SetState( iNewState ); } } float Model::GetAnimationLengthSeconds() const { float fSeconds = 0; FOREACH_CONST( msMaterial, m_Materials, m ) fSeconds = max( fSeconds, m->diffuse.GetAnimationLengthSeconds() ); return fSeconds; } void Model::SetSecondsIntoAnimation( float fSeconds ) { FOREACH( msMaterial, m_Materials, m ) { m->diffuse.SetSecondsIntoAnimation( fSeconds ); m->alpha.SetSecondsIntoAnimation( fSeconds ); } } bool Model::MaterialsNeedNormals() const { FOREACH_CONST( msMaterial, m_Materials, m ) { if( m->NeedsNormals() ) return true; } return false; } // lua start #include "LuaBinding.h" /** @brief Allow Lua to have access to the Model. */ class LunaModel: public Luna { public: static int position( T* p, lua_State *L ) { p->SetPosition( FArg(1) ); return 0; } static int playanimation( T* p, lua_State *L ) { p->PlayAnimation(SArg(1),FArg(2)); return 0; } static int SetDefaultAnimation( T* p, lua_State *L ) { p->SetDefaultAnimation(SArg(1),FArg(2)); return 0; } static int GetDefaultAnimation( T* p, lua_State *L ) { lua_pushstring( L, p->GetDefaultAnimation() ); return 1; } static int loop( T* p, lua_State *L ) { p->SetLoop(BArg(1)); return 0; } static int rate( T* p, lua_State *L ) { p->SetRate(FArg(1)); return 0; } static int GetNumStates( T* p, lua_State *L ) { lua_pushnumber( L, p->GetNumStates() ); return 1; } //static int CelShading( T* p, lua_State *L ) { p->SetCelShading(BArg(1)); return 0; } LunaModel() { ADD_METHOD( position ); ADD_METHOD( playanimation ); ADD_METHOD( SetDefaultAnimation ); ADD_METHOD( GetDefaultAnimation ); ADD_METHOD( loop ); ADD_METHOD( rate ); // sm-ssc adds: ADD_METHOD( GetNumStates ); //ADD_METHOD( CelShading ); // LoadMilkshapeAsciiBones? } }; LUA_REGISTER_DERIVED_CLASS( Model, Actor ) // lua end /* * (c) 2003-2004 Chris Danford * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons to * whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */