Index: binaries/data/config/default.cfg
===================================================================
--- binaries/data/config/default.cfg
+++ binaries/data/config/default.cfg
@@ -47,6 +47,9 @@
xres = 0
yres = 0
+lod_mult=1
+max_lod=255
+
; Force a non-standard bit depth (if 0 then use the current desktop bit depth)
bpp = 0
Index: binaries/data/mods/public/art/actors/actor.rng
===================================================================
--- binaries/data/mods/public/art/actors/actor.rng
+++ binaries/data/mods/public/art/actors/actor.rng
@@ -1,194 +1,216 @@
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ 255
+ 0
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
- 0
- 1
-
-
-
-
-
-
- 0
- 1
-
-
-
-
-
-
- 0
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- false
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 1
+
+
+
+
+
+
+ 0
+ 1
+
+
+
+
+
+
+ 0
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ false
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
Index: binaries/data/mods/public/art/actors/props/units/heads/head_hele_b.xml
===================================================================
--- binaries/data/mods/public/art/actors/props/units/heads/head_hele_b.xml
+++ binaries/data/mods/public/art/actors/props/units/heads/head_hele_b.xml
@@ -1,53 +1,102 @@
-
-
-
-
-
-
- props/dude_head.dae
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 184 151 98
-
-
- 255 205 125
-
-
- 153 145 129
-
-
- 129 152 153
-
-
- 8 8 8
-
-
- 250 130 78
-
-
-
- objectcolor.xml
-
-
+
+
+
+
+ props/dude_head.dae
+
+ 153 145 129
+
+
+
+ objectcolor.xml
+
+
+
+
+
+
+
+
+ props/dude_head.dae
+
+
+
+
+
+
+
+
+
+
+
+ 255 205 125
+
+
+ 153 145 129
+
+
+ 8 8 8
+
+
+ 250 130 78
+
+
+
+ objectcolor.xml
+
+
+
+
+
+
+
+
+ props/dude_head.dae
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 184 151 98
+
+
+ 255 205 125
+
+
+ 153 145 129
+
+
+ 129 152 153
+
+
+ 8 8 8
+
+
+ 250 130 78
+
+
+
+ objectcolor.xml
+
+
+
+
Index: binaries/data/mods/public/art/actors/props/units/helmets/hele_corinthian_e1.xml
===================================================================
--- binaries/data/mods/public/art/actors/props/units/helmets/hele_corinthian_e1.xml
+++ binaries/data/mods/public/art/actors/props/units/helmets/hele_corinthian_e1.xml
@@ -1,73 +1,123 @@
-
-
-
-
-
- props/helmet/hele_corinthian_a.dae
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- basic_trans_parallax_spec.xml
-
+
+
+
+
+
+
+ props/helmet/hele_corinthian_a.dae
+
+
+
+
+
+
+
+
+ basic_trans.xml
+
+
+
+
+
+ props/helmet/hele_corinthian_a.dae
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ basic_trans.xml
+
+
+
+
+
+ props/helmet/hele_corinthian_a.dae
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ basic_trans_parallax_spec.xml
+
+
Index: binaries/data/mods/public/art/actors/props/units/shields/aspis_athen_e.xml
===================================================================
--- binaries/data/mods/public/art/actors/props/units/shields/aspis_athen_e.xml
+++ binaries/data/mods/public/art/actors/props/units/shields/aspis_athen_e.xml
@@ -1,28 +1,82 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- player_trans_parallax_spec_helmet.xml
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ player_trans.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ player_trans_spec_helmet.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ player_trans_spec_helmet.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ player_trans_parallax_spec_helmet.xml
+
+
Index: binaries/data/mods/public/art/actors/units/athenians/infantry_archer_b.xml
===================================================================
--- binaries/data/mods/public/art/actors/units/athenians/infantry_archer_b.xml
+++ binaries/data/mods/public/art/actors/units/athenians/infantry_archer_b.xml
@@ -1,53 +1,140 @@
-
-
-
-
- skeletal/new/m_tunic_short.dae
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- player_trans.xml
-
+
+
+
+
+
+ skeletal/new/m_tunic_short.dae
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ player_trans.xml
+
+
+
+
+
+ skeletal/new/m_tunic_short.dae
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ player_trans.xml
+
+
+
+
+
+ skeletal/new/m_tunic_short.dae
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ player_trans.xml
+
+
Index: binaries/data/mods/public/art/actors/units/athenians/infantry_spearman_e.xml
===================================================================
--- binaries/data/mods/public/art/actors/units/athenians/infantry_spearman_e.xml
+++ binaries/data/mods/public/art/actors/units/athenians/infantry_spearman_e.xml
@@ -1,215 +1,481 @@
-
-
-
-
- skeletal/new/m_armor_tunic_short.dae
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- player_trans_spec.xml
-
+
+
+
+
+ skeletal/new/m_armor_tunic_short.dae
+
+
+
+
+
+
+
+
+
+
+
+
+ player_trans.xml
+
+
+
+
+ skeletal/new/m_armor_tunic_short.dae
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ player_trans.xml
+
+
+
+
+
+ skeletal/new/m_armor_tunic_short.dae
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ player_trans.xml
+
+
+
+
+
+ skeletal/new/m_armor_tunic_short.dae
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ player_trans_spec.xml
+
+
Index: binaries/data/mods/public/art/actors/units/celts/female_citizen.xml
===================================================================
--- binaries/data/mods/public/art/actors/units/celts/female_citizen.xml
+++ binaries/data/mods/public/art/actors/units/celts/female_citizen.xml
@@ -1,82 +1,149 @@
-
-
-
-
- skeletal/new/f_dress.dae
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- player_trans.xml
-
+
+
+
+
+ skeletal/new/f_dress.dae
+
+
+
+
+
+
+
+
+
+ player_trans.xml
+
+
+
+
+ skeletal/new/f_dress.dae
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ player_trans.xml
+
+
+
+
+
+ skeletal/new/f_dress.dae
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ player_trans.xml
+
+
Index: binaries/data/mods/public/gui/options/options.json
===================================================================
--- binaries/data/mods/public/gui/options/options.json
+++ binaries/data/mods/public/gui/options/options.json
@@ -149,6 +149,24 @@
"min": 0,
"max": 1
},
+ {
+ "type": "slider",
+ "label": "LOD",
+ "tooltip": "LOD multiplier",
+ "dependencies": [],
+ "config": "lod_mult",
+ "min": 0.01,
+ "max": 10.0
+ },
+ {
+ "type": "slider",
+ "label": "max LOD",
+ "tooltip": "Maximal LOD",
+ "dependencies": [],
+ "config": "max_lod",
+ "min": 0,
+ "max": 255
+ },
{
"type": "slider",
"label": "Shader effects",
Index: source/graphics/ObjectBase.h
===================================================================
--- source/graphics/ObjectBase.h
+++ source/graphics/ObjectBase.h
@@ -22,7 +22,9 @@
#include "ps/CStr.h"
#include "ps/CStrIntern.h"
+class CActorDef;
class CModel;
+class CObjectEntry;
class CObjectManager;
class CSkeletonAnim;
class CXeromyces;
@@ -34,8 +36,18 @@
#include
#include
+/**
+ * Maintains the tree of possible objects from a specific actor-LOD definition.
+ * An Object Base is made of:
+ * - a material
+ * - a few properties (float on water / casts shadow / ...)
+ * - a number of variant groups.
+ * Any actual object in game will pick a variant from each group (see ObjectEntry).
+ */
class CObjectBase
{
+ friend CActorDef;
+
NONCOPYABLE(CObjectBase);
public:
struct Anim
@@ -118,56 +130,21 @@
std::multimap samplers;
};
- CObjectBase(CObjectManager& objectManager);
+ CObjectBase(CObjectManager& objectManager, CActorDef& actorDef, u8 lodLevel);
// Get the variation key (indices of chosen variants from each group)
// based on the selection strings
- std::vector CalculateVariationKey(const std::vector >& selections);
+ std::vector CalculateVariationKey(const std::vector >& selections) const;
// Get the final actor data, combining all selected variants
- const Variation BuildVariation(const std::vector& variationKey);
-
- // Get a set of selection strings that are complete enough to specify an
- // exact variation of the actor, using the initial selections wherever possible
- // and choosing randomly where a choice is necessary.
- std::set CalculateRandomVariation(uint32_t seed, const std::set& initialSelections);
-
- // Given a prioritized vector of selection string sets that partially specify
- // a variation, calculates a remaining set of selection strings such that the resulting
- // set merged with the initial selections fully specifies an exact variation of
- // the actor. The resulting selections are selected randomly, but only where a choice
- // is necessary (i.e. where there are multiple variants but the initial selections,
- // applied in priority order, fail to select one).
- std::set CalculateRandomRemainingSelections(uint32_t seed, const std::vector >& initialSelections);
+ const Variation BuildVariation(const std::vector& variationKey) const;
// Get a list of variant groups for this object, plus for all possible
// props. Duplicated groups are removed, if several props share the same
// variant names.
std::vector > GetVariantGroups() const;
- /**
- * Initialise this object by loading from the given file.
- * Returns false on error.
- */
- bool Load(const VfsPath& pathname);
-
- /**
- * Reload this object from the file that it was previously loaded from.
- * Returns false on error.
- */
- bool Reload();
-
- /**
- * Returns whether this object (including any possible props)
- * uses the given file. (This is used for hotloading.)
- */
- bool UsesFile(const VfsPath& pathname);
-
- // filename that this was loaded from
- VfsPath m_Pathname;
-
- // short human-readable name
- CStrW m_ShortName;
+ CStrW GetPathname() const;
struct {
// cast shadows from this object
@@ -179,20 +156,93 @@
// the material file
VfsPath m_Material;
+ u8 m_LODLevel;
+
+ // short human-readable name
+ CStrW m_ShortName;
+
private:
+ // Backref to the owning actor.
+ CActorDef& m_ActorDef;
+
// A low-quality RNG like rand48 causes visible non-random patterns (particularly
// in large grids of the same actor with consecutive seeds, e.g. forests),
// so use a better one that appears to avoid those patterns
using rng_t = boost::mt19937;
- std::set CalculateRandomRemainingSelections(rng_t& rng, const std::vector >& initialSelections);
+ std::set CalculateRandomRemainingSelections(rng_t& rng, const std::vector>& initialSelections) const;
std::vector< std::vector > m_VariantGroups;
CObjectManager& m_ObjectManager;
- std::unordered_set m_UsedFiles;
-
+ void Load(const CXeromyces& XeroFile, const XMBElement& base);
void LoadVariant(const CXeromyces& XeroFile, const XMBElement& variant, Variant& currentVariant);
};
+/**
+ * Represents an actor file. Actors can contain various Level of Detail (LOD) levels.
+ * An ActorDef maintains a CObjectBase for each specified LOD level, and provides access to it.
+ */
+class CActorDef
+{
+ friend class CObjectManager;
+ friend class CObjectBase;
+ NONCOPYABLE(CActorDef);
+public:
+
+ CActorDef(CObjectManager& objectManager);
+
+ /**
+ * Actors can have various LOD, and ideally we want those to match.
+ * This calculates a complete list of selection for the highest-LOD actor,
+ * which should be used for any-LOD entry.
+ */
+ std::set CalculateRandomVariation(uint32_t seed, const std::vector>& initialSelections) const;
+
+ /**
+ * Return a list of Object Entries (one for each LOD level) matching the selections.
+ */
+ std::vector CreateEntries(const std::vector>& selections);
+
+ u8 LODLevels() const;
+ u8 GetLODFor(u8 ratio) const;
+
+ VfsPath GetPathname() const { return m_Pathname; }
+
+// Interface accessible from CObjectManager / CObjectBase
+protected:
+ /**
+ * Return the Object base matching the given LOD level.
+ */
+ CObjectBase* GetBase(u8 LODLevel) const;
+
+ /**
+ * Initialise this object by loading from the given file.
+ * Returns false on error.
+ */
+ bool Load(const VfsPath& pathname);
+
+ /**
+ * Reload this object from the file that it was previously loaded from.
+ * Returns false on error.
+ */
+ bool Reload();
+
+ /**
+ * Returns whether this object (including any possible props)
+ * uses the given file. (This is used for hotloading.)
+ */
+ bool UsesFile(const VfsPath& pathname);
+
+ // filename that this was loaded from
+ VfsPath m_Pathname;
+
+private:
+ CObjectManager& m_ObjectManager;
+
+ std::vector> m_ObjectBases;
+
+ std::unordered_set m_UsedFiles;
+};
+
#endif
Index: source/graphics/ObjectBase.cpp
===================================================================
--- source/graphics/ObjectBase.cpp
+++ source/graphics/ObjectBase.cpp
@@ -31,11 +31,79 @@
#include
-CObjectBase::CObjectBase(CObjectManager& objectManager)
-: m_ObjectManager(objectManager)
+CObjectBase::CObjectBase(CObjectManager& objectManager, CActorDef& actorDef, u8 lodLevel)
+: m_ObjectManager(objectManager), m_ActorDef(actorDef)
{
+ m_LODLevel = lodLevel;
m_Properties.m_CastShadows = false;
m_Properties.m_FloatOnWater = false;
+
+ m_ShortName = m_ActorDef.m_Pathname.string() + CStrW::FromInt(m_LODLevel);
+}
+
+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(actor);
+ EL(castshadow);
+ EL(float);
+ EL(group);
+ EL(material);
+#undef AT
+#undef EL
+
+ // Set up the vector> m_Variants to contain the right number
+ // of elements, to avoid wasteful copying/reallocation later.
+ {
+ // Count the variants in each group
+ std::vector 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 >::iterator currentGroup = m_VariantGroups.begin();
+
+ XERO_ITER_EL(root, child)
+ {
+ int child_name = child.GetNodeName();
+
+ if (child_name == el_group)
+ {
+ std::vector::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)
@@ -87,7 +155,7 @@
{
// Open up an external file to load.
// 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;
if (XeroVariant.Load(g_VFS, "art/variants/" + attr.Value) == PSRETURN_OK)
{
@@ -152,7 +220,7 @@
// For particle hotloading, it's easiest to reload the entire 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)
{
@@ -213,105 +281,7 @@
}
}
-bool CObjectBase::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(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> m_Variants to contain the right number
- // of elements, to avoid wasteful copying/reallocation later.
- {
- // Count the variants in each group
- std::vector 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 >::iterator currentGroup = m_VariantGroups.begin();
-
- XERO_ITER_EL(root, child)
- {
- int child_name = child.GetNodeName();
-
- if (child_name == el_group)
- {
- std::vector::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 CObjectBase::CalculateVariationKey(const std::vector >& selections)
+std::vector CObjectBase::CalculateVariationKey(const std::vector >& selections) const
{
// (TODO: see CObjectManager::FindObjectVariation for an opportunity to
// call this function a bit less frequently)
@@ -328,7 +298,7 @@
std::multimap chosenProps;
- for (std::vector >::iterator grp = m_VariantGroups.begin();
+ for (std::vector >::const_iterator grp = m_VariantGroups.begin();
grp != m_VariantGroups.end();
++grp)
{
@@ -376,7 +346,7 @@
// Remember which props were chosen, so we can call CalculateVariationKey on them
// at the end.
// 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)
chosenProps.erase(prop.m_PropPointName);
@@ -389,10 +359,10 @@
// Load each prop, and add their CalculateVariationKey to our key:
for (std::multimap::iterator it = chosenProps.begin(); it != chosenProps.end(); ++it)
{
- CObjectBase* prop = m_ObjectManager.FindObjectBase(it->second);
+ CActorDef* prop = m_ObjectManager.FindActorDef(it->second);
if (prop)
{
- std::vector propChoices = prop->CalculateVariationKey(selections);
+ std::vector propChoices = prop->GetBase(m_LODLevel)->CalculateVariationKey(selections);
choices.insert(choices.end(), propChoices.begin(), propChoices.end());
}
}
@@ -400,7 +370,7 @@
return choices;
}
-const CObjectBase::Variation CObjectBase::BuildVariation(const std::vector& variationKey)
+const CObjectBase::Variation CObjectBase::BuildVariation(const std::vector& variationKey) const
{
Variation variation;
@@ -408,7 +378,7 @@
// 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.)
- std::vector >::iterator grp = m_VariantGroups.begin();
+ std::vector >::const_iterator grp = m_VariantGroups.begin();
std::vector::const_iterator match = variationKey.begin();
for ( ;
grp != m_VariantGroups.end() && match != variationKey.end();
@@ -428,7 +398,7 @@
}
// Get the matched variant
- CObjectBase::Variant& var ((*grp)[id]);
+ const CObjectBase::Variant& var ((*grp)[id]);
// Apply its data:
@@ -449,50 +419,32 @@
// original should be erased, and replaced by the two new ones.
//
// So, erase all existing props which are overridden by this variant:
- for (std::vector::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it)
+ for (std::vector::const_iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it)
variation.props.erase(it->m_PropPointName);
// and then insert the new ones:
- for (std::vector::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it)
+ for (std::vector::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
variation.props.insert(make_pair(it->m_PropPointName, *it));
// Same idea applies for animations.
// So, erase all existing animations which are overridden by this variant:
- for (std::vector::iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it)
+ for (std::vector::const_iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it)
variation.anims.erase(it->m_AnimName);
// and then insert the new ones:
- for (std::vector::iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it)
+ for (std::vector::const_iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it)
variation.anims.insert(make_pair(it->m_AnimName, *it));
// Same for samplers, though perhaps not strictly necessary:
- for (std::vector::iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it)
+ for (std::vector::const_iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it)
variation.samplers.erase(it->m_SamplerName.string());
- for (std::vector::iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it)
+ for (std::vector::const_iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it)
variation.samplers.insert(make_pair(it->m_SamplerName.string(), *it));
}
return variation;
}
-std::set CObjectBase::CalculateRandomVariation(uint32_t seed, const std::set& initialSelections)
-{
- rng_t rng;
- rng.seed(seed);
-
- std::set remainingSelections = CalculateRandomRemainingSelections(rng, std::vector >(1, initialSelections));
- remainingSelections.insert(initialSelections.begin(), initialSelections.end());
-
- return remainingSelections; // now actually a complete set of selections
-}
-
-std::set CObjectBase::CalculateRandomRemainingSelections(uint32_t seed, const std::vector >& initialSelections)
-{
- rng_t rng;
- rng.seed(seed);
- return CalculateRandomRemainingSelections(rng, initialSelections);
-}
-
-std::set CObjectBase::CalculateRandomRemainingSelections(rng_t& rng, const std::vector >& initialSelections)
+std::set CObjectBase::CalculateRandomRemainingSelections(rng_t& rng, const std::vector>& initialSelections) const
{
std::set remainingSelections;
std::multimap chosenProps;
@@ -507,7 +459,7 @@
// When choosing randomly, make use of each variant's frequency. If all
// variants have frequency 0, treat them as if they were 1.
- for (std::vector >::iterator grp = m_VariantGroups.begin();
+ for (std::vector >::const_iterator grp = m_VariantGroups.begin();
grp != m_VariantGroups.end();
++grp)
{
@@ -587,7 +539,7 @@
// Remember which props were chosen, so we can call CalculateRandomVariation on them
// at the end.
- Variant& var ((*grp)[match]);
+ const Variant& var ((*grp)[match]);
// Erase all existing props which are overridden by this variant:
for (const Prop& prop : var.m_Props)
chosenProps.erase(prop.m_PropPointName);
@@ -600,19 +552,19 @@
// Load each prop, and add their required selections to ours:
for (std::multimap::iterator it = chosenProps.begin(); it != chosenProps.end(); ++it)
{
- CObjectBase* prop = m_ObjectManager.FindObjectBase(it->second);
+ CActorDef* prop = m_ObjectManager.FindActorDef(it->second);
if (prop)
{
std::vector > propInitialSelections = initialSelections;
if (!remainingSelections.empty())
propInitialSelections.push_back(remainingSelections);
- std::set propRemainingSelections = prop->CalculateRandomRemainingSelections(rng, propInitialSelections);
+ std::set propRemainingSelections = prop->GetBase(m_LODLevel)->CalculateRandomRemainingSelections(rng, propInitialSelections);
remainingSelections.insert(propRemainingSelections.begin(), propRemainingSelections.end());
// Add the prop's used files to our own (recursively) so we can hotload
// 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());
}
}
@@ -677,9 +629,9 @@
{
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)
- objectsQueue.push(prop);
+ objectsQueue.push(prop->GetBase(m_LODLevel));
}
}
}
@@ -688,3 +640,141 @@
return groups;
}
+
+CStrW CObjectBase::GetPathname() const
+{
+ return m_ActorDef.m_Pathname.string();
+};
+
+CActorDef::CActorDef(CObjectManager& objectManager) : m_ObjectManager(objectManager)
+{
+}
+
+std::set CActorDef::CalculateRandomVariation(uint32_t seed, const std::vector>& initialSelections) const
+{
+ ENSURE(!m_ObjectBases.empty());
+
+ CObjectBase::rng_t rng;
+ rng.seed(seed);
+
+ std::set remainingSelections = m_ObjectBases.back()->CalculateRandomRemainingSelections(rng, initialSelections);
+ for (const std::set& sel : initialSelections)
+ remainingSelections.insert(sel.begin(), sel.end());
+
+ return remainingSelections; // now actually a complete set of selections
+}
+
+std::vector CActorDef::CreateEntries(const std::vector>& selections)
+{
+ CObjectEntry* entry = m_ObjectManager.FindObjectVariation(this, 100, selections);
+ if (!entry)
+ return {};
+ return { entry };
+}
+
+u8 CActorDef::LODLevels() const
+{
+ return m_ObjectBases.size();
+}
+
+u8 CActorDef::GetLODFor(u8 ratio) const
+{
+ for (const auto& base : m_ObjectBases)
+ if (base->m_LODLevel >= ratio)
+ return base->m_LODLevel;
+}
+
+CObjectBase* CActorDef::GetBase(u8 LODLevel) const
+{
+ for (const auto& 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(from);
+ 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 base = std::make_unique(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 base = std::make_unique(m_ObjectManager, *this, rt);
+ base->Load(XeroFile, actor);
+ m_ObjectBases.emplace_back(std::move(base));
+ }
+
+ }
+ return true;
+}
+
+bool CActorDef::Reload()
+{
+ return Load(m_Pathname);
+}
+
+bool CActorDef::UsesFile(const VfsPath& pathname)
+{
+ return m_UsedFiles.find(pathname) != m_UsedFiles.end();
+}
Index: source/graphics/ObjectEntry.h
===================================================================
--- source/graphics/ObjectEntry.h
+++ source/graphics/ObjectEntry.h
@@ -40,7 +40,7 @@
NONCOPYABLE(CObjectEntry);
public:
- CObjectEntry(CObjectBase* base, CSimulation2& simulation);
+ CObjectEntry(const CObjectBase& base, CSimulation2& simulation);
~CObjectEntry();
// Construct this actor, using the specified variation selections
@@ -49,7 +49,7 @@
// Base actor. Contains all the things that don't change between
// different variations of the actor.
- CObjectBase* m_Base;
+ const CObjectBase& m_Base;
// samplers list
std::vector m_Samplers;
Index: source/graphics/ObjectEntry.cpp
===================================================================
--- source/graphics/ObjectEntry.cpp
+++ source/graphics/ObjectEntry.cpp
@@ -39,7 +39,7 @@
#include
-CObjectEntry::CObjectEntry(CObjectBase* base, CSimulation2& simulation) :
+CObjectEntry::CObjectEntry(const CObjectBase& base, CSimulation2& simulation) :
m_Base(base), m_Color(1.0f, 1.0f, 1.0f, 1.0f), m_Model(NULL), m_Outdated(false), m_Simulation(simulation)
{
}
@@ -57,7 +57,7 @@
const std::vector& variationKey,
CObjectManager& objectManager)
{
- CObjectBase::Variation variation = m_Base->BuildVariation(variationKey);
+ CObjectBase::Variation variation = m_Base.BuildVariation(variationKey);
// Copy the chosen data onto this model:
@@ -72,14 +72,14 @@
str << variation.color;
int r, g, b;
if (! (str >> r >> g >> b)) // Any trailing data is ignored
- LOGERROR("Actor '%s' has invalid RGB color '%s'", utf8_from_wstring(m_Base->m_ShortName), variation.color);
+ LOGERROR("Actor '%s' has invalid RGB color '%s'", utf8_from_wstring(m_Base.m_ShortName), variation.color);
else
m_Color = CColor(r/255.0f, g/255.0f, b/255.0f, 1.0f);
}
if (variation.decal.m_SizeX && variation.decal.m_SizeZ)
{
- CMaterial material = g_Renderer.GetMaterialManager().LoadMaterial(m_Base->m_Material);
+ CMaterial material = g_Renderer.GetMaterialManager().LoadMaterial(m_Base.m_Material);
for (const CObjectBase::Samp& samp : m_Samplers)
{
@@ -94,7 +94,7 @@
SDecal decal(material,
variation.decal.m_SizeX, variation.decal.m_SizeZ,
variation.decal.m_Angle, variation.decal.m_OffsetX, variation.decal.m_OffsetZ,
- m_Base->m_Properties.m_FloatOnWater);
+ m_Base.m_Properties.m_FloatOnWater);
m_Model = new CModelDecal(objectManager.GetTerrain(), decal);
return true;
@@ -125,12 +125,12 @@
CModel* model = new CModel(objectManager.GetSkeletonAnimManager(), m_Simulation);
delete m_Model;
m_Model = model;
- model->SetMaterial(g_Renderer.GetMaterialManager().LoadMaterial(m_Base->m_Material));
+ model->SetMaterial(g_Renderer.GetMaterialManager().LoadMaterial(m_Base.m_Material));
model->GetMaterial().AddStaticUniform("objectColor", CVector4D(m_Color.r, m_Color.g, m_Color.b, m_Color.a));
model->InitModel(modeldef);
if (m_Samplers.empty())
- LOGERROR("Actor '%s' has no textures.", utf8_from_wstring(m_Base->m_ShortName));
+ LOGERROR("Actor '%s' has no textures.", utf8_from_wstring(m_Base.m_ShortName));
for (const CObjectBase::Samp& samp : m_Samplers)
{
@@ -148,7 +148,7 @@
{
if (std::find_if(m_Samplers.begin(), m_Samplers.end(),
[&](const CObjectBase::Samp& sampler) { return sampler.m_SamplerName == requSampName; }) == m_Samplers.end())
- LOGERROR("Actor %s: required texture sampler %s not found (material %s)", utf8_from_wstring(m_Base->m_ShortName), requSampName.string().c_str(), m_Base->m_Material.string8().c_str());
+ LOGERROR("Actor %s: required texture sampler %s not found (material %s)", utf8_from_wstring(m_Base.m_ShortName), requSampName.string().c_str(), m_Base.m_Material.string8().c_str());
}
// calculate initial object space bounds, based on vertex positions
@@ -209,10 +209,10 @@
continue;
}
- CObjectEntry* oe = objectManager.FindObjectVariation(prop.m_ModelName.c_str(), selections);
+ CObjectEntry* oe = objectManager.FindObjectVariation(prop.m_ModelName.c_str(), m_Base.m_LODLevel, selections);
if (!oe)
{
- LOGERROR("Failed to build prop model \"%s\" on actor \"%s\"", utf8_from_wstring(prop.m_ModelName), utf8_from_wstring(m_Base->m_ShortName));
+ LOGERROR("Failed to build prop model \"%s\" on actor \"%s\"", utf8_from_wstring(prop.m_ModelName), utf8_from_wstring(m_Base.m_ShortName));
continue;
}
@@ -243,16 +243,16 @@
propmodel->ToCModel()->SetAnimation(oe->GetRandomAnimation("idle"));
}
else
- LOGERROR("Failed to find matching prop point called \"%s\" in model \"%s\" for actor \"%s\"", ppn, m_ModelName.string8(), utf8_from_wstring(m_Base->m_ShortName));
+ LOGERROR("Failed to find matching prop point called \"%s\" in model \"%s\" for actor \"%s\"", ppn, m_ModelName.string8(), utf8_from_wstring(m_Base.m_ShortName));
}
// Setup flags.
- if (m_Base->m_Properties.m_CastShadows)
+ if (m_Base.m_Properties.m_CastShadows)
{
model->SetFlags(model->GetFlags() | MODELFLAG_CASTSHADOWS);
}
- if (m_Base->m_Properties.m_FloatOnWater)
+ if (m_Base.m_Properties.m_FloatOnWater)
{
model->SetFlags(model->GetFlags() | MODELFLAG_FLOATONWATER);
}
Index: source/graphics/ObjectManager.h
===================================================================
--- source/graphics/ObjectManager.h
+++ source/graphics/ObjectManager.h
@@ -18,13 +18,15 @@
#ifndef INCLUDED_OBJECTMANAGER
#define INCLUDED_OBJECTMANAGER
-#include
-#include