Ticket #2324: ActorModel.patch
File ActorModel.patch, 38.7 KB (added by , 10 years ago) |
---|
-
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.cpp
96 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 103 … … 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(); … … 137 139 XERO_ITER_ATTR(variant, attr) 138 140 { 139 141 if (attr.Name == at_name) 140 141 142 currentVariant->m_VariantName = attr.Value.LowerCase(); 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(); … … 161 163 XERO_ITER_ATTR(textures_element, se) 162 164 { 163 165 if (se.Name == at_file) 164 166 samp.m_SamplerFile = VfsPath("art/textures/skins") / se.Value.FromUTF8(); 165 167 else if (se.Name == at_name) 166 168 samp.m_SamplerName = se.Value; 167 169 } 168 170 currentVariant->m_Samplers.push_back(samp); 169 171 } … … 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 { 247 310 if (pe.Name == at_attachpoint) 248 311 prop.m_PropPointName = pe.Value; 249 312 else if (pe.Name == at_actor) 250 313 prop.m_ModelName = pe.Value.FromUTF8(); 251 314 else if (pe.Name == at_minheight) 252 315 prop.m_minHeight = pe.Value.ToFloat(); 253 316 else if (pe.Name == at_maxheight) 254 317 prop.m_maxHeight = pe.Value.ToFloat(); 255 318 } 256 319 currentVariant->m_Props.push_back(prop); 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 287 349 m_Material = VfsPath("art/materials/default.xml"); 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 326 388 continue; 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]); … … 368 431 { 369 432 // Erase all existing props which are overridden by this variant: 370 433 for (std::vector<Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) 371 434 chosenProps.erase(it->m_PropPointName); 372 435 // and then insert the new ones: 373 436 for (std::vector<Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) 374 375 437 if (! it->m_ModelName.empty()) 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 411 473 continue; 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 427 489 variation.model = var.m_ModelFilename; 490 428 491 if (var.m_Decal.m_SizeX && var.m_Decal.m_SizeZ) 429 430 492 variation.decal = var.m_Decal; 493 431 494 if (! var.m_Particles.empty()) 432 433 495 variation.particles = var.m_Particles; 496 434 497 if (! var.m_Color.empty()) 435 436 498 variation.color = var.m_Color; 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. 440 503 // 441 504 // So, erase all existing props which are overridden by this variant: 442 505 for (std::vector<CObjectBase::Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) 443 506 variation.props.erase(it->m_PropPointName); 444 507 // and then insert the new ones: 445 508 for (std::vector<CObjectBase::Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) 446 447 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 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) 459 522 variation.samplers.erase(it->m_SamplerName); 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 508 570 continue; 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) … … 541 604 // Sum the frequencies 542 605 int totalFreq = 0; 543 606 for (size_t i = 0; i < grp->size(); ++i) 544 545 607 totalFreq += (*grp)[i].m_Frequency; 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]); … … 582 645 { 583 646 // Erase all existing props which are overridden by this variant: 584 647 for (std::vector<Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) 585 648 chosenProps.erase(it->m_PropPointName); 586 649 // and then insert the new ones: 587 650 for (std::vector<Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) 588 589 651 if (! it->m_ModelName.empty()) 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 { … … 598 661 { 599 662 std::vector<std::set<CStr> > propInitialSelections = initialSelections; 600 663 if (!remainingSelections.empty()) 601 602 664 propInitialSelections.push_back(remainingSelections); 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(); 629 692 objectsQueue.pop(); 630 693 // Ignore repeated objects (likely to be props) 631 694 if (objectsProcessed.find(obj) != objectsProcessed.end()) 632 633 695 continue; 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 { … … 640 703 std::vector<CStr> group; 641 704 group.reserve(obj->m_VariantGroups[i].size()); 642 705 for (size_t j = 0; j < obj->m_VariantGroups[i].size(); ++j) 643 644 706 group.push_back(obj->m_VariantGroups[i][j].m_VariantName); 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 … … 656 719 } 657 720 } 658 721 if (dupe) 659 660 722 continue; 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 664 726 groups.push_back(group); 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 { … … 672 735 { 673 736 CObjectBase* prop = m_ObjectManager.FindObjectBase(props[k].m_ModelName.c_str()); 674 737 if (prop) 675 738 objectsQueue.push(prop); 676 739 } 677 740 } 678 741 } 679 742 } 680 743 } 681 744 682 745 return groups; 683 746 } -
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 );110 void AddModel(CModel* model, const CObjectEntry* object, bool useName = false, const CStr& animName = ""); 109 111 110 112 entity_id_t m_Entity; 111 113 CModel* m_Model; -
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 -
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/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, const CStr& animName) 56 56 { 57 57 SModelAnimState state; 58 58 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); 60 65 61 66 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 } 63 79 ENSURE(!state.anims.empty()); // there must always be an idle animation 64 80 65 81 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 67 107 state.time = 0.f; 68 108 state.pastLoadPos = false; 69 109 state.pastActionPos = false; 70 110 state.pastSoundPos = false; 71 111 72 112 m_AnimStates.push_back(state); 73 113 74 114 model->SetAnimation(state.anims[state.animIdx], !m_Looping); 75 115 76 116 // Recursively add all props 77 117 const std::vector<CModel::Prop>& props = model->GetProps(); 78 118 for (std::vector<CModel::Prop>::const_iterator it = props.begin(); it != props.end(); ++it) 79 119 { 80 120 CModel* propModel = it->m_Model->ToCModel(); 81 121 if (propModel) 82 AddModel(propModel, it->m_ObjectEntry );122 AddModel(propModel, it->m_ObjectEntry, true, state.animName); 83 123 } 84 124 } 85 125 … … 148 188 void CUnitAnimation::Update(float time) 149 189 { 150 190 // Advance all of the prop models independently 191 SModelAnimState* needLooping[m_AnimStates.size()]; 192 size_t looped = 0; 193 151 194 for (std::vector<SModelAnimState>::iterator it = m_AnimStates.begin(); it != m_AnimStates.end(); ++it) 152 195 { 153 196 CSkeletonAnimDef* animDef = it->anims[it->animIdx]->m_AnimDef; … … 216 259 else if (m_Looping) 217 260 { 218 261 // If we've finished the current animation and want to loop... 219 262 needLooping[looped++] = &(*it); 263 220 264 // Wrap the timer around 221 265 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 234 267 it->pastActionPos = false; 235 268 it->pastLoadPos = false; 236 269 it->pastSoundPos = false; 237 238 it->model->UpdateTo(it->time);239 270 } 240 271 else 241 272 { … … 250 281 } 251 282 } 252 283 } 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 } 253 381 } -
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/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.