Ticket #2324: ActorModel.2.patch

File ActorModel.2.patch, 35.9 KB (added by wraitii, 10 years ago)

cleaned up version

  • 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, CStr* animName)
    5656{
    5757    SModelAnimState state;
    5858
    59     state.anims = object->GetAnimations(m_State);
     59    ENSURE(!object->GetStateAnimations("idle").empty()); // there must always be an idle animation
    6060
    61     if (state.anims.empty())
    62         state.anims = object->GetAnimations("idle");
    63     ENSURE(!state.anims.empty()); // there must always be an idle animation
    64 
     61    CStr &name = m_State;
     62   
    6563    state.model = model;
    66     state.animIdx = rand(0, state.anims.size());
     64    state.object = object;
    6765    state.time = 0.f;
    6866    state.pastLoadPos = false;
    6967    state.pastActionPos = false;
    7068    state.pastSoundPos = false;
     69   
     70    this->pickAnim(&state,name, useName, animName);
    7171
    7272    m_AnimStates.push_back(state);
    73 
     73   
    7474    model->SetAnimation(state.anims[state.animIdx], !m_Looping);
    75 
     75   
    7676    // Recursively add all props
    7777    const std::vector<CModel::Prop>& props = model->GetProps();
    7878    for (std::vector<CModel::Prop>::const_iterator it = props.begin(); it != props.end(); ++it)
    7979    {
    8080        CModel* propModel = it->m_Model->ToCModel();
    8181        if (propModel)
    82             AddModel(propModel, it->m_ObjectEntry);
     82            AddModel(propModel, it->m_ObjectEntry, true, &state.animName);
    8383    }
    8484}
    8585
     86// note: this is a voluntary Cstr copy
     87void CUnitAnimation::pickAnim(SModelAnimState* state, CStr& stateName, bool useName, CStr* animName, bool changeName)
     88{
     89    ENSURE(!(animName == NULL && useName));
     90   
     91        // get the proper animations.
     92    if (useName)
     93        state->anims = state->object->GetAnimations(stateName, *animName);
     94    else
     95        state->anims = state->object->GetStateAnimations(stateName);
     96
     97    // if empty and we used a specific anim name, try something more general
     98    // else try the idle animation, there is at least one by construction.
     99    if (state->anims.size() == 0)
     100    {
     101        if (useName)
     102            state->anims = state->object->GetAnimations(stateName, *animName);
     103        useName = false;
     104        if (state->anims.size() == 0)
     105        {
     106            stateName = "idle";
     107            state->anims = state->object->GetStateAnimations("idle");
     108        }
     109    }
     110
     111    // choose an animation. This is done randomly, but we respect the frequencies defined.
     112    // in case frequencies are all 0, we pick the first.
     113    size_t total = 0;
     114    if (useName)
     115        total = state->object->GetStateAnimFrequency(stateName, *animName);
     116    else
     117        total = state->object->GetStateFrequency(stateName);
     118   
     119    size_t sum = 0;
     120    size_t randv = 0;
     121    if (total != 0)
     122        randv = rand(1, total+1);
     123   
     124    for (size_t i = 0; i < state->anims.size(); ++i)
     125    {
     126        sum += state->anims[i]->m_Frequency;
     127        if (sum >= randv)
     128        {
     129            if (i != state->animIdx || state->anims[i]->m_AnimationName != state->animName)
     130            {
     131                state->animIdx = i;
     132                state->animName = state->anims[i]->m_AnimationName;
     133                state->model->SetAnimation(state->anims[state->animIdx], !m_Looping);
     134            }
     135            if (changeName)
     136                *animName = state->animName;
     137            break;
     138        }
     139    }
     140}
     141
    86142void CUnitAnimation::ReloadUnit(CModel* model, const CObjectEntry* object)
    87143{
    88144    m_Model = model;
     
    148204void CUnitAnimation::Update(float time)
    149205{
    150206    // Advance all of the prop models independently
     207    SModelAnimState* needLooping[m_AnimStates.size()];
     208    size_t looped = 0;
     209   
    151210    for (std::vector<SModelAnimState>::iterator it = m_AnimStates.begin(); it != m_AnimStates.end(); ++it)
    152211    {
    153212        CSkeletonAnimDef* animDef = it->anims[it->animIdx]->m_AnimDef;
     
    216275        else if (m_Looping)
    217276        {
    218277            // If we've finished the current animation and want to loop...
    219 
     278            needLooping[looped++] = &(*it);
     279           
    220280            // Wrap the timer around
    221281            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 
     282           
    234283            it->pastActionPos = false;
    235284            it->pastLoadPos = false;
    236285            it->pastSoundPos = false;
    237 
    238             it->model->UpdateTo(it->time);
    239286        }
    240287        else
    241288        {
     
    250297            }
    251298        }
    252299    }
     300   
     301   
     302    if (looped == 0)
     303        return;
     304   
     305    // to keep our props in synch (well those that can be)
     306    // we'll update them separately.
     307    CStr animName = "";
     308    CStr &name = m_State;
     309
     310    // the first one is the initializer so out of the loop.
     311    SModelAnimState* oState = needLooping[0];
     312   
     313    this->pickAnim(oState, name, false, &animName, true);
     314
     315    oState->model->UpdateTo(oState->time);
     316   
     317    if (looped == 1)
     318        return;
     319   
     320    for (size_t i = 1; i < looped; ++i)
     321    {
     322        SModelAnimState* state = needLooping[i];
     323       
     324        this->pickAnim(state, name, true, &animName);
     325       
     326        state->model->UpdateTo(state->time);
     327    }
    253328}
  • 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.
  • source/graphics/ObjectBase.cpp

     
    4242{
    4343    m_UsedFiles.clear();
    4444    m_UsedFiles.insert(pathname);
    45 
     45   
    4646    CXeromyces XeroFile;
    4747    if (XeroFile.Load(g_VFS, pathname) != PSRETURN_OK)
    4848        return false;
    49 
     49   
    5050    // Define all the elements used in the XML file
    51     #define EL(x) int el_##x = XeroFile.GetElementID(#x)
    52     #define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
     51#define EL(x) int el_##x = XeroFile.GetElementID(#x)
     52#define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
    5353    EL(actor);
    5454    EL(castshadow);
    5555    EL(float);
     
    8282    AT(offsetz);
    8383    AT(minheight);
    8484    AT(maxheight);
    85     #undef AT
    86     #undef EL
    87 
     85#undef AT
     86#undef EL
     87   
    8888    XMBElement root = XeroFile.GetRoot();
    89 
     89   
    9090    if (root.GetNodeName() != el_actor)
    9191    {
    9292        LOGERROR(L"Invalid actor format (unrecognised root element '%hs')", XeroFile.GetElementString(root.GetNodeName()).c_str());
    9393        return false;
    9494    }
    95 
    96 
     95   
     96   
    9797    m_VariantGroups.clear();
    98 
     98   
     99    int version = root.GetAttributes().Item(0).Value.ToInt();
     100   
    99101    m_Pathname = pathname;
    100102    m_ShortName = pathname.Basename().string();
    101 
    102 
     103   
     104   
    103105    // Set up the vector<vector<T>> m_Variants to contain the right number
    104106    // of elements, to avoid wasteful copying/reallocation later.
    105107    {
     
    112114                variantGroupSizes.push_back(child.GetChildNodes().Count);
    113115            }
    114116        }
    115 
     117       
    116118        m_VariantGroups.resize(variantGroupSizes.size());
    117119        // Set each vector to match the number of variants
    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();
     
    138140                {
    139141                    if (attr.Name == at_name)
    140142                        currentVariant->m_VariantName = attr.Value.LowerCase();
    141 
     143                   
    142144                    else if (attr.Name == at_frequency)
    143145                        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();
     
    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                            {
     
    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())
    286349        m_Material = VfsPath("art/materials/default.xml");
    287 
     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)
    325388            continue;
    326 
     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)
    354417                    break;
    355418            }
    356 
     419           
    357420            // If no match, just choose the first
    358421            if (match == -1)
    359422                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]);
     
    375438                    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)
    410473            continue;
    411 
     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())
    426489            variation.model = var.m_ModelFilename;
    427 
     490       
    428491        if (var.m_Decal.m_SizeX && var.m_Decal.m_SizeZ)
    429492            variation.decal = var.m_Decal;
    430 
     493       
    431494        if (! var.m_Particles.empty())
    432495            variation.particles = var.m_Particles;
    433 
     496       
    434497        if (! var.m_Color.empty())
    435498            variation.color = var.m_Color;
    436 
     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.
     
    445508        for (std::vector<CObjectBase::Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it)
    446509            if (! it->m_ModelName.empty()) // if the name is empty then the overridden prop is just deleted
    447510                variation.props.insert(make_pair(it->m_PropPointName, *it));
    448 
     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)
     
    460523        for (std::vector<CObjectBase::Samp>::iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it)
    461524            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)
    507570            continue;
    508 
     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)
    534597                    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)
     
    542605                int totalFreq = 0;
    543606                for (size_t i = 0; i < grp->size(); ++i)
    544607                    totalFreq += (*grp)[i].m_Frequency;
    545 
     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]);
     
    589652                    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    {
     
    599662            std::vector<std::set<CStr> > propInitialSelections = initialSelections;
    600663            if (!remainingSelections.empty())
    601664                propInitialSelections.push_back(remainingSelections);
    602 
     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();
     
    630693        // Ignore repeated objects (likely to be props)
    631694        if (objectsProcessed.find(obj) != objectsProcessed.end())
    632695            continue;
    633 
     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        {
     
    641704            group.reserve(obj->m_VariantGroups[i].size());
    642705            for (size_t j = 0; j < obj->m_VariantGroups[i].size(); ++j)
    643706                group.push_back(obj->m_VariantGroups[i][j].m_VariantName);
    644 
     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
     
    657720            }
    658721            if (dupe)
    659722                continue;
    660 
     723           
    661724            // Add non-trivial groups (i.e. not just one entry) to the returned list
    662725            if (obj->m_VariantGroups[i].size() > 1)
    663726                groups.push_back(group);
    664 
     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            {
     
    678741            }
    679742        }
    680743    }
    681 
     744   
    682745    return groups;
    683746}
  • 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/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.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/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);
    109 
     110    void AddModel(CModel* model, const CObjectEntry* object, bool useName = false, CStr* animName = NULL);
     111    // called in AddModel and Update.
     112    // Will try to choose animations fitting the animName if needed.
     113    void pickAnim(SModelAnimState* state, CStr& stateName, bool useName = false, CStr* animName = NULL, bool changeName = false);
     114   
    110115    entity_id_t m_Entity;
    111116    CModel* m_Model;
    112117    const CObjectEntry* m_Object;
  • 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