Ticket #2324: ActorModel.2.patch
File ActorModel.2.patch, 35.9 KB (added by , 10 years ago) |
---|
-
source/graphics/UnitAnimation.cpp
52 52 m_Entity = ent; 53 53 } 54 54 55 void CUnitAnimation::AddModel(CModel* model, const CObjectEntry* object )55 void CUnitAnimation::AddModel(CModel* model, const CObjectEntry* object, bool useName, CStr* animName) 56 56 { 57 57 SModelAnimState state; 58 58 59 state.anims = object->GetAnimations(m_State);59 ENSURE(!object->GetStateAnimations("idle").empty()); // there must always be an idle animation 60 60 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 65 63 state.model = model; 66 state. animIdx = rand(0, state.anims.size());64 state.object = object; 67 65 state.time = 0.f; 68 66 state.pastLoadPos = false; 69 67 state.pastActionPos = false; 70 68 state.pastSoundPos = false; 69 70 this->pickAnim(&state,name, useName, animName); 71 71 72 72 m_AnimStates.push_back(state); 73 73 74 74 model->SetAnimation(state.anims[state.animIdx], !m_Looping); 75 75 76 76 // Recursively add all props 77 77 const std::vector<CModel::Prop>& props = model->GetProps(); 78 78 for (std::vector<CModel::Prop>::const_iterator it = props.begin(); it != props.end(); ++it) 79 79 { 80 80 CModel* propModel = it->m_Model->ToCModel(); 81 81 if (propModel) 82 AddModel(propModel, it->m_ObjectEntry );82 AddModel(propModel, it->m_ObjectEntry, true, &state.animName); 83 83 } 84 84 } 85 85 86 // note: this is a voluntary Cstr copy 87 void 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 86 142 void CUnitAnimation::ReloadUnit(CModel* model, const CObjectEntry* object) 87 143 { 88 144 m_Model = model; … … 148 204 void CUnitAnimation::Update(float time) 149 205 { 150 206 // Advance all of the prop models independently 207 SModelAnimState* needLooping[m_AnimStates.size()]; 208 size_t looped = 0; 209 151 210 for (std::vector<SModelAnimState>::iterator it = m_AnimStates.begin(); it != m_AnimStates.end(); ++it) 152 211 { 153 212 CSkeletonAnimDef* animDef = it->anims[it->animIdx]->m_AnimDef; … … 216 275 else if (m_Looping) 217 276 { 218 277 // If we've finished the current animation and want to loop... 219 278 needLooping[looped++] = &(*it); 279 220 280 // Wrap the timer around 221 281 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 234 283 it->pastActionPos = false; 235 284 it->pastLoadPos = false; 236 285 it->pastSoundPos = false; 237 238 it->model->UpdateTo(it->time);239 286 } 240 287 else 241 288 { … … 250 297 } 251 298 } 252 299 } 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 } 253 328 } -
source/graphics/SkeletonAnim.h
33 33 { 34 34 public: 35 35 // 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; 37 39 // the raw animation frame data; may be NULL if this is a static 'animation' 38 40 CSkeletonAnimDef* m_AnimDef; 39 41 // speed at which this animation runs, as a factor of the AnimDef default speed 40 42 // (treated as 0 if m_AnimDef == NULL) 41 43 float m_Speed; 44 // how often this animation will be played. 45 size_t m_Frequency; 42 46 // Times during the animation at which the interesting bits happen, 43 47 // as msec times in the range [0, AnimDef->GetDuration], 44 48 // or special value -1 if unspecified. -
source/graphics/ObjectBase.cpp
42 42 { 43 43 m_UsedFiles.clear(); 44 44 m_UsedFiles.insert(pathname); 45 45 46 46 CXeromyces XeroFile; 47 47 if (XeroFile.Load(g_VFS, pathname) != PSRETURN_OK) 48 48 return false; 49 49 50 50 // Define all the elements used in the XML file 51 52 51 #define EL(x) int el_##x = XeroFile.GetElementID(#x) 52 #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) 53 53 EL(actor); 54 54 EL(castshadow); 55 55 EL(float); … … 82 82 AT(offsetz); 83 83 AT(minheight); 84 84 AT(maxheight); 85 86 87 85 #undef AT 86 #undef EL 87 88 88 XMBElement root = XeroFile.GetRoot(); 89 89 90 90 if (root.GetNodeName() != el_actor) 91 91 { 92 92 LOGERROR(L"Invalid actor format (unrecognised root element '%hs')", XeroFile.GetElementString(root.GetNodeName()).c_str()); 93 93 return false; 94 94 } 95 96 95 96 97 97 m_VariantGroups.clear(); 98 98 99 int version = root.GetAttributes().Item(0).Value.ToInt(); 100 99 101 m_Pathname = pathname; 100 102 m_ShortName = pathname.Basename().string(); 101 102 103 104 103 105 // Set up the vector<vector<T>> m_Variants to contain the right number 104 106 // of elements, to avoid wasteful copying/reallocation later. 105 107 { … … 112 114 variantGroupSizes.push_back(child.GetChildNodes().Count); 113 115 } 114 116 } 115 117 116 118 m_VariantGroups.resize(variantGroupSizes.size()); 117 119 // Set each vector to match the number of variants 118 120 for (size_t i = 0; i < variantGroupSizes.size(); ++i) 119 121 m_VariantGroups[i].resize(variantGroupSizes[i]); 120 122 } 121 122 123 124 123 125 // (This XML-reading code is rather worryingly verbose...) 124 126 125 127 std::vector<std::vector<Variant> >::iterator currentGroup = m_VariantGroups.begin(); 126 128 127 129 XERO_ITER_EL(root, child) 128 130 { 129 131 int child_name = child.GetNodeName(); 130 132 131 133 if (child_name == el_group) 132 134 { 133 135 std::vector<Variant>::iterator currentVariant = currentGroup->begin(); … … 138 140 { 139 141 if (attr.Name == at_name) 140 142 currentVariant->m_VariantName = attr.Value.LowerCase(); 141 143 142 144 else if (attr.Name == at_frequency) 143 145 currentVariant->m_Frequency = attr.Value.ToInt(); 144 146 } 145 147 146 148 XERO_ITER_EL(variant, option) 147 149 { 148 150 int option_name = option.GetNodeName(); 149 151 150 152 if (option_name == el_mesh) 151 153 { 152 154 currentVariant->m_ModelFilename = VfsPath("art/meshes") / option.GetText().FromUTF8(); … … 184 186 XMBAttributeList attrs = option.GetAttributes(); 185 187 VfsPath file = VfsPath("art/particles") / attrs.GetNamedItem(at_file).FromUTF8(); 186 188 currentVariant->m_Particles = file; 187 189 188 190 // For particle hotloading, it's easiest to reload the entire actor, 189 191 // so remember the relevant particle file as a dependency for this actor 190 192 m_UsedFiles.insert(file); … … 195 197 } 196 198 else if (option_name == el_animations) 197 199 { 198 XERO_ITER_EL(option, anim_element)200 if (version == 2) 199 201 { 200 ENSURE(anim_element.GetNodeName() == el_animation); 201 202 Anim anim; 203 XERO_ITER_ATTR(anim_element, ae) 202 XERO_ITER_EL(option, states) 204 203 { 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) 206 209 { 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); 208 253 } 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) 210 267 { 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 } 212 296 } 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); 233 298 } 234 currentVariant->m_Anims.push_back(anim);235 299 } 236 237 300 } 238 301 else if (option_name == el_props) 239 302 { 240 303 XERO_ITER_EL(option, prop_element) 241 304 { 242 305 ENSURE(prop_element.GetNodeName() == el_prop); 243 306 244 307 Prop prop; 245 308 XERO_ITER_ATTR(prop_element, pe) 246 309 { … … 257 320 } 258 321 } 259 322 } 260 323 261 324 ++currentVariant; 262 325 } 263 326 264 327 if (currentGroup->size() == 0) 265 328 { 266 329 LOGERROR(L"Actor group has zero variants ('%ls')", pathname.string().c_str()); 267 330 } 268 331 269 332 ++currentGroup; 270 333 } 271 334 else if (child_name == el_castshadow) … … 281 344 m_Material = VfsPath("art/materials") / child.GetText().FromUTF8(); 282 345 } 283 346 } 284 347 285 348 if (m_Material.empty()) 286 349 m_Material = VfsPath("art/materials/default.xml"); 287 350 288 351 return true; 289 352 } 290 353 … … 302 365 { 303 366 // (TODO: see CObjectManager::FindObjectVariation for an opportunity to 304 367 // call this function a bit less frequently) 305 368 306 369 // Calculate a complete list of choices, one per group, based on the 307 370 // supposedly-complete selections (i.e. not making random choices at this 308 371 // stage). … … 310 373 // first 'selections', set use that one. 311 374 // Otherwise, try with the next (lower priority) selections set, and repeat. 312 375 // Otherwise, choose the first variant (arbitrarily). 313 376 314 377 std::vector<u8> choices; 315 378 316 379 std::multimap<CStr, CStrW> chosenProps; 317 380 318 381 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) 321 384 { 322 385 // Ignore groups with nothing inside. (A warning will have been 323 386 // emitted by the loading code.) 324 387 if (grp->size() == 0) 325 388 continue; 326 389 327 390 int match = -1; // -1 => none found yet 328 391 329 392 // If there's only a single variant, choose that one 330 393 if (grp->size() == 1) 331 394 { … … 335 398 { 336 399 // Determine the first variant that matches the provided strings, 337 400 // starting with the highest priority selections set: 338 401 339 402 for (std::vector<std::set<CStr> >::const_iterator selset = selections.begin(); selset < selections.end(); ++selset) 340 403 { 341 404 ENSURE(grp->size() < 256); // else they won't fit in 'choices' 342 405 343 406 for (size_t i = 0; i < grp->size(); ++i) 344 407 { 345 408 if (selset->count((*grp)[i].m_VariantName)) … … 348 411 break; 349 412 } 350 413 } 351 414 352 415 // Stop after finding the first match 353 416 if (match != -1) 354 417 break; 355 418 } 356 419 357 420 // If no match, just choose the first 358 421 if (match == -1) 359 422 match = 0; 360 423 } 361 424 362 425 choices.push_back(match); 363 426 364 427 // Remember which props were chosen, so we can call CalculateVariationKey on them 365 428 // at the end. 366 429 Variant& var ((*grp)[match]); … … 375 438 chosenProps.insert(make_pair(it->m_PropPointName, it->m_ModelName)); 376 439 } 377 440 } 378 441 379 442 // Load each prop, and add their CalculateVariationKey to our key: 380 443 for (std::multimap<CStr, CStrW>::iterator it = chosenProps.begin(); it != chosenProps.end(); ++it) 381 444 { … … 386 449 choices.insert(choices.end(), propChoices.begin(), propChoices.end()); 387 450 } 388 451 } 389 452 390 453 return choices; 391 454 } 392 455 393 456 const CObjectBase::Variation CObjectBase::BuildVariation(const std::vector<u8>& variationKey) 394 457 { 395 458 Variation variation; 396 459 397 460 // variationKey should correspond with m_Variants, giving the id of the 398 461 // chosen variant from each group. (Except variationKey has some bits stuck 399 462 // on the end for props, but we don't care about those in here.) 400 463 401 464 std::vector<std::vector<CObjectBase::Variant> >::iterator grp = m_VariantGroups.begin(); 402 465 std::vector<u8>::const_iterator match = variationKey.begin(); 403 466 for ( ; 404 grp != m_VariantGroups.end() && match != variationKey.end();405 ++grp, ++match)467 grp != m_VariantGroups.end() && match != variationKey.end(); 468 ++grp, ++match) 406 469 { 407 470 // Ignore groups with nothing inside. (A warning will have been 408 471 // emitted by the loading code.) 409 472 if (grp->size() == 0) 410 473 continue; 411 474 412 475 size_t id = *match; 413 476 if (id >= grp->size()) 414 477 { … … 416 479 debug_warn(L"BuildVariation: invalid variant id"); 417 480 continue; 418 481 } 419 482 420 483 // Get the matched variant 421 484 CObjectBase::Variant& var ((*grp)[id]); 422 485 423 486 // Apply its data: 424 487 425 488 if (! var.m_ModelFilename.empty()) 426 489 variation.model = var.m_ModelFilename; 427 490 428 491 if (var.m_Decal.m_SizeX && var.m_Decal.m_SizeZ) 429 492 variation.decal = var.m_Decal; 430 493 431 494 if (! var.m_Particles.empty()) 432 495 variation.particles = var.m_Particles; 433 496 434 497 if (! var.m_Color.empty()) 435 498 variation.color = var.m_Color; 436 499 437 500 // If one variant defines one prop attached to e.g. "root", and this 438 501 // variant defines two different props with the same attachpoint, the one 439 502 // original should be erased, and replaced by the two new ones. … … 445 508 for (std::vector<CObjectBase::Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) 446 509 if (! it->m_ModelName.empty()) // if the name is empty then the overridden prop is just deleted 447 510 variation.props.insert(make_pair(it->m_PropPointName, *it)); 448 511 449 512 // Same idea applies for animations. 450 513 // So, erase all existing animations which are overridden by this variant: 451 514 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); 453 516 // and then insert the new ones: 454 517 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)); 456 519 457 520 // Same for samplers, though perhaps not strictly necessary: 458 521 for (std::vector<CObjectBase::Samp>::iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it) … … 460 523 for (std::vector<CObjectBase::Samp>::iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it) 461 524 variation.samplers.insert(make_pair(it->m_SamplerName, *it)); 462 525 } 463 526 464 527 return variation; 465 528 } 466 529 … … 468 531 { 469 532 rng_t rng; 470 533 rng.seed(seed); 471 534 472 535 std::set<CStr> remainingSelections = CalculateRandomRemainingSelections(rng, std::vector<std::set<CStr> >(1, initialSelections)); 473 536 remainingSelections.insert(initialSelections.begin(), initialSelections.end()); 474 537 475 538 return remainingSelections; // now actually a complete set of selections 476 539 } 477 540 … … 486 549 { 487 550 std::set<CStr> remainingSelections; 488 551 std::multimap<CStr, CStrW> chosenProps; 489 552 490 553 // Calculate a complete list of selections, so there is at least one 491 554 // (and in most cases only one) per group. 492 555 // In each group, if one of the variants has a name matching a string in … … 496 559 // 497 560 // When choosing randomly, make use of each variant's frequency. If all 498 561 // variants have frequency 0, treat them as if they were 1. 499 562 500 563 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) 503 566 { 504 567 // Ignore groups with nothing inside. (A warning will have been 505 568 // emitted by the loading code.) 506 569 if (grp->size() == 0) 507 570 continue; 508 571 509 572 int match = -1; // -1 => none found yet 510 573 511 574 // If there's only a single variant, choose that one 512 575 if (grp->size() == 1) 513 576 { … … 518 581 // See if a variant (or several, but we only care about the first) 519 582 // is already matched by the selections we've made, keeping their 520 583 // priority order into account 521 584 522 585 for (size_t s = 0; s < initialSelections.size(); ++s) 523 586 { 524 587 for (size_t i = 0; i < grp->size(); ++i) … … 529 592 break; 530 593 } 531 594 } 532 595 533 596 if (match >= 0) 534 597 break; 535 598 } 536 599 537 600 // If there was one, we don't need to do anything now because there's 538 601 // already something to choose. Otherwise, choose randomly from the others. 539 602 if (match == -1) … … 542 605 int totalFreq = 0; 543 606 for (size_t i = 0; i < grp->size(); ++i) 544 607 totalFreq += (*grp)[i].m_Frequency; 545 608 546 609 // Someone might be silly and set all variants to have freq==0, in 547 610 // which case we just pretend they're all 1 548 611 bool allZero = (totalFreq == 0); 549 612 if (allZero) totalFreq = (int)grp->size(); 550 613 551 614 // Choose a random number in the interval [0..totalFreq) 552 615 int randNum = boost::uniform_int<>(0, totalFreq-1)(rng); 553 616 554 617 // and use that to choose one of the variants 555 618 for (size_t i = 0; i < grp->size(); ++i) 556 619 { … … 558 621 if (randNum < 0) 559 622 { 560 623 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 562 625 // we'll get some non-fatal inconsistencies that just break the randomness. But that 563 626 // shouldn't happen, much.) 564 627 // (As an example, suppose you have a group with variants "a" and "b", and another … … 574 637 // wouldn't have chosen any of the variants. 575 638 } 576 639 } 577 640 578 641 // Remember which props were chosen, so we can call CalculateRandomVariation on them 579 642 // at the end. 580 643 Variant& var ((*grp)[match]); … … 589 652 chosenProps.insert(make_pair(it->m_PropPointName, it->m_ModelName)); 590 653 } 591 654 } 592 655 593 656 // Load each prop, and add their required selections to ours: 594 657 for (std::multimap<CStr, CStrW>::iterator it = chosenProps.begin(); it != chosenProps.end(); ++it) 595 658 { … … 599 662 std::vector<std::set<CStr> > propInitialSelections = initialSelections; 600 663 if (!remainingSelections.empty()) 601 664 propInitialSelections.push_back(remainingSelections); 602 665 603 666 std::set<CStr> propRemainingSelections = prop->CalculateRandomRemainingSelections(rng, propInitialSelections); 604 667 remainingSelections.insert(propRemainingSelections.begin(), propRemainingSelections.end()); 605 668 606 669 // Add the prop's used files to our own (recursively) so we can hotload 607 670 // when any prop is changed 608 671 m_UsedFiles.insert(prop->m_UsedFiles.begin(), prop->m_UsedFiles.end()); 609 672 } 610 673 } 611 674 612 675 return remainingSelections; 613 676 } 614 677 615 678 std::vector<std::vector<CStr> > CObjectBase::GetVariantGroups() const 616 679 { 617 680 std::vector<std::vector<CStr> > groups; 618 681 619 682 // Queue of objects (main actor plus props (recursively)) to be processed 620 683 std::queue<const CObjectBase*> objectsQueue; 621 684 objectsQueue.push(this); 622 685 623 686 // Set of objects already processed, so we don't do them more than once 624 687 std::set<const CObjectBase*> objectsProcessed; 625 688 626 689 while (!objectsQueue.empty()) 627 690 { 628 691 const CObjectBase* obj = objectsQueue.front(); … … 630 693 // Ignore repeated objects (likely to be props) 631 694 if (objectsProcessed.find(obj) != objectsProcessed.end()) 632 695 continue; 633 696 634 697 objectsProcessed.insert(obj); 635 698 636 699 // Iterate through the list of groups 637 700 for (size_t i = 0; i < obj->m_VariantGroups.size(); ++i) 638 701 { … … 641 704 group.reserve(obj->m_VariantGroups[i].size()); 642 705 for (size_t j = 0; j < obj->m_VariantGroups[i].size(); ++j) 643 706 group.push_back(obj->m_VariantGroups[i][j].m_VariantName); 644 707 645 708 // If this group is identical to one elsewhere, don't bother listing 646 709 // it twice. 647 710 // Linear search is theoretically not very efficient, but hopefully … … 657 720 } 658 721 if (dupe) 659 722 continue; 660 723 661 724 // Add non-trivial groups (i.e. not just one entry) to the returned list 662 725 if (obj->m_VariantGroups[i].size() > 1) 663 726 groups.push_back(group); 664 727 665 728 // Add all props onto the queue to be considered 666 729 for (size_t j = 0; j < obj->m_VariantGroups[i].size(); ++j) 667 730 { … … 678 741 } 679 742 } 680 743 } 681 744 682 745 return groups; 683 746 } -
source/graphics/ObjectEntry.h
63 63 64 64 std::wstring m_ProjectileModelName; 65 65 66 // Returns a randomly-chosen animation matching the given name.66 // Returns a randomly-chosen animation from a given state 67 67 // If none is found, returns NULL. 68 CSkeletonAnim* GetRandomAnimation(const CStr& animationName) const;68 CSkeletonAnim* GetRandomAnimation(const CStr& stateName) const; 69 69 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; 72 75 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 73 82 // corresponding model 74 83 CModelAbstract* m_Model; 75 84 … … 80 89 private: 81 90 CSimulation2& m_Simulation; 82 91 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? 86 99 }; 87 100 88 101 -
source/graphics/Model.cpp
255 255 ///////////////////////////////////////////////////////////////////////////////////////////////////////////// 256 256 // BuildAnimation: load raw animation frame animation from given file, and build a 257 257 // animation specific to this model 258 CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name, float speed, float actionpos, float actionpos2, float soundpos)258 CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& stateName, const CStr& animName, float speed, int frequency, float actionpos, float actionpos2, float soundpos) 259 259 { 260 260 CSkeletonAnimDef* def = m_SkeletonAnimManager.GetAnimation(pathname); 261 261 if (!def) 262 262 return NULL; 263 263 264 264 CSkeletonAnim* anim = new CSkeletonAnim(); 265 anim->m_Name = name; 265 anim->m_StateName = stateName; 266 anim->m_AnimationName = animName; 266 267 anim->m_AnimDef = def; 267 268 anim->m_Speed = speed; 268 269 anim->m_Frequency = frequency; 270 269 271 if (actionpos == -1.f) 270 272 anim->m_ActionPos = -1.f; 271 273 else -
source/graphics/ObjectBase.h
40 40 { 41 41 // constructor 42 42 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 44 46 CStr m_AnimName; 45 47 // filename of the animation - manidle.psa, manrun.psa, etc 46 48 VfsPath m_FileName; 47 49 // animation speed, as specified in XML actor file 48 50 float m_Speed; 51 // how often the animation will be chosen (if several are possible). 52 size_t m_Frequency; 49 53 // fraction [0.0, 1.0] of the way through the animation that the interesting bit(s) 50 54 // happens, or -1.0 if unspecified 51 55 float m_ActionPos; -
source/graphics/Model.h
201 201 * Load raw animation frame animation from given file, and build an 202 202 * animation specific to this model. 203 203 * @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 "") 205 206 * @param speed animation speed as a factor of the default animation speed 207 * @param frequency how often the animation will be chosen 206 208 * @param actionpos offset of 'action' event, in range [0, 1] 207 209 * @param actionpos2 offset of 'action2' event, in range [0, 1] 208 210 * @param sound offset of 'sound' event, in range [0, 1] 209 211 * @return new animation, or NULL on error 210 212 */ 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); 212 214 213 215 /** 214 216 * Add a prop to the model on the given point. -
source/graphics/UnitAnimation.h
95 95 struct SModelAnimState 96 96 { 97 97 CModel* model; 98 const CObjectEntry* object; 98 99 std::vector<CSkeletonAnim*> anims; 100 CStr animName; 99 101 size_t animIdx; 100 102 float time; 101 103 bool pastLoadPos; … … 105 107 106 108 std::vector<SModelAnimState> m_AnimStates; 107 109 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 110 115 entity_id_t m_Entity; 111 116 CModel* m_Model; 112 117 const CObjectEntry* m_Object; -
source/graphics/ObjectEntry.cpp
48 48 49 49 CObjectEntry::~CObjectEntry() 50 50 { 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*>); 52 52 53 53 delete m_Model; 54 54 } … … 155 155 // load the animations 156 156 for (std::multimap<CStr, CObjectBase::Anim>::iterator it = variation.anims.begin(); it != variation.anims.end(); ++it) 157 157 { 158 CStr name = it->first.LowerCase();158 CStr stateName = it->first.LowerCase(); 159 159 160 160 // TODO: Use consistent names everywhere, then remove this translation section. 161 161 // (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"; 165 165 166 166 if (! it->second.m_FileName.empty()) 167 167 { 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); 169 169 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 } 171 178 } 172 179 } 173 180 174 181 // 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()) 176 183 { 177 184 CSkeletonAnim* anim = new CSkeletonAnim(); 178 anim->m_Name = "idle"; 185 anim->m_StateName = "idle"; 186 anim->m_AnimationName = ""; 179 187 anim->m_AnimDef = NULL; 180 188 anim->m_Speed = 0.f; 189 anim->m_Frequency = 1; 181 190 anim->m_ActionPos = 0.f; 182 191 anim->m_ActionPos2 = 0.f; 183 192 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; 185 195 186 196 // Ignore errors, since they're probably saying this is a non-animated model 187 197 model->SetAnimation(anim); … … 252 262 return true; 253 263 } 254 264 255 CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName) const265 CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& stateName) const 256 266 { 257 Skeleton AnimMap::const_iterator lower = m_Animations.lower_bound(animationName);258 Skeleton AnimMap::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); 259 269 size_t count = std::distance(lower, upper); 260 270 if (count == 0) 261 271 return NULL; 262 272 273 // TODO: make this use frequencies. 263 274 size_t id = rand(0, count); 264 275 std::advance(lower, id); 265 276 return lower->second; 266 277 } 267 278 268 std::vector<CSkeletonAnim*> CObjectEntry::GetAnimations(const CStr& animationName) const279 std::vector<CSkeletonAnim*> CObjectEntry::GetAnimations(const CStr& stateName, const CStr& animationName) const 269 280 { 270 281 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 } 271 292 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) 293 std::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) 275 300 anims.push_back(it->second); 276 301 return anims; 277 302 } 303 304 size_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 312 size_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