Ticket #2324: ActorModel.patch

File ActorModel.patch, 38.7 KB (added by wraitii, 10 years ago)
  • source/graphics/Model.cpp

     
    255255/////////////////////////////////////////////////////////////////////////////////////////////////////////////
    256256// BuildAnimation: load raw animation frame animation from given file, and build a
    257257// animation specific to this model
    258 CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name, float speed, float actionpos, float actionpos2, float soundpos)
     258CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& stateName, const CStr& animName, float speed, int frequency, float actionpos, float actionpos2, float soundpos)
    259259{
    260260    CSkeletonAnimDef* def = m_SkeletonAnimManager.GetAnimation(pathname);
    261261    if (!def)
    262262        return NULL;
    263263
    264264    CSkeletonAnim* anim = new CSkeletonAnim();
    265     anim->m_Name = name;
     265    anim->m_StateName = stateName;
     266    anim->m_AnimationName = animName;
    266267    anim->m_AnimDef = def;
    267268    anim->m_Speed = speed;
    268 
     269    anim->m_Frequency = frequency;
     270   
    269271    if (actionpos == -1.f)
    270272        anim->m_ActionPos = -1.f;
    271273    else
  • source/graphics/ObjectBase.cpp

     
    9696
    9797    m_VariantGroups.clear();
    9898
     99    int version = root.GetAttributes().Item(0).Value.ToInt();
     100   
    99101    m_Pathname = pathname;
    100102    m_ShortName = pathname.Basename().string();
    101103
     
    118120        for (size_t i = 0; i < variantGroupSizes.size(); ++i)
    119121            m_VariantGroups[i].resize(variantGroupSizes[i]);
    120122    }
    121 
    122 
     123   
     124   
    123125    // (This XML-reading code is rather worryingly verbose...)
    124 
     126   
    125127    std::vector<std::vector<Variant> >::iterator currentGroup = m_VariantGroups.begin();
    126 
     128   
    127129    XERO_ITER_EL(root, child)
    128130    {
    129131        int child_name = child.GetNodeName();
    130 
     132       
    131133        if (child_name == el_group)
    132134        {
    133135            std::vector<Variant>::iterator currentVariant = currentGroup->begin();
     
    137139                XERO_ITER_ATTR(variant, attr)
    138140                {
    139141                    if (attr.Name == at_name)
    140                         currentVariant->m_VariantName = attr.Value.LowerCase();
    141 
     142                    currentVariant->m_VariantName = attr.Value.LowerCase();
     143                   
    142144                    else if (attr.Name == at_frequency)
    143                         currentVariant->m_Frequency = attr.Value.ToInt();
     145                    currentVariant->m_Frequency = attr.Value.ToInt();
    144146                }
    145 
     147               
    146148                XERO_ITER_EL(variant, option)
    147149                {
    148150                    int option_name = option.GetNodeName();
    149 
     151                   
    150152                    if (option_name == el_mesh)
    151153                    {
    152154                        currentVariant->m_ModelFilename = VfsPath("art/meshes") / option.GetText().FromUTF8();
     
    161163                            XERO_ITER_ATTR(textures_element, se)
    162164                            {
    163165                                if (se.Name == at_file)
    164                                     samp.m_SamplerFile = VfsPath("art/textures/skins") / se.Value.FromUTF8();
     166                                samp.m_SamplerFile = VfsPath("art/textures/skins") / se.Value.FromUTF8();
    165167                                else if (se.Name == at_name)
    166                                     samp.m_SamplerName = se.Value;
     168                                samp.m_SamplerName = se.Value;
    167169                            }
    168170                            currentVariant->m_Samplers.push_back(samp);
    169171                        }
     
    184186                        XMBAttributeList attrs = option.GetAttributes();
    185187                        VfsPath file = VfsPath("art/particles") / attrs.GetNamedItem(at_file).FromUTF8();
    186188                        currentVariant->m_Particles = file;
    187 
     189                       
    188190                        // For particle hotloading, it's easiest to reload the entire actor,
    189191                        // so remember the relevant particle file as a dependency for this actor
    190192                        m_UsedFiles.insert(file);
     
    195197                    }
    196198                    else if (option_name == el_animations)
    197199                    {
    198                         XERO_ITER_EL(option, anim_element)
     200                        if (version == 2)
    199201                        {
    200                             ENSURE(anim_element.GetNodeName() == el_animation);
    201 
    202                             Anim anim;
    203                             XERO_ITER_ATTR(anim_element, ae)
     202                            XERO_ITER_EL(option, states)
    204203                            {
    205                                 if (ae.Name == at_name)
     204                                // because I want niceness, I'm not going to use el_XX
     205                                // but get the (user chosen) name of the balise.
     206                                std::string stateName = XeroFile.GetElementString(states.GetNodeName());
     207                               
     208                                XERO_ITER_EL(states, anim_element)
    206209                                {
    207                                     anim.m_AnimName = ae.Value;
     210                                    Anim anim;
     211                                    ENSURE(anim_element.GetNodeName() == el_animation);
     212                                   
     213                                    anim.m_StateName = stateName;
     214                                    anim.m_Frequency = 1;
     215                                    anim.m_AnimName = "";
     216                                   
     217                                    XERO_ITER_ATTR(anim_element, ae)
     218                                    {
     219                                        if (ae.Name == at_name)
     220                                        {
     221                                            anim.m_AnimName = ae.Value;
     222                                        }
     223                                        else if (ae.Name == at_file)
     224                                        {
     225                                            anim.m_FileName = VfsPath("art/animation") / ae.Value.FromUTF8();
     226                                        }
     227                                        else if (ae.Name == at_speed)
     228                                        {
     229                                            anim.m_Speed = ae.Value.ToInt() / 100.f;
     230                                            if (anim.m_Speed <= 0.0) anim.m_Speed = 1.0f;
     231                                        }
     232                                        else if (ae.Name == at_frequency)
     233                                        {
     234                                            anim.m_Frequency = ae.Value.ToUInt();
     235                                        }
     236                                        else if (ae.Name == at_event)
     237                                        {
     238                                            float pos = ae.Value.ToFloat();
     239                                            anim.m_ActionPos = clamp(pos, 0.f, 1.f);
     240                                        }
     241                                        else if (ae.Name == at_load)
     242                                        {
     243                                            float pos = ae.Value.ToFloat();
     244                                            anim.m_ActionPos2 = clamp(pos, 0.f, 1.f);
     245                                        }
     246                                        else if (ae.Name == at_sound)
     247                                        {
     248                                            float pos = ae.Value.ToFloat();
     249                                            anim.m_SoundPos = clamp(pos, 0.f, 1.f);
     250                                        }
     251                                    }
     252                                    currentVariant->m_Anims.push_back(anim);
    208253                                }
    209                                 else if (ae.Name == at_file)
     254                            }
     255                        }
     256                        else
     257                        {
     258                            XERO_ITER_EL(option, anim_element)
     259                            {
     260                                Anim anim;
     261                                ENSURE(anim_element.GetNodeName() == el_animation);
     262                               
     263                                anim.m_Frequency = 1;
     264                                anim.m_AnimName = "";
     265
     266                                XERO_ITER_ATTR(anim_element, ae)
    210267                                {
    211                                     anim.m_FileName = VfsPath("art/animation") / ae.Value.FromUTF8();
     268                                    if (ae.Name == at_name)
     269                                    {
     270                                        anim.m_StateName = ae.Value;
     271                                    }
     272                                    else if (ae.Name == at_file)
     273                                    {
     274                                        anim.m_FileName = VfsPath("art/animation") / ae.Value.FromUTF8();
     275                                    }
     276                                    else if (ae.Name == at_speed)
     277                                    {
     278                                        anim.m_Speed = ae.Value.ToInt() / 100.f;
     279                                        if (anim.m_Speed <= 0.0) anim.m_Speed = 1.0f;
     280                                    }
     281                                    else if (ae.Name == at_event)
     282                                    {
     283                                        float pos = ae.Value.ToFloat();
     284                                        anim.m_ActionPos = clamp(pos, 0.f, 1.f);
     285                                    }
     286                                    else if (ae.Name == at_load)
     287                                    {
     288                                        float pos = ae.Value.ToFloat();
     289                                        anim.m_ActionPos2 = clamp(pos, 0.f, 1.f);
     290                                    }
     291                                    else if (ae.Name == at_sound)
     292                                    {
     293                                        float pos = ae.Value.ToFloat();
     294                                        anim.m_SoundPos = clamp(pos, 0.f, 1.f);
     295                                    }
    212296                                }
    213                                 else if (ae.Name == at_speed)
    214                                 {
    215                                     anim.m_Speed = ae.Value.ToInt() / 100.f;
    216                                     if (anim.m_Speed <= 0.0) anim.m_Speed = 1.0f;
    217                                 }
    218                                 else if (ae.Name == at_event)
    219                                 {
    220                                     float pos = ae.Value.ToFloat();
    221                                     anim.m_ActionPos = clamp(pos, 0.f, 1.f);
    222                                 }
    223                                 else if (ae.Name == at_load)
    224                                 {
    225                                     float pos = ae.Value.ToFloat();
    226                                     anim.m_ActionPos2 = clamp(pos, 0.f, 1.f);
    227                                 }
    228                                 else if (ae.Name == at_sound)
    229                                 {
    230                                     float pos = ae.Value.ToFloat();
    231                                     anim.m_SoundPos = clamp(pos, 0.f, 1.f);
    232                                 }
     297                                currentVariant->m_Anims.push_back(anim);
    233298                            }
    234                             currentVariant->m_Anims.push_back(anim);
    235299                        }
    236 
    237300                    }
    238301                    else if (option_name == el_props)
    239302                    {
    240303                        XERO_ITER_EL(option, prop_element)
    241304                        {
    242305                            ENSURE(prop_element.GetNodeName() == el_prop);
    243 
     306                           
    244307                            Prop prop;
    245308                            XERO_ITER_ATTR(prop_element, pe)
    246309                            {
    247310                                if (pe.Name == at_attachpoint)
    248                                     prop.m_PropPointName = pe.Value;
     311                                prop.m_PropPointName = pe.Value;
    249312                                else if (pe.Name == at_actor)
    250                                     prop.m_ModelName = pe.Value.FromUTF8();
     313                                prop.m_ModelName = pe.Value.FromUTF8();
    251314                                else if (pe.Name == at_minheight)
    252                                     prop.m_minHeight = pe.Value.ToFloat();
     315                                prop.m_minHeight = pe.Value.ToFloat();
    253316                                else if (pe.Name == at_maxheight)
    254                                     prop.m_maxHeight = pe.Value.ToFloat();
     317                                prop.m_maxHeight = pe.Value.ToFloat();
    255318                            }
    256319                            currentVariant->m_Props.push_back(prop);
    257320                        }
    258321                    }
    259322                }
    260 
     323               
    261324                ++currentVariant;
    262325            }
    263 
     326           
    264327            if (currentGroup->size() == 0)
    265328            {
    266329                LOGERROR(L"Actor group has zero variants ('%ls')", pathname.string().c_str());
    267330            }
    268 
     331           
    269332            ++currentGroup;
    270333        }
    271334        else if (child_name == el_castshadow)
     
    281344            m_Material = VfsPath("art/materials") / child.GetText().FromUTF8();
    282345        }
    283346    }
    284 
     347   
    285348    if (m_Material.empty())
    286         m_Material = VfsPath("art/materials/default.xml");
    287 
     349    m_Material = VfsPath("art/materials/default.xml");
     350   
    288351    return true;
    289352}
    290353
     
    302365{
    303366    // (TODO: see CObjectManager::FindObjectVariation for an opportunity to
    304367    // call this function a bit less frequently)
    305 
     368   
    306369    // Calculate a complete list of choices, one per group, based on the
    307370    // supposedly-complete selections (i.e. not making random choices at this
    308371    // stage).
     
    310373    // first 'selections', set use that one.
    311374    // Otherwise, try with the next (lower priority) selections set, and repeat.
    312375    // Otherwise, choose the first variant (arbitrarily).
    313 
     376   
    314377    std::vector<u8> choices;
    315 
     378   
    316379    std::multimap<CStr, CStrW> chosenProps;
    317 
     380   
    318381    for (std::vector<std::vector<CObjectBase::Variant> >::iterator grp = m_VariantGroups.begin();
    319         grp != m_VariantGroups.end();
    320         ++grp)
     382         grp != m_VariantGroups.end();
     383         ++grp)
    321384    {
    322385        // Ignore groups with nothing inside. (A warning will have been
    323386        // emitted by the loading code.)
    324387        if (grp->size() == 0)
    325             continue;
    326 
     388        continue;
     389       
    327390        int match = -1; // -1 => none found yet
    328 
     391       
    329392        // If there's only a single variant, choose that one
    330393        if (grp->size() == 1)
    331394        {
     
    335398        {
    336399            // Determine the first variant that matches the provided strings,
    337400            // starting with the highest priority selections set:
    338 
     401           
    339402            for (std::vector<std::set<CStr> >::const_iterator selset = selections.begin(); selset < selections.end(); ++selset)
    340403            {
    341404                ENSURE(grp->size() < 256); // else they won't fit in 'choices'
    342 
     405               
    343406                for (size_t i = 0; i < grp->size(); ++i)
    344407                {
    345408                    if (selset->count((*grp)[i].m_VariantName))
     
    348411                        break;
    349412                    }
    350413                }
    351 
     414               
    352415                // Stop after finding the first match
    353416                if (match != -1)
    354                     break;
     417                break;
    355418            }
    356 
     419           
    357420            // If no match, just choose the first
    358421            if (match == -1)
    359                 match = 0;
     422            match = 0;
    360423        }
    361 
     424       
    362425        choices.push_back(match);
    363 
     426       
    364427        // Remember which props were chosen, so we can call CalculateVariationKey on them
    365428        // at the end.
    366429        Variant& var ((*grp)[match]);
     
    368431        {
    369432            // Erase all existing props which are overridden by this variant:
    370433            for (std::vector<Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it)
    371                 chosenProps.erase(it->m_PropPointName);
     434            chosenProps.erase(it->m_PropPointName);
    372435            // and then insert the new ones:
    373436            for (std::vector<Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it)
    374                 if (! it->m_ModelName.empty())
    375                     chosenProps.insert(make_pair(it->m_PropPointName, it->m_ModelName));
     437            if (! it->m_ModelName.empty())
     438            chosenProps.insert(make_pair(it->m_PropPointName, it->m_ModelName));
    376439        }
    377440    }
    378 
     441   
    379442    // Load each prop, and add their CalculateVariationKey to our key:
    380443    for (std::multimap<CStr, CStrW>::iterator it = chosenProps.begin(); it != chosenProps.end(); ++it)
    381444    {
     
    386449            choices.insert(choices.end(), propChoices.begin(), propChoices.end());
    387450        }
    388451    }
    389 
     452   
    390453    return choices;
    391454}
    392455
    393456const CObjectBase::Variation CObjectBase::BuildVariation(const std::vector<u8>& variationKey)
    394457{
    395458    Variation variation;
    396 
     459   
    397460    // variationKey should correspond with m_Variants, giving the id of the
    398461    // chosen variant from each group. (Except variationKey has some bits stuck
    399462    // on the end for props, but we don't care about those in here.)
    400 
     463   
    401464    std::vector<std::vector<CObjectBase::Variant> >::iterator grp = m_VariantGroups.begin();
    402465    std::vector<u8>::const_iterator match = variationKey.begin();
    403466    for ( ;
    404         grp != m_VariantGroups.end() && match != variationKey.end();
    405         ++grp, ++match)
     467         grp != m_VariantGroups.end() && match != variationKey.end();
     468         ++grp, ++match)
    406469    {
    407470        // Ignore groups with nothing inside. (A warning will have been
    408471        // emitted by the loading code.)
    409472        if (grp->size() == 0)
    410             continue;
    411 
     473        continue;
     474       
    412475        size_t id = *match;
    413476        if (id >= grp->size())
    414477        {
     
    416479            debug_warn(L"BuildVariation: invalid variant id");
    417480            continue;
    418481        }
    419 
     482       
    420483        // Get the matched variant
    421484        CObjectBase::Variant& var ((*grp)[id]);
    422 
     485       
    423486        // Apply its data:
    424 
     487       
    425488        if (! var.m_ModelFilename.empty())
    426             variation.model = var.m_ModelFilename;
    427 
     489        variation.model = var.m_ModelFilename;
     490       
    428491        if (var.m_Decal.m_SizeX && var.m_Decal.m_SizeZ)
    429             variation.decal = var.m_Decal;
    430 
     492        variation.decal = var.m_Decal;
     493       
    431494        if (! var.m_Particles.empty())
    432             variation.particles = var.m_Particles;
    433 
     495        variation.particles = var.m_Particles;
     496       
    434497        if (! var.m_Color.empty())
    435             variation.color = var.m_Color;
    436 
     498        variation.color = var.m_Color;
     499       
    437500        // If one variant defines one prop attached to e.g. "root", and this
    438501        // variant defines two different props with the same attachpoint, the one
    439502        // original should be erased, and replaced by the two new ones.
    440503        //
    441504        // So, erase all existing props which are overridden by this variant:
    442505        for (std::vector<CObjectBase::Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it)
    443             variation.props.erase(it->m_PropPointName);
     506        variation.props.erase(it->m_PropPointName);
    444507        // and then insert the new ones:
    445508        for (std::vector<CObjectBase::Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it)
    446             if (! it->m_ModelName.empty()) // if the name is empty then the overridden prop is just deleted
    447                 variation.props.insert(make_pair(it->m_PropPointName, *it));
    448 
     509        if (! it->m_ModelName.empty()) // if the name is empty then the overridden prop is just deleted
     510        variation.props.insert(make_pair(it->m_PropPointName, *it));
     511       
    449512        // Same idea applies for animations.
    450513        // So, erase all existing animations which are overridden by this variant:
    451514        for (std::vector<CObjectBase::Anim>::iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it)
    452             variation.anims.erase(it->m_AnimName);
     515        variation.anims.erase(it->m_StateName);
    453516        // and then insert the new ones:
    454517        for (std::vector<CObjectBase::Anim>::iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it)
    455             variation.anims.insert(make_pair(it->m_AnimName, *it));
     518        variation.anims.insert(make_pair(it->m_StateName, *it));
    456519       
    457520        // Same for samplers, though perhaps not strictly necessary:
    458521        for (std::vector<CObjectBase::Samp>::iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it)
    459             variation.samplers.erase(it->m_SamplerName);
     522        variation.samplers.erase(it->m_SamplerName);
    460523        for (std::vector<CObjectBase::Samp>::iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it)
    461             variation.samplers.insert(make_pair(it->m_SamplerName, *it));
     524        variation.samplers.insert(make_pair(it->m_SamplerName, *it));
    462525    }
    463 
     526   
    464527    return variation;
    465528}
    466529
     
    468531{
    469532    rng_t rng;
    470533    rng.seed(seed);
    471 
     534   
    472535    std::set<CStr> remainingSelections = CalculateRandomRemainingSelections(rng, std::vector<std::set<CStr> >(1, initialSelections));
    473536    remainingSelections.insert(initialSelections.begin(), initialSelections.end());
    474 
     537   
    475538    return remainingSelections; // now actually a complete set of selections
    476539}
    477540
     
    486549{
    487550    std::set<CStr> remainingSelections;
    488551    std::multimap<CStr, CStrW> chosenProps;
    489 
     552   
    490553    // Calculate a complete list of selections, so there is at least one
    491554    // (and in most cases only one) per group.
    492555    // In each group, if one of the variants has a name matching a string in
     
    496559    //
    497560    // When choosing randomly, make use of each variant's frequency. If all
    498561    // variants have frequency 0, treat them as if they were 1.
    499 
     562   
    500563    for (std::vector<std::vector<Variant> >::iterator grp = m_VariantGroups.begin();
    501         grp != m_VariantGroups.end();
    502         ++grp)
     564         grp != m_VariantGroups.end();
     565         ++grp)
    503566    {
    504567        // Ignore groups with nothing inside. (A warning will have been
    505568        // emitted by the loading code.)
    506569        if (grp->size() == 0)
    507             continue;
    508 
     570        continue;
     571       
    509572        int match = -1; // -1 => none found yet
    510 
     573       
    511574        // If there's only a single variant, choose that one
    512575        if (grp->size() == 1)
    513576        {
     
    518581            // See if a variant (or several, but we only care about the first)
    519582            // is already matched by the selections we've made, keeping their
    520583            // priority order into account
    521 
     584           
    522585            for (size_t s = 0; s < initialSelections.size(); ++s)
    523586            {
    524587                for (size_t i = 0; i < grp->size(); ++i)
     
    529592                        break;
    530593                    }
    531594                }
    532 
     595               
    533596                if (match >= 0)
    534                     break;
     597                break;
    535598            }
    536 
     599           
    537600            // If there was one, we don't need to do anything now because there's
    538601            // already something to choose. Otherwise, choose randomly from the others.
    539602            if (match == -1)
     
    541604                // Sum the frequencies
    542605                int totalFreq = 0;
    543606                for (size_t i = 0; i < grp->size(); ++i)
    544                     totalFreq += (*grp)[i].m_Frequency;
    545 
     607                totalFreq += (*grp)[i].m_Frequency;
     608               
    546609                // Someone might be silly and set all variants to have freq==0, in
    547610                // which case we just pretend they're all 1
    548611                bool allZero = (totalFreq == 0);
    549612                if (allZero) totalFreq = (int)grp->size();
    550 
     613               
    551614                // Choose a random number in the interval [0..totalFreq)
    552615                int randNum = boost::uniform_int<>(0, totalFreq-1)(rng);
    553 
     616               
    554617                // and use that to choose one of the variants
    555618                for (size_t i = 0; i < grp->size(); ++i)
    556619                {
     
    558621                    if (randNum < 0)
    559622                    {
    560623                        remainingSelections.insert((*grp)[i].m_VariantName);
    561                         // (If this change to 'remainingSelections' interferes with earlier choices, then 
     624                        // (If this change to 'remainingSelections' interferes with earlier choices, then
    562625                        // we'll get some non-fatal inconsistencies that just break the randomness. But that
    563626                        // shouldn't happen, much.)
    564627                        // (As an example, suppose you have a group with variants "a" and "b", and another
     
    574637                // wouldn't have chosen any of the variants.
    575638            }
    576639        }
    577 
     640       
    578641        // Remember which props were chosen, so we can call CalculateRandomVariation on them
    579642        // at the end.
    580643        Variant& var ((*grp)[match]);
     
    582645        {
    583646            // Erase all existing props which are overridden by this variant:
    584647            for (std::vector<Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it)
    585                 chosenProps.erase(it->m_PropPointName);
     648            chosenProps.erase(it->m_PropPointName);
    586649            // and then insert the new ones:
    587650            for (std::vector<Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it)
    588                 if (! it->m_ModelName.empty())
    589                     chosenProps.insert(make_pair(it->m_PropPointName, it->m_ModelName));
     651            if (! it->m_ModelName.empty())
     652            chosenProps.insert(make_pair(it->m_PropPointName, it->m_ModelName));
    590653        }
    591654    }
    592 
     655   
    593656    // Load each prop, and add their required selections to ours:
    594657    for (std::multimap<CStr, CStrW>::iterator it = chosenProps.begin(); it != chosenProps.end(); ++it)
    595658    {
     
    598661        {
    599662            std::vector<std::set<CStr> > propInitialSelections = initialSelections;
    600663            if (!remainingSelections.empty())
    601                 propInitialSelections.push_back(remainingSelections);
    602 
     664            propInitialSelections.push_back(remainingSelections);
     665           
    603666            std::set<CStr> propRemainingSelections = prop->CalculateRandomRemainingSelections(rng, propInitialSelections);
    604667            remainingSelections.insert(propRemainingSelections.begin(), propRemainingSelections.end());
    605 
     668           
    606669            // Add the prop's used files to our own (recursively) so we can hotload
    607670            // when any prop is changed
    608671            m_UsedFiles.insert(prop->m_UsedFiles.begin(), prop->m_UsedFiles.end());
    609672        }
    610673    }
    611 
     674   
    612675    return remainingSelections;
    613676}
    614677
    615678std::vector<std::vector<CStr> > CObjectBase::GetVariantGroups() const
    616679{
    617680    std::vector<std::vector<CStr> > groups;
    618 
     681   
    619682    // Queue of objects (main actor plus props (recursively)) to be processed
    620683    std::queue<const CObjectBase*> objectsQueue;
    621684    objectsQueue.push(this);
    622 
     685   
    623686    // Set of objects already processed, so we don't do them more than once
    624687    std::set<const CObjectBase*> objectsProcessed;
    625 
     688   
    626689    while (!objectsQueue.empty())
    627690    {
    628691        const CObjectBase* obj = objectsQueue.front();
    629692        objectsQueue.pop();
    630693        // Ignore repeated objects (likely to be props)
    631694        if (objectsProcessed.find(obj) != objectsProcessed.end())
    632             continue;
    633 
     695        continue;
     696       
    634697        objectsProcessed.insert(obj);
    635 
     698       
    636699        // Iterate through the list of groups
    637700        for (size_t i = 0; i < obj->m_VariantGroups.size(); ++i)
    638701        {
     
    640703            std::vector<CStr> group;
    641704            group.reserve(obj->m_VariantGroups[i].size());
    642705            for (size_t j = 0; j < obj->m_VariantGroups[i].size(); ++j)
    643                 group.push_back(obj->m_VariantGroups[i][j].m_VariantName);
    644 
     706            group.push_back(obj->m_VariantGroups[i][j].m_VariantName);
     707           
    645708            // If this group is identical to one elsewhere, don't bother listing
    646709            // it twice.
    647710            // Linear search is theoretically not very efficient, but hopefully
     
    656719                }
    657720            }
    658721            if (dupe)
    659                 continue;
    660 
     722            continue;
     723           
    661724            // Add non-trivial groups (i.e. not just one entry) to the returned list
    662725            if (obj->m_VariantGroups[i].size() > 1)
    663                 groups.push_back(group);
    664 
     726            groups.push_back(group);
     727           
    665728            // Add all props onto the queue to be considered
    666729            for (size_t j = 0; j < obj->m_VariantGroups[i].size(); ++j)
    667730            {
     
    672735                    {
    673736                        CObjectBase* prop = m_ObjectManager.FindObjectBase(props[k].m_ModelName.c_str());
    674737                        if (prop)
    675                             objectsQueue.push(prop);
     738                        objectsQueue.push(prop);
    676739                    }
    677740                }
    678741            }
    679742        }
    680743    }
    681 
     744   
    682745    return groups;
    683746}
  • source/graphics/Model.h

     
    201201     * Load raw animation frame animation from given file, and build an
    202202     * animation specific to this model.
    203203     * @param pathname animation file to load
    204      * @param name animation name (e.g. "idle")
     204     * @param stateName state name (e.g. "idle")
     205     * @param animName animation name (default is "")
    205206     * @param speed animation speed as a factor of the default animation speed
     207     * @param frequency how often the animation will be chosen
    206208     * @param actionpos offset of 'action' event, in range [0, 1]
    207209     * @param actionpos2 offset of 'action2' event, in range [0, 1]
    208210     * @param sound offset of 'sound' event, in range [0, 1]
    209211     * @return new animation, or NULL on error
    210212     */
    211     CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const CStr& name, float speed, float actionpos, float actionpos2, float soundpos);
     213    CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const CStr& stateName, const CStr& animName, float speed, int frequency, float actionpos, float actionpos2, float soundpos);
    212214
    213215    /**
    214216     * Add a prop to the model on the given point.
  • source/graphics/UnitAnimation.h

     
    9595    struct SModelAnimState
    9696    {
    9797        CModel* model;
     98        const CObjectEntry* object;
    9899        std::vector<CSkeletonAnim*> anims;
     100        CStr animName;
    99101        size_t animIdx;
    100102        float time;
    101103        bool pastLoadPos;
     
    105107
    106108    std::vector<SModelAnimState> m_AnimStates;
    107109
    108     void AddModel(CModel* model, const CObjectEntry* object);
     110    void AddModel(CModel* model, const CObjectEntry* object, bool useName = false, const CStr& animName = "");
    109111
    110112    entity_id_t m_Entity;
    111113    CModel* m_Model;
  • source/graphics/ObjectEntry.cpp

     
    4848
    4949CObjectEntry::~CObjectEntry()
    5050{
    51     std::for_each(m_Animations.begin(), m_Animations.end(), delete_pair_2nd<CStr, CSkeletonAnim*>);
     51    std::for_each(m_StateAnimations.begin(), m_StateAnimations.end(), delete_pair_2nd<CStr, CSkeletonAnim*>);
    5252
    5353    delete m_Model;
    5454}
     
    155155    // load the animations
    156156    for (std::multimap<CStr, CObjectBase::Anim>::iterator it = variation.anims.begin(); it != variation.anims.end(); ++it)
    157157    {
    158         CStr name = it->first.LowerCase();
     158        CStr stateName = it->first.LowerCase();
    159159
    160160        // TODO: Use consistent names everywhere, then remove this translation section.
    161161        // (It's just mapping the names used in actors onto the names used by code.)
    162         if (name == "attack") name = "melee";
    163         else if (name == "chop") name = "gather";
    164         else if (name == "decay") name = "corpse";
     162        if (stateName == "attack") stateName = "melee";
     163        else if (stateName == "chop") stateName = "gather";
     164        else if (stateName == "decay") stateName = "corpse";
    165165
    166166        if (! it->second.m_FileName.empty())
    167167        {
    168             CSkeletonAnim* anim = model->BuildAnimation(it->second.m_FileName, name, it->second.m_Speed, it->second.m_ActionPos, it->second.m_ActionPos2, it->second.m_SoundPos);
     168            CSkeletonAnim* anim = model->BuildAnimation(it->second.m_FileName, stateName, it->second.m_AnimName, it->second.m_Speed, it->second.m_Frequency, it->second.m_ActionPos, it->second.m_ActionPos2, it->second.m_SoundPos);
    169169            if (anim)
    170                 m_Animations.insert(std::make_pair(name, anim));
     170            {
     171                m_StateAnimations.insert(std::make_pair(stateName, anim));
     172                SkeletonStateFrequencies::const_iterator idx =  m_StateFrequencies.find(stateName);
     173                if (idx == m_StateFrequencies.end())
     174                    m_StateFrequencies[stateName] = it->second.m_Frequency;
     175                else
     176                    m_StateFrequencies[stateName] += it->second.m_Frequency;
     177            }
    171178        }
    172179    }
    173180
    174181    // ensure there's always an idle animation
    175     if (m_Animations.find("idle") == m_Animations.end())
     182    if (m_StateAnimations.find("idle") == m_StateAnimations.end())
    176183    {
    177184        CSkeletonAnim* anim = new CSkeletonAnim();
    178         anim->m_Name = "idle";
     185        anim->m_StateName = "idle";
     186        anim->m_AnimationName = "";
    179187        anim->m_AnimDef = NULL;
    180188        anim->m_Speed = 0.f;
     189        anim->m_Frequency = 1;
    181190        anim->m_ActionPos = 0.f;
    182191        anim->m_ActionPos2 = 0.f;
    183192        anim->m_SoundPos = 0.f;
    184         m_Animations.insert(std::make_pair("idle", anim));
     193        m_StateAnimations.insert(std::make_pair("idle", anim));
     194        m_StateFrequencies["idle"] = 1;
    185195
    186196        // Ignore errors, since they're probably saying this is a non-animated model
    187197        model->SetAnimation(anim);
     
    252262    return true;
    253263}
    254264
    255 CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName) const
     265CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& stateName) const
    256266{
    257     SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound(animationName);
    258     SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName);
     267    SkeletonStateAnimMap::const_iterator lower = m_StateAnimations.lower_bound(stateName);
     268    SkeletonStateAnimMap::const_iterator upper = m_StateAnimations.upper_bound(stateName);
    259269    size_t count = std::distance(lower, upper);
    260270    if (count == 0)
    261271        return NULL;
    262272
     273    // TODO: make this use frequencies.
    263274    size_t id = rand(0, count);
    264275    std::advance(lower, id);
    265276    return lower->second;
    266277}
    267278
    268 std::vector<CSkeletonAnim*> CObjectEntry::GetAnimations(const CStr& animationName) const
     279std::vector<CSkeletonAnim*> CObjectEntry::GetAnimations(const CStr& stateName, const CStr& animationName) const
    269280{
    270281    std::vector<CSkeletonAnim*> anims;
     282   
     283    SkeletonStateAnimMap::const_iterator lower = m_StateAnimations.lower_bound(stateName);
     284    SkeletonStateAnimMap::const_iterator upper = m_StateAnimations.upper_bound(stateName);
     285    for (SkeletonStateAnimMap::const_iterator it = lower; it != upper; ++it)
     286    {
     287        if (it->second->m_AnimationName == animationName)
     288            anims.push_back(it->second);
     289    }
     290    return anims;
     291}
    271292
    272     SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound(animationName);
    273     SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName);
    274     for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it)
     293std::vector<CSkeletonAnim*> CObjectEntry::GetStateAnimations(const CStr& stateName) const
     294{
     295    std::vector<CSkeletonAnim*> anims;
     296
     297    SkeletonStateAnimMap::const_iterator lower = m_StateAnimations.lower_bound(stateName);
     298    SkeletonStateAnimMap::const_iterator upper = m_StateAnimations.upper_bound(stateName);
     299    for (SkeletonStateAnimMap::const_iterator it = lower; it != upper; ++it)
    275300        anims.push_back(it->second);
    276301    return anims;
    277302}
     303
     304size_t CObjectEntry::GetStateFrequency(const CStr& stateName) const
     305{
     306    SkeletonStateFrequencies::const_iterator idx =  m_StateFrequencies.find(stateName);
     307    if (idx != m_StateFrequencies.end())
     308        return idx->second;
     309    return 0;
     310}
     311
     312size_t CObjectEntry::GetStateAnimFrequency(const CStr& stateName, const CStr& animationName) const
     313{
     314    size_t total = 0;
     315   
     316    SkeletonStateAnimMap::const_iterator lower = m_StateAnimations.lower_bound(stateName);
     317    SkeletonStateAnimMap::const_iterator upper = m_StateAnimations.upper_bound(stateName);
     318    for (SkeletonStateAnimMap::const_iterator it = lower; it != upper; ++it)
     319        if (it->second->m_AnimationName == animationName)
     320            total += it->second->m_Frequency;
     321   
     322    return total;
     323}
     324
  • source/graphics/ObjectEntry.h

     
    6363
    6464    std::wstring m_ProjectileModelName;
    6565
    66     // Returns a randomly-chosen animation matching the given name.
     66    // Returns a randomly-chosen animation from a given state
    6767    // If none is found, returns NULL.
    68     CSkeletonAnim* GetRandomAnimation(const CStr& animationName) const;
     68    CSkeletonAnim* GetRandomAnimation(const CStr& stateName) const;
    6969
    70     // Returns all the animations matching the given name.
    71     std::vector<CSkeletonAnim*> GetAnimations(const CStr& animationName) const;
     70    // Returns all the animations for a given state and name
     71    std::vector<CSkeletonAnim*> GetAnimations(const CStr& stateName, const CStr& animationName) const;
     72   
     73    // Returns all the animations for a given state.
     74    std::vector<CSkeletonAnim*> GetStateAnimations(const CStr& stateName) const;
    7275
     76    // Returns total frequencies for a state
     77    size_t GetStateFrequency(const CStr& stateName) const;
     78   
     79    // Returns total frequencies for a state and anim name
     80    size_t GetStateAnimFrequency(const CStr& stateName, const CStr& animationName) const;
     81
    7382    // corresponding model
    7483    CModelAbstract* m_Model;
    7584
     
    8089private:
    8190    CSimulation2& m_Simulation;
    8291
    83     typedef std::multimap<CStr, CSkeletonAnim*> SkeletonAnimMap;
    84     SkeletonAnimMap m_Animations;
    85         // TODO: something more memory-efficient than storing loads of similar strings for each unit?
     92    // stores animations for each state. We'll iterate if we need to find a specific one.
     93    // TODO: maybe someday if we have lots of anims use something cleverer.
     94    typedef std::multimap<CStr, CSkeletonAnim*> SkeletonStateAnimMap;
     95    SkeletonStateAnimMap m_StateAnimations;
     96    typedef std::map<CStr, size_t> SkeletonStateFrequencies;
     97    SkeletonStateFrequencies m_StateFrequencies;
     98    // TODO: something more memory-efficient than storing loads of similar strings for each unit?
    8699};
    87100
    88101
  • source/graphics/UnitAnimation.cpp

     
    5252    m_Entity = ent;
    5353}
    5454
    55 void CUnitAnimation::AddModel(CModel* model, const CObjectEntry* object)
     55void CUnitAnimation::AddModel(CModel* model, const CObjectEntry* object, bool useName, const CStr& animName)
    5656{
    5757    SModelAnimState state;
    5858
    59     state.anims = object->GetAnimations(m_State);
     59    CStr &name = m_State;
     60   
     61    if (useName)
     62        state.anims = object->GetAnimations(name,animName);
     63    else
     64        state.anims = object->GetStateAnimations(name);
    6065
    6166    if (state.anims.empty())
    62         state.anims = object->GetAnimations("idle");
     67    {
     68        if (useName)
     69        {
     70            state.anims = object->GetStateAnimations(name);
     71            useName = false;
     72        }
     73        if (state.anims.empty())
     74        {
     75            state.anims = object->GetStateAnimations("idle");
     76            name = "idle";
     77        }
     78    }
    6379    ENSURE(!state.anims.empty()); // there must always be an idle animation
    64 
     80   
    6581    state.model = model;
    66     state.animIdx = rand(0, state.anims.size());
     82    state.object = object;
     83   
     84    // picking the animation
     85    size_t total = 0;
     86    if (useName)
     87        total = object->GetStateAnimFrequency(name, animName);
     88    else
     89        total = object->GetStateFrequency(name);
     90   
     91    size_t sum = 0;
     92    size_t randv = 0;
     93    if (total != 0)
     94        randv = rand(1, total+1);
     95   
     96    for (size_t i = 0; i < state.anims.size(); ++i)
     97    {
     98        sum += state.anims[i]->m_Frequency;
     99        if (sum >= randv)
     100        {
     101            state.animIdx = i;
     102            state.animName = state.anims[i]->m_AnimationName;
     103            break;
     104        }
     105    }
     106   
    67107    state.time = 0.f;
    68108    state.pastLoadPos = false;
    69109    state.pastActionPos = false;
    70110    state.pastSoundPos = false;
    71 
     111   
    72112    m_AnimStates.push_back(state);
    73 
     113   
    74114    model->SetAnimation(state.anims[state.animIdx], !m_Looping);
    75 
     115   
    76116    // Recursively add all props
    77117    const std::vector<CModel::Prop>& props = model->GetProps();
    78118    for (std::vector<CModel::Prop>::const_iterator it = props.begin(); it != props.end(); ++it)
    79119    {
    80120        CModel* propModel = it->m_Model->ToCModel();
    81121        if (propModel)
    82             AddModel(propModel, it->m_ObjectEntry);
     122            AddModel(propModel, it->m_ObjectEntry, true, state.animName);
    83123    }
    84124}
    85125
     
    148188void CUnitAnimation::Update(float time)
    149189{
    150190    // Advance all of the prop models independently
     191    SModelAnimState* needLooping[m_AnimStates.size()];
     192    size_t looped = 0;
     193   
    151194    for (std::vector<SModelAnimState>::iterator it = m_AnimStates.begin(); it != m_AnimStates.end(); ++it)
    152195    {
    153196        CSkeletonAnimDef* animDef = it->anims[it->animIdx]->m_AnimDef;
     
    216259        else if (m_Looping)
    217260        {
    218261            // If we've finished the current animation and want to loop...
    219 
     262            needLooping[looped++] = &(*it);
     263           
    220264            // Wrap the timer around
    221265            it->time = fmod(it->time + advance, duration);
    222 
    223             // If there's a choice of multiple animations, pick a new random one
    224             if (it->anims.size() > 1)
    225             {
    226                 size_t newAnimIdx = rand(0, it->anims.size());
    227                 if (newAnimIdx != it->animIdx)
    228                 {
    229                     it->animIdx = newAnimIdx;
    230                     it->model->SetAnimation(it->anims[it->animIdx], !m_Looping);
    231                 }
    232             }
    233 
     266           
    234267            it->pastActionPos = false;
    235268            it->pastLoadPos = false;
    236269            it->pastSoundPos = false;
    237 
    238             it->model->UpdateTo(it->time);
    239270        }
    240271        else
    241272        {
     
    250281            }
    251282        }
    252283    }
     284   
     285    if (looped == 0)
     286        return;
     287   
     288    // to keep our props in synch (well those that can be)
     289    // we'll update them separately.
     290    CStr animName = "";
     291    bool useName = false;
     292   
     293    CStr &name = m_State;
     294
     295    // the first one is the initializer so out of the loop.
     296    SModelAnimState* state = needLooping[0];
     297   
     298    state->anims = state->object->GetStateAnimations(m_State);
     299    if (state->anims.size() == 0)
     300    {
     301        name = "idle";
     302        state->anims = state->object->GetStateAnimations("idle");
     303    }
     304   
     305    if (state->anims.size() > 1)
     306    {
     307        // picking the animation
     308        size_t total = state->object->GetStateFrequency(name);
     309        size_t sum = 0;
     310        size_t randv = 0;
     311        if (total != 0)
     312            randv = rand(1, total+1);
     313       
     314        for (size_t i = 0; i < state->anims.size(); ++i)
     315        {
     316            sum += state->anims[i]->m_Frequency;
     317            if (sum >= randv)
     318            {
     319                if (i != state->animIdx || state->anims[i]->m_AnimationName != state->animName)
     320                {
     321                    state->animIdx = i;
     322                    state->animName = state->anims[i]->m_AnimationName;
     323                    state->model->SetAnimation(state->anims[state->animIdx], !m_Looping);
     324                }
     325                animName = state->animName;
     326                break;
     327            }
     328        }
     329    } else
     330        animName = state->animName;
     331
     332    state->model->UpdateTo(state->time);
     333   
     334    if (looped == 1)
     335        return;
     336   
     337    for (size_t i = 1; i < looped; ++i)
     338    {
     339        SModelAnimState* state = needLooping[i];
     340        useName = true;
     341       
     342        state->anims = state->object->GetAnimations(name, animName);
     343        if (state->anims.size() == 0)
     344        {
     345            useName = false;
     346            if (state->anims.size() == 0)
     347            {
     348                name = "idle";
     349                state->anims = state->object->GetStateAnimations("idle");
     350            }
     351        }
     352        // picking the animation
     353        size_t total = 0;
     354        if (useName)
     355            total = state->object->GetStateAnimFrequency(name, animName);
     356        else
     357            total = state->object->GetStateFrequency(name);
     358
     359        size_t sum = 0;
     360        size_t randv = 0;
     361        if (total != 0)
     362            randv = rand(1, total+1);
     363       
     364        for (size_t i = 0; i < state->anims.size(); ++i)
     365        {
     366            sum += state->anims[i]->m_Frequency;
     367            if (sum >= randv)
     368            {
     369                if (i != state->animIdx || state->anims[i]->m_AnimationName != state->animName)
     370                {
     371                    state->animIdx = i;
     372                    state->animName = state->anims[i]->m_AnimationName;
     373                    state->model->SetAnimation(state->anims[state->animIdx], !m_Looping);
     374                }
     375                break;
     376            }
     377        }
     378       
     379        state->model->UpdateTo(state->time);
     380    }
    253381}
  • source/graphics/ObjectBase.h

     
    4040    {
    4141        // constructor
    4242        Anim() : m_Speed(1.f), m_ActionPos(-1.f), m_ActionPos2(-1.f), m_SoundPos(-1.f) {}
    43         // name of the animation - "Idle", "Run", etc
     43        // State the animation is used for — "Idle", "Run",…
     44        CStr m_StateName;
     45        // name of the animation, used for synching with props
    4446        CStr m_AnimName;
    4547        // filename of the animation - manidle.psa, manrun.psa, etc
    4648        VfsPath m_FileName;
    4749        // animation speed, as specified in XML actor file
    4850        float m_Speed;
     51        // how often the animation will be chosen (if several are possible).
     52        size_t m_Frequency;
    4953        // fraction [0.0, 1.0] of the way through the animation that the interesting bit(s)
    5054        // happens, or -1.0 if unspecified
    5155        float m_ActionPos;
  • source/graphics/SkeletonAnim.h

     
    3333{
    3434public:
    3535    // the name of the action which uses this animation (e.g. "idle")
    36     CStr m_Name;
     36    CStr m_StateName;
     37    // the name of the animation ("" is default, and you can then specify)
     38    CStr m_AnimationName;
    3739    // the raw animation frame data; may be NULL if this is a static 'animation'
    3840    CSkeletonAnimDef* m_AnimDef;
    3941    // speed at which this animation runs, as a factor of the AnimDef default speed
    4042    // (treated as 0 if m_AnimDef == NULL)
    4143    float m_Speed;
     44    // how often this animation will be played.
     45    size_t m_Frequency;
    4246    // Times during the animation at which the interesting bits happen,
    4347    // as msec times in the range [0, AnimDef->GetDuration],
    4448    // or special value -1 if unspecified.