Changeset View
Changeset View
Standalone View
Standalone View
source/graphics/ObjectBase.cpp
Show All 25 Lines | |||||
#include "ps/XML/Xeromyces.h" | #include "ps/XML/Xeromyces.h" | ||||
#include "ps/Filesystem.h" | #include "ps/Filesystem.h" | ||||
#include "ps/CLogger.h" | #include "ps/CLogger.h" | ||||
#include "lib/timer.h" | #include "lib/timer.h" | ||||
#include "maths/MathUtil.h" | #include "maths/MathUtil.h" | ||||
#include <boost/random/uniform_int_distribution.hpp> | #include <boost/random/uniform_int_distribution.hpp> | ||||
CObjectBase::CObjectBase(CObjectManager& objectManager) | CObjectBase::CObjectBase(CObjectManager& objectManager, CActorDef& actorDef, u8 lodLevel) | ||||
: m_ObjectManager(objectManager) | : m_ObjectManager(objectManager), m_ActorDef(actorDef) | ||||
{ | { | ||||
m_LODLevel = lodLevel; | |||||
m_Properties.m_CastShadows = false; | m_Properties.m_CastShadows = false; | ||||
m_Properties.m_FloatOnWater = false; | m_Properties.m_FloatOnWater = false; | ||||
m_ShortName = m_ActorDef.m_Pathname.string() + CStrW::FromInt(m_LODLevel); | |||||
} | |||||
std::unique_ptr<CObjectBase> CObjectBase::CopyWithLOD(u8 newLod) const | |||||
{ | |||||
std::unique_ptr<CObjectBase> ret = std::make_unique<CObjectBase>(m_ObjectManager, m_ActorDef, newLod); | |||||
// No need to actually change any LOD-related stuff here, since that's handled in key variations instead. | |||||
// TODO: this means the variantGroups could be a shared_ptr. | |||||
ret->m_VariantGroups = m_VariantGroups; | |||||
ret->m_Material = m_Material; | |||||
ret->m_Properties = m_Properties; | |||||
return ret; | |||||
} | |||||
void CObjectBase::Load(const CXeromyces& XeroFile, const XMBElement& root) | |||||
{ | |||||
// Define all the elements used in the XML file | |||||
#define EL(x) int el_##x = XeroFile.GetElementID(#x) | |||||
#define AT(x) int at_##x = XeroFile.GetAttributeID(#x) | |||||
EL(castshadow); | |||||
EL(float); | |||||
EL(group); | |||||
EL(material); | |||||
#undef AT | |||||
#undef EL | |||||
// Set up the vector<vector<T>> m_Variants to contain the right number | |||||
// of elements, to avoid wasteful copying/reallocation later. | |||||
{ | |||||
// Count the variants in each group | |||||
std::vector<int> variantGroupSizes; | |||||
XERO_ITER_EL(root, child) | |||||
{ | |||||
if (child.GetNodeName() == el_group) | |||||
variantGroupSizes.push_back(child.GetChildNodes().size()); | |||||
} | |||||
m_VariantGroups.resize(variantGroupSizes.size()); | |||||
// Set each vector to match the number of variants | |||||
for (size_t i = 0; i < variantGroupSizes.size(); ++i) | |||||
m_VariantGroups[i].resize(variantGroupSizes[i]); | |||||
} | |||||
// (This XML-reading code is rather worryingly verbose...) | |||||
std::vector<std::vector<Variant> >::iterator currentGroup = m_VariantGroups.begin(); | |||||
XERO_ITER_EL(root, child) | |||||
{ | |||||
int child_name = child.GetNodeName(); | |||||
if (child_name == el_group) | |||||
{ | |||||
std::vector<Variant>::iterator currentVariant = currentGroup->begin(); | |||||
XERO_ITER_EL(child, variant) | |||||
{ | |||||
LoadVariant(XeroFile, variant, *currentVariant); | |||||
++currentVariant; | |||||
} | |||||
if (currentGroup->size() == 0) | |||||
LOGERROR("Actor group has zero variants ('%s')", m_ShortName.ToUTF8()); | |||||
++currentGroup; | |||||
} | |||||
else if (child_name == el_castshadow) | |||||
m_Properties.m_CastShadows = true; | |||||
else if (child_name == el_float) | |||||
m_Properties.m_FloatOnWater = true; | |||||
else if (child_name == el_material) | |||||
m_Material = VfsPath("art/materials") / child.GetText().FromUTF8(); | |||||
} | |||||
if (m_Material.empty()) | |||||
m_Material = VfsPath("art/materials/default.xml"); | |||||
} | } | ||||
void CObjectBase::LoadVariant(const CXeromyces& XeroFile, const XMBElement& variant, Variant& currentVariant) | void CObjectBase::LoadVariant(const CXeromyces& XeroFile, const XMBElement& variant, Variant& currentVariant) | ||||
{ | { | ||||
#define EL(x) int el_##x = XeroFile.GetElementID(#x) | #define EL(x) int el_##x = XeroFile.GetElementID(#x) | ||||
#define AT(x) int at_##x = XeroFile.GetAttributeID(#x) | #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) | ||||
EL(animation); | EL(animation); | ||||
EL(animations); | EL(animations); | ||||
Show All 35 Lines | void CObjectBase::LoadVariant(const CXeromyces& XeroFile, const XMBElement& variant, Variant& currentVariant) | ||||
// Load variants first, so that they can be overriden if necessary. | // Load variants first, so that they can be overriden if necessary. | ||||
XERO_ITER_ATTR(variant, attr) | XERO_ITER_ATTR(variant, attr) | ||||
{ | { | ||||
if (attr.Name == at_file) | if (attr.Name == at_file) | ||||
{ | { | ||||
// Open up an external file to load. | // Open up an external file to load. | ||||
// Don't crash hard when failures happen, but log them and continue | // Don't crash hard when failures happen, but log them and continue | ||||
m_UsedFiles.insert(attr.Value); | m_ActorDef.m_UsedFiles.insert(attr.Value); | ||||
CXeromyces XeroVariant; | CXeromyces XeroVariant; | ||||
if (XeroVariant.Load(g_VFS, "art/variants/" + attr.Value) == PSRETURN_OK) | if (XeroVariant.Load(g_VFS, "art/variants/" + attr.Value) == PSRETURN_OK) | ||||
{ | { | ||||
XMBElement variantRoot = XeroVariant.GetRoot(); | XMBElement variantRoot = XeroVariant.GetRoot(); | ||||
LoadVariant(XeroVariant, variantRoot, currentVariant); | LoadVariant(XeroVariant, variantRoot, currentVariant); | ||||
} | } | ||||
else | else | ||||
LOGERROR("Could not open path %s", attr.Value); | LOGERROR("Could not open path %s", attr.Value); | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | XERO_ITER_EL(variant, option) | ||||
else if (option_name == el_particles) | else if (option_name == el_particles) | ||||
{ | { | ||||
XMBAttributeList attrs = option.GetAttributes(); | XMBAttributeList attrs = option.GetAttributes(); | ||||
VfsPath file = VfsPath("art/particles") / attrs.GetNamedItem(at_file).FromUTF8(); | VfsPath file = VfsPath("art/particles") / attrs.GetNamedItem(at_file).FromUTF8(); | ||||
currentVariant.m_Particles = file; | currentVariant.m_Particles = file; | ||||
// For particle hotloading, it's easiest to reload the entire actor, | // For particle hotloading, it's easiest to reload the entire actor, | ||||
// so remember the relevant particle file as a dependency for this actor | // so remember the relevant particle file as a dependency for this actor | ||||
m_UsedFiles.insert(file); | m_ActorDef.m_UsedFiles.insert(file); | ||||
} | } | ||||
else if (option_name == el_color) | else if (option_name == el_color) | ||||
{ | { | ||||
currentVariant.m_Color = option.GetText(); | currentVariant.m_Color = option.GetText(); | ||||
} | } | ||||
else if (option_name == el_animations) | else if (option_name == el_animations) | ||||
{ | { | ||||
XERO_ITER_EL(option, anim_element) | XERO_ITER_EL(option, anim_element) | ||||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | else if (option_name == el_props) | ||||
prop.m_selectable = pe.Value != "false"; | prop.m_selectable = pe.Value != "false"; | ||||
} | } | ||||
currentVariant.m_Props.push_back(prop); | currentVariant.m_Props.push_back(prop); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
bool CObjectBase::Load(const VfsPath& pathname) | std::vector<u8> CObjectBase::CalculateVariationKey(const std::vector<std::set<CStr> >& selections) const | ||||
{ | |||||
m_UsedFiles.clear(); | |||||
m_UsedFiles.insert(pathname); | |||||
CXeromyces XeroFile; | |||||
if (XeroFile.Load(g_VFS, pathname, "actor") != PSRETURN_OK) | |||||
return false; | |||||
// Define all the elements used in the XML file | |||||
#define EL(x) int el_##x = XeroFile.GetElementID(#x) | |||||
#define AT(x) int at_##x = XeroFile.GetAttributeID(#x) | |||||
EL(actor); | |||||
EL(castshadow); | |||||
EL(float); | |||||
EL(group); | |||||
EL(material); | |||||
#undef AT | |||||
#undef EL | |||||
XMBElement root = XeroFile.GetRoot(); | |||||
if (root.GetNodeName() != el_actor) | |||||
{ | |||||
LOGERROR("Invalid actor format (unrecognised root element '%s')", XeroFile.GetElementString(root.GetNodeName()).c_str()); | |||||
return false; | |||||
} | |||||
m_VariantGroups.clear(); | |||||
m_Pathname = pathname; | |||||
m_ShortName = pathname.Basename().string(); | |||||
// Set up the vector<vector<T>> m_Variants to contain the right number | |||||
// of elements, to avoid wasteful copying/reallocation later. | |||||
{ | |||||
// Count the variants in each group | |||||
std::vector<int> variantGroupSizes; | |||||
XERO_ITER_EL(root, child) | |||||
{ | |||||
if (child.GetNodeName() == el_group) | |||||
variantGroupSizes.push_back(child.GetChildNodes().size()); | |||||
} | |||||
m_VariantGroups.resize(variantGroupSizes.size()); | |||||
// Set each vector to match the number of variants | |||||
for (size_t i = 0; i < variantGroupSizes.size(); ++i) | |||||
m_VariantGroups[i].resize(variantGroupSizes[i]); | |||||
} | |||||
// (This XML-reading code is rather worryingly verbose...) | |||||
std::vector<std::vector<Variant> >::iterator currentGroup = m_VariantGroups.begin(); | |||||
XERO_ITER_EL(root, child) | |||||
{ | |||||
int child_name = child.GetNodeName(); | |||||
if (child_name == el_group) | |||||
{ | |||||
std::vector<Variant>::iterator currentVariant = currentGroup->begin(); | |||||
XERO_ITER_EL(child, variant) | |||||
{ | |||||
LoadVariant(XeroFile, variant, *currentVariant); | |||||
++currentVariant; | |||||
} | |||||
if (currentGroup->size() == 0) | |||||
LOGERROR("Actor group has zero variants ('%s')", pathname.string8()); | |||||
++currentGroup; | |||||
} | |||||
else if (child_name == el_castshadow) | |||||
m_Properties.m_CastShadows = true; | |||||
else if (child_name == el_float) | |||||
m_Properties.m_FloatOnWater = true; | |||||
else if (child_name == el_material) | |||||
m_Material = VfsPath("art/materials") / child.GetText().FromUTF8(); | |||||
} | |||||
if (m_Material.empty()) | |||||
m_Material = VfsPath("art/materials/default.xml"); | |||||
return true; | |||||
} | |||||
bool CObjectBase::Reload() | |||||
{ | |||||
return Load(m_Pathname); | |||||
} | |||||
bool CObjectBase::UsesFile(const VfsPath& pathname) | |||||
{ | |||||
return m_UsedFiles.find(pathname) != m_UsedFiles.end(); | |||||
} | |||||
std::vector<u8> CObjectBase::CalculateVariationKey(const std::vector<std::set<CStr> >& selections) | |||||
{ | { | ||||
// (TODO: see CObjectManager::FindObjectVariation for an opportunity to | // (TODO: see CObjectManager::FindObjectVariation for an opportunity to | ||||
// call this function a bit less frequently) | // call this function a bit less frequently) | ||||
// Calculate a complete list of choices, one per group, based on the | // Calculate a complete list of choices, one per group, based on the | ||||
// supposedly-complete selections (i.e. not making random choices at this | // supposedly-complete selections (i.e. not making random choices at this | ||||
// stage). | // stage). | ||||
// In each group, if one of the variants has a name matching a string in the | // In each group, if one of the variants has a name matching a string in the | ||||
// first 'selections', set use that one. | // first 'selections', set use that one. | ||||
// Otherwise, try with the next (lower priority) selections set, and repeat. | // Otherwise, try with the next (lower priority) selections set, and repeat. | ||||
// Otherwise, choose the first variant (arbitrarily). | // Otherwise, choose the first variant (arbitrarily). | ||||
std::vector<u8> choices; | std::vector<u8> choices; | ||||
std::multimap<CStr, CStrW> chosenProps; | std::multimap<CStr, CStrW> chosenProps; | ||||
for (std::vector<std::vector<CObjectBase::Variant> >::iterator grp = m_VariantGroups.begin(); | for (std::vector<std::vector<CObjectBase::Variant> >::const_iterator grp = m_VariantGroups.begin(); | ||||
grp != m_VariantGroups.end(); | grp != m_VariantGroups.end(); | ||||
++grp) | ++grp) | ||||
{ | { | ||||
// Ignore groups with nothing inside. (A warning will have been | // Ignore groups with nothing inside. (A warning will have been | ||||
// emitted by the loading code.) | // emitted by the loading code.) | ||||
if (grp->size() == 0) | if (grp->size() == 0) | ||||
continue; | continue; | ||||
Show All 31 Lines | else | ||||
if (match == -1) | if (match == -1) | ||||
match = 0; | match = 0; | ||||
} | } | ||||
choices.push_back(match); | choices.push_back(match); | ||||
// Remember which props were chosen, so we can call CalculateVariationKey on them | // Remember which props were chosen, so we can call CalculateVariationKey on them | ||||
// at the end. | // at the end. | ||||
// Erase all existing props which are overridden by this variant: | // Erase all existing props which are overridden by this variant: | ||||
Variant& var((*grp)[match]); | const Variant& var((*grp)[match]); | ||||
for (const Prop& prop : var.m_Props) | for (const Prop& prop : var.m_Props) | ||||
chosenProps.erase(prop.m_PropPointName); | chosenProps.erase(prop.m_PropPointName); | ||||
// and then insert the new ones: | // and then insert the new ones: | ||||
for (const Prop& prop : var.m_Props) | for (const Prop& prop : var.m_Props) | ||||
if (!prop.m_ModelName.empty()) | if (!prop.m_ModelName.empty()) | ||||
chosenProps.insert(make_pair(prop.m_PropPointName, prop.m_ModelName)); | chosenProps.insert(make_pair(prop.m_PropPointName, prop.m_ModelName)); | ||||
} | } | ||||
// Load each prop, and add their CalculateVariationKey to our key: | // Load each prop, and add their CalculateVariationKey to our key: | ||||
for (std::multimap<CStr, CStrW>::iterator it = chosenProps.begin(); it != chosenProps.end(); ++it) | for (std::multimap<CStr, CStrW>::iterator it = chosenProps.begin(); it != chosenProps.end(); ++it) | ||||
{ | { | ||||
CObjectBase* prop = m_ObjectManager.FindObjectBase(it->second); | CActorDef* prop = m_ObjectManager.FindActorDef(it->second); | ||||
if (prop) | if (prop) | ||||
{ | { | ||||
std::vector<u8> propChoices = prop->CalculateVariationKey(selections); | std::vector<u8> propChoices = prop->GetBase(m_LODLevel)->CalculateVariationKey(selections); | ||||
choices.insert(choices.end(), propChoices.begin(), propChoices.end()); | choices.insert(choices.end(), propChoices.begin(), propChoices.end()); | ||||
} | } | ||||
} | } | ||||
return choices; | return choices; | ||||
} | } | ||||
const CObjectBase::Variation CObjectBase::BuildVariation(const std::vector<u8>& variationKey) | const CObjectBase::Variation CObjectBase::BuildVariation(const std::vector<u8>& variationKey) const | ||||
{ | { | ||||
Variation variation; | Variation variation; | ||||
// variationKey should correspond with m_Variants, giving the id of the | // variationKey should correspond with m_Variants, giving the id of the | ||||
// chosen variant from each group. (Except variationKey has some bits stuck | // chosen variant from each group. (Except variationKey has some bits stuck | ||||
// on the end for props, but we don't care about those in here.) | // on the end for props, but we don't care about those in here.) | ||||
std::vector<std::vector<CObjectBase::Variant> >::iterator grp = m_VariantGroups.begin(); | std::vector<std::vector<CObjectBase::Variant> >::const_iterator grp = m_VariantGroups.begin(); | ||||
std::vector<u8>::const_iterator match = variationKey.begin(); | std::vector<u8>::const_iterator match = variationKey.begin(); | ||||
for ( ; | for ( ; | ||||
grp != m_VariantGroups.end() && match != variationKey.end(); | grp != m_VariantGroups.end() && match != variationKey.end(); | ||||
++grp, ++match) | ++grp, ++match) | ||||
{ | { | ||||
// Ignore groups with nothing inside. (A warning will have been | // Ignore groups with nothing inside. (A warning will have been | ||||
// emitted by the loading code.) | // emitted by the loading code.) | ||||
if (grp->size() == 0) | if (grp->size() == 0) | ||||
continue; | continue; | ||||
size_t id = *match; | size_t id = *match; | ||||
if (id >= grp->size()) | if (id >= grp->size()) | ||||
{ | { | ||||
// This should be impossible | // This should be impossible | ||||
debug_warn(L"BuildVariation: invalid variant id"); | debug_warn(L"BuildVariation: invalid variant id"); | ||||
continue; | continue; | ||||
} | } | ||||
// Get the matched variant | // Get the matched variant | ||||
CObjectBase::Variant& var ((*grp)[id]); | const CObjectBase::Variant& var ((*grp)[id]); | ||||
// Apply its data: | // Apply its data: | ||||
if (! var.m_ModelFilename.empty()) | if (! var.m_ModelFilename.empty()) | ||||
variation.model = var.m_ModelFilename; | variation.model = var.m_ModelFilename; | ||||
if (var.m_Decal.m_SizeX && var.m_Decal.m_SizeZ) | if (var.m_Decal.m_SizeX && var.m_Decal.m_SizeZ) | ||||
variation.decal = var.m_Decal; | variation.decal = var.m_Decal; | ||||
if (! var.m_Particles.empty()) | if (! var.m_Particles.empty()) | ||||
variation.particles = var.m_Particles; | variation.particles = var.m_Particles; | ||||
if (! var.m_Color.empty()) | if (! var.m_Color.empty()) | ||||
variation.color = var.m_Color; | variation.color = var.m_Color; | ||||
// If one variant defines one prop attached to e.g. "root", and this | // If one variant defines one prop attached to e.g. "root", and this | ||||
// variant defines two different props with the same attachpoint, the one | // variant defines two different props with the same attachpoint, the one | ||||
// original should be erased, and replaced by the two new ones. | // original should be erased, and replaced by the two new ones. | ||||
// | // | ||||
// So, erase all existing props which are overridden by this variant: | // So, erase all existing props which are overridden by this variant: | ||||
for (std::vector<CObjectBase::Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) | for (std::vector<CObjectBase::Prop>::const_iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) | ||||
variation.props.erase(it->m_PropPointName); | variation.props.erase(it->m_PropPointName); | ||||
// and then insert the new ones: | // and then insert the new ones: | ||||
for (std::vector<CObjectBase::Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) | for (std::vector<CObjectBase::Prop>::const_iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it) | ||||
if (! it->m_ModelName.empty()) // if the name is empty then the overridden prop is just deleted | if (! it->m_ModelName.empty()) // if the name is empty then the overridden prop is just deleted | ||||
variation.props.insert(make_pair(it->m_PropPointName, *it)); | variation.props.insert(make_pair(it->m_PropPointName, *it)); | ||||
// Same idea applies for animations. | // Same idea applies for animations. | ||||
// So, erase all existing animations which are overridden by this variant: | // So, erase all existing animations which are overridden by this variant: | ||||
for (std::vector<CObjectBase::Anim>::iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it) | for (std::vector<CObjectBase::Anim>::const_iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it) | ||||
variation.anims.erase(it->m_AnimName); | variation.anims.erase(it->m_AnimName); | ||||
// and then insert the new ones: | // and then insert the new ones: | ||||
for (std::vector<CObjectBase::Anim>::iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it) | for (std::vector<CObjectBase::Anim>::const_iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it) | ||||
variation.anims.insert(make_pair(it->m_AnimName, *it)); | variation.anims.insert(make_pair(it->m_AnimName, *it)); | ||||
// Same for samplers, though perhaps not strictly necessary: | // Same for samplers, though perhaps not strictly necessary: | ||||
for (std::vector<CObjectBase::Samp>::iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it) | for (std::vector<CObjectBase::Samp>::const_iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it) | ||||
variation.samplers.erase(it->m_SamplerName.string()); | variation.samplers.erase(it->m_SamplerName.string()); | ||||
for (std::vector<CObjectBase::Samp>::iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it) | for (std::vector<CObjectBase::Samp>::const_iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it) | ||||
variation.samplers.insert(make_pair(it->m_SamplerName.string(), *it)); | variation.samplers.insert(make_pair(it->m_SamplerName.string(), *it)); | ||||
} | } | ||||
return variation; | return variation; | ||||
} | } | ||||
std::set<CStr> CObjectBase::CalculateRandomVariation(uint32_t seed, const std::set<CStr>& initialSelections) | std::set<CStr> CObjectBase::CalculateRandomRemainingSelections(rng_t& rng, const std::vector<std::set<CStr>>& initialSelections) const | ||||
{ | |||||
rng_t rng; | |||||
rng.seed(seed); | |||||
std::set<CStr> remainingSelections = CalculateRandomRemainingSelections(rng, std::vector<std::set<CStr> >(1, initialSelections)); | |||||
remainingSelections.insert(initialSelections.begin(), initialSelections.end()); | |||||
return remainingSelections; // now actually a complete set of selections | |||||
} | |||||
std::set<CStr> CObjectBase::CalculateRandomRemainingSelections(uint32_t seed, const std::vector<std::set<CStr> >& initialSelections) | |||||
{ | |||||
rng_t rng; | |||||
rng.seed(seed); | |||||
return CalculateRandomRemainingSelections(rng, initialSelections); | |||||
} | |||||
std::set<CStr> CObjectBase::CalculateRandomRemainingSelections(rng_t& rng, const std::vector<std::set<CStr> >& initialSelections) | |||||
{ | { | ||||
std::set<CStr> remainingSelections; | std::set<CStr> remainingSelections; | ||||
std::multimap<CStr, CStrW> chosenProps; | std::multimap<CStr, CStrW> chosenProps; | ||||
// Calculate a complete list of selections, so there is at least one | // Calculate a complete list of selections, so there is at least one | ||||
// (and in most cases only one) per group. | // (and in most cases only one) per group. | ||||
// In each group, if one of the variants has a name matching a string in | // In each group, if one of the variants has a name matching a string in | ||||
// 'selections', use that one. | // 'selections', use that one. | ||||
// If more than one matches, choose randomly from those matching ones. | // If more than one matches, choose randomly from those matching ones. | ||||
// If none match, choose randomly from all variants. | // If none match, choose randomly from all variants. | ||||
// | // | ||||
// When choosing randomly, make use of each variant's frequency. If all | // When choosing randomly, make use of each variant's frequency. If all | ||||
// variants have frequency 0, treat them as if they were 1. | // variants have frequency 0, treat them as if they were 1. | ||||
for (std::vector<std::vector<Variant> >::iterator grp = m_VariantGroups.begin(); | for (std::vector<std::vector<Variant> >::const_iterator grp = m_VariantGroups.begin(); | ||||
grp != m_VariantGroups.end(); | grp != m_VariantGroups.end(); | ||||
++grp) | ++grp) | ||||
{ | { | ||||
// Ignore groups with nothing inside. (A warning will have been | // Ignore groups with nothing inside. (A warning will have been | ||||
// emitted by the loading code.) | // emitted by the loading code.) | ||||
if (grp->size() == 0) | if (grp->size() == 0) | ||||
continue; | continue; | ||||
▲ Show 20 Lines • Show All 63 Lines • ▼ Show 20 Lines | else | ||||
ENSURE(randNum < 0); | ENSURE(randNum < 0); | ||||
// This should always succeed; otherwise it | // This should always succeed; otherwise it | ||||
// wouldn't have chosen any of the variants. | // wouldn't have chosen any of the variants. | ||||
} | } | ||||
} | } | ||||
// Remember which props were chosen, so we can call CalculateRandomVariation on them | // Remember which props were chosen, so we can call CalculateRandomVariation on them | ||||
// at the end. | // at the end. | ||||
Variant& var ((*grp)[match]); | const Variant& var ((*grp)[match]); | ||||
// Erase all existing props which are overridden by this variant: | // Erase all existing props which are overridden by this variant: | ||||
for (const Prop& prop : var.m_Props) | for (const Prop& prop : var.m_Props) | ||||
chosenProps.erase(prop.m_PropPointName); | chosenProps.erase(prop.m_PropPointName); | ||||
// and then insert the new ones: | // and then insert the new ones: | ||||
for (const Prop& prop : var.m_Props) | for (const Prop& prop : var.m_Props) | ||||
if (!prop.m_ModelName.empty()) | if (!prop.m_ModelName.empty()) | ||||
chosenProps.insert(make_pair(prop.m_PropPointName, prop.m_ModelName)); | chosenProps.insert(make_pair(prop.m_PropPointName, prop.m_ModelName)); | ||||
} | } | ||||
// Load each prop, and add their required selections to ours: | // Load each prop, and add their required selections to ours: | ||||
for (std::multimap<CStr, CStrW>::iterator it = chosenProps.begin(); it != chosenProps.end(); ++it) | for (std::multimap<CStr, CStrW>::iterator it = chosenProps.begin(); it != chosenProps.end(); ++it) | ||||
{ | { | ||||
CObjectBase* prop = m_ObjectManager.FindObjectBase(it->second); | CActorDef* prop = m_ObjectManager.FindActorDef(it->second); | ||||
if (prop) | if (prop) | ||||
{ | { | ||||
std::vector<std::set<CStr> > propInitialSelections = initialSelections; | std::vector<std::set<CStr> > propInitialSelections = initialSelections; | ||||
if (!remainingSelections.empty()) | if (!remainingSelections.empty()) | ||||
propInitialSelections.push_back(remainingSelections); | propInitialSelections.push_back(remainingSelections); | ||||
std::set<CStr> propRemainingSelections = prop->CalculateRandomRemainingSelections(rng, propInitialSelections); | std::set<CStr> propRemainingSelections = prop->GetBase(m_LODLevel)->CalculateRandomRemainingSelections(rng, propInitialSelections); | ||||
remainingSelections.insert(propRemainingSelections.begin(), propRemainingSelections.end()); | remainingSelections.insert(propRemainingSelections.begin(), propRemainingSelections.end()); | ||||
// Add the prop's used files to our own (recursively) so we can hotload | // Add the prop's used files to our own (recursively) so we can hotload | ||||
// when any prop is changed | // when any prop is changed | ||||
m_UsedFiles.insert(prop->m_UsedFiles.begin(), prop->m_UsedFiles.end()); | m_ActorDef.m_UsedFiles.insert(prop->m_UsedFiles.begin(), prop->m_UsedFiles.end()); | ||||
} | } | ||||
} | } | ||||
return remainingSelections; | return remainingSelections; | ||||
} | } | ||||
std::vector<std::vector<CStr> > CObjectBase::GetVariantGroups() const | std::vector<std::vector<CStr> > CObjectBase::GetVariantGroups() const | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | for (size_t i = 0; i < obj->m_VariantGroups.size(); ++i) | ||||
// Add non-trivial groups (i.e. not just one entry) to the returned list | // Add non-trivial groups (i.e. not just one entry) to the returned list | ||||
if (obj->m_VariantGroups[i].size() > 1) | if (obj->m_VariantGroups[i].size() > 1) | ||||
groups.push_back(group); | groups.push_back(group); | ||||
// Add all props onto the queue to be considered | // Add all props onto the queue to be considered | ||||
for (size_t j = 0; j < obj->m_VariantGroups[i].size(); ++j) | for (size_t j = 0; j < obj->m_VariantGroups[i].size(); ++j) | ||||
{ | { | ||||
const std::vector<Prop>& props = obj->m_VariantGroups[i][j].m_Props; | const std::vector<Prop>& props = obj->m_VariantGroups[i][j].m_Props; | ||||
for (size_t k = 0; k < props.size(); ++k) | for (size_t k = 0; k < props.size(); ++k) | ||||
{ | { | ||||
Stan: Can't we use std::find in that function? | |||||
if (! props[k].m_ModelName.empty()) | if (! props[k].m_ModelName.empty()) | ||||
{ | { | ||||
CObjectBase* prop = m_ObjectManager.FindObjectBase(props[k].m_ModelName.c_str()); | CActorDef* prop = m_ObjectManager.FindActorDef(props[k].m_ModelName.c_str()); | ||||
if (prop) | if (prop) | ||||
objectsQueue.push(prop); | objectsQueue.push(prop->GetBase(m_LODLevel)); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
return groups; | return groups; | ||||
} | } | ||||
void CObjectBase::GetLODSplits(std::vector<u8>& splits) const | |||||
{ | |||||
std::vector<u8>::iterator it = std::find_if(splits.begin(), splits.end(), [this](u8 lod) { return lod >= m_LODLevel; }); | |||||
if (it == splits.end() || *it != m_LODLevel) | |||||
splits.emplace(it, m_LODLevel); | |||||
for (const std::vector<Variant>& group : m_VariantGroups) | |||||
for (const Variant& variant : group) | |||||
for (const Prop& prop : variant.m_Props) | |||||
{ | |||||
// TODO: we probably should clean those up after XML load. | |||||
if (prop.m_ModelName.empty()) | |||||
continue; | |||||
CActorDef* propActor = m_ObjectManager.FindActorDef(prop.m_ModelName.c_str()); | |||||
if (!propActor) | |||||
continue; | |||||
std::vector<u8> newSplits = propActor->LODLevels(); | |||||
if (newSplits.size() <= 1) | |||||
continue; | |||||
// This is not entirely optimal since we might loop though redundant LOD levels, but that shouldn't matter. | |||||
// Custom implementation because this is inplace, std::set_union needs a 3rd vector. | |||||
std::vector<u8>::iterator v1 = splits.begin(); | |||||
std::vector<u8>::iterator v2 = newSplits.begin(); | |||||
while (v2 != newSplits.end()) | |||||
{ | |||||
if (v1 == splits.end() || *v1 > *v2) | |||||
{ | |||||
v1 = ++splits.insert(v1, *v2); | |||||
++v2; | |||||
} | |||||
else if (*v1 == *v2) | |||||
{ | |||||
++v1; | |||||
++v2; | |||||
} | |||||
else | |||||
++v1; | |||||
} | |||||
} | |||||
} | |||||
CStrW CObjectBase::GetPathname() const | |||||
{ | |||||
return m_ActorDef.m_Pathname.string(); | |||||
}; | |||||
CActorDef::CActorDef(CObjectManager& objectManager) : m_ObjectManager(objectManager) | |||||
{ | |||||
} | |||||
std::set<CStr> CActorDef::CalculateRandomVariation(uint32_t seed, const std::vector<std::set<CStr>>& initialSelections) const | |||||
{ | |||||
ENSURE(!m_ObjectBases.empty()); | |||||
CObjectBase::rng_t rng; | |||||
rng.seed(seed); | |||||
std::set<CStr> remainingSelections = m_ObjectBases.back()->CalculateRandomRemainingSelections(rng, initialSelections); | |||||
for (const std::set<CStr>& sel : initialSelections) | |||||
remainingSelections.insert(sel.begin(), sel.end()); | |||||
return remainingSelections; // now actually a complete set of selections | |||||
} | |||||
std::vector<CObjectEntry*> CActorDef::CreateEntries(const std::vector<std::set<CStr>>& selections) | |||||
{ | |||||
CObjectEntry* entry = m_ObjectManager.FindObjectVariation(this, 100, selections); | |||||
if (!entry) | |||||
return {}; | |||||
return { entry }; | |||||
} | |||||
std::vector<u8> CActorDef::LODLevels() const | |||||
{ | |||||
std::vector<u8> splits; | |||||
for (const std::unique_ptr<CObjectBase>& base : m_ObjectBases) | |||||
splits.emplace_back(base->m_LODLevel); | |||||
return splits; | |||||
} | |||||
u8 CActorDef::GetLODFor(u8 ratio) const | |||||
{ | |||||
for (const std::unique_ptr<CObjectBase>& base : m_ObjectBases) | |||||
if (base->m_LODLevel >= ratio) | |||||
return base->m_LODLevel; | |||||
// TODO: I'm not sure this makes sense | |||||
return 255; | |||||
} | |||||
CObjectBase* CActorDef::GetBase(u8 LODLevel) const | |||||
{ | |||||
for (const std::unique_ptr<CObjectBase>& base : m_ObjectBases) | |||||
if (base->m_LODLevel >= LODLevel) | |||||
return base.get(); | |||||
return nullptr; | |||||
} | |||||
bool CActorDef::Load(const VfsPath& pathname) | |||||
{ | |||||
m_UsedFiles.clear(); | |||||
m_UsedFiles.insert(pathname); | |||||
CXeromyces XeroFile; | |||||
if (XeroFile.Load(g_VFS, pathname, "actor") != PSRETURN_OK) | |||||
return false; | |||||
// Define all the elements used in the XML file | |||||
#define EL(x) int el_##x = XeroFile.GetElementID(#x) | |||||
#define AT(x) int at_##x = XeroFile.GetAttributeID(#x) | |||||
EL(lods); | |||||
EL(actor); | |||||
AT(to); | |||||
#undef AT | |||||
#undef EL | |||||
XMBElement root = XeroFile.GetRoot(); | |||||
if (root.GetNodeName() != el_actor && root.GetNodeName() != el_lods) | |||||
{ | |||||
LOGERROR("Invalid actor format (actor '%s', unrecognised root element '%s')", | |||||
pathname.string8().c_str(), XeroFile.GetElementString(root.GetNodeName()).c_str()); | |||||
return false; | |||||
} | |||||
m_Pathname = pathname; | |||||
if (root.GetNodeName() == el_actor) | |||||
{ | |||||
std::unique_ptr<CObjectBase> base = std::make_unique<CObjectBase>(m_ObjectManager, *this, 255); | |||||
base->Load(XeroFile, root); | |||||
m_ObjectBases.emplace_back(std::move(base)); | |||||
} | |||||
else | |||||
{ | |||||
u8 rt = 0; | |||||
XERO_ITER_EL(root, actor) | |||||
{ | |||||
if (actor.GetNodeName() != el_actor) | |||||
{ | |||||
LOGERROR("Non-actor element in actors LODS definition (file %s)", pathname.string8().c_str()); | |||||
return false; | |||||
} | |||||
bool found_to = false; | |||||
XERO_ITER_ATTR(actor, attr) | |||||
{ | |||||
if (attr.Name == at_to) | |||||
{ | |||||
unsigned int v = attr.Value.ToUInt(); | |||||
if (v > 255) | |||||
{ | |||||
LOGERROR("LOD to attribute must not be above 255 (file %s)", pathname.string8().c_str()); | |||||
return false; | |||||
} | |||||
if (v <= rt) | |||||
{ | |||||
LOGERROR("Elements must be in increasing LOD order (file %s)", pathname.string8().c_str()); | |||||
return false; | |||||
} | |||||
rt = v; | |||||
found_to = true; | |||||
} | |||||
} | |||||
if (!found_to) | |||||
rt = 255; | |||||
std::unique_ptr<CObjectBase> base = std::make_unique<CObjectBase>(m_ObjectManager, *this, rt); | |||||
base->Load(XeroFile, actor); | |||||
m_ObjectBases.emplace_back(std::move(base)); | |||||
} | |||||
if (rt != 255) | |||||
{ | |||||
LOGERROR("LOD must go up to 255 (file %s)", pathname.string8().c_str()); | |||||
return false; | |||||
} | |||||
} | |||||
// For each LOD level, check if we need to further split. | |||||
std::vector<u8> splits = LODLevels(); | |||||
for (const std::unique_ptr<CObjectBase>& base : m_ObjectBases) | |||||
base->GetLODSplits(splits); | |||||
ENSURE(splits.size() >= 1); | |||||
if (splits.size() > 5) | |||||
{ | |||||
LOGERROR("Too many LOD levels (%i) for actor %s", splits.size(), pathname.string8().c_str()); | |||||
return false; | |||||
} | |||||
std::vector<std::unique_ptr<CObjectBase>>::iterator it = m_ObjectBases.begin(); | |||||
std::vector<u8>::const_iterator lods = splits.begin(); | |||||
while (it != m_ObjectBases.end()) | |||||
if ((*it)->m_LODLevel > *lods) | |||||
{ | |||||
it = ++m_ObjectBases.emplace(it, (*it)->CopyWithLOD(*lods)); | |||||
++lods; | |||||
} | |||||
else if ((*it)->m_LODLevel == *lods) | |||||
{ | |||||
++it; | |||||
++lods; | |||||
} | |||||
else | |||||
++it; | |||||
return true; | |||||
} | |||||
bool CActorDef::Reload() | |||||
{ | |||||
return Load(m_Pathname); | |||||
} | |||||
bool CActorDef::UsesFile(const VfsPath& pathname) | |||||
{ | |||||
return m_UsedFiles.find(pathname) != m_UsedFiles.end(); | |||||
} |
Wildfire Games · Phabricator
Can't we use std::find in that function?