Index: ps/trunk/source/graphics/UnitManager.cpp
===================================================================
--- ps/trunk/source/graphics/UnitManager.cpp (revision 26269)
+++ ps/trunk/source/graphics/UnitManager.cpp (revision 26270)
@@ -1,99 +1,107 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
/*
* Container that owns all units
*/
#include "precompiled.h"
#include
#include "Model.h"
#include "UnitManager.h"
#include "Unit.h"
#include "ObjectManager.h"
#include "ObjectEntry.h"
#include "ps/Game.h"
#include "ps/World.h"
#include
///////////////////////////////////////////////////////////////////////////////
// CUnitManager constructor
CUnitManager::CUnitManager() :
m_ObjectManager(NULL)
{
}
///////////////////////////////////////////////////////////////////////////////
// CUnitManager destructor
CUnitManager::~CUnitManager()
{
DeleteAll();
}
///////////////////////////////////////////////////////////////////////////////
// AddUnit: add given unit to world
void CUnitManager::AddUnit(CUnit* unit)
{
m_Units.push_back(unit);
}
///////////////////////////////////////////////////////////////////////////////
// RemoveUnit: remove given unit from world, but don't delete it
void CUnitManager::RemoveUnit(CUnit* unit)
{
// find entry in list
typedef std::vector::iterator Iter;
Iter i=std::find(m_Units.begin(),m_Units.end(),unit);
if (i!=m_Units.end()) {
m_Units.erase(i);
}
}
///////////////////////////////////////////////////////////////////////////////
// DeleteUnit: remove given unit from world and delete it
void CUnitManager::DeleteUnit(CUnit* unit)
{
RemoveUnit(unit);
delete unit;
}
///////////////////////////////////////////////////////////////////////////////
// DeleteAll: remove and delete all units
void CUnitManager::DeleteAll()
{
for (size_t i=0;iGetModel().SetTerrainDirty(i0, j0, i1, j1);
+}
Index: ps/trunk/source/graphics/UnitManager.h
===================================================================
--- ps/trunk/source/graphics/UnitManager.h (revision 26269)
+++ ps/trunk/source/graphics/UnitManager.h (revision 26270)
@@ -1,66 +1,72 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
/*
* Container that owns all units
*/
#ifndef INCLUDED_UNITMANAGER
#define INCLUDED_UNITMANAGER
#include "ps/CStrForward.h"
#include
#include
class CUnit;
class CObjectManager;
///////////////////////////////////////////////////////////////////////////////
// CUnitManager: simple container class holding all units within the world
class CUnitManager
{
public:
// constructor, destructor
CUnitManager();
~CUnitManager();
// add given unit to world
void AddUnit(CUnit* unit);
// remove given unit from world, but don't delete it
void RemoveUnit(CUnit* unit);
// remove given unit from world and delete it
void DeleteUnit(CUnit* unit);
// remove and delete all units
void DeleteAll();
// creates a new unit and adds it to the world
CUnit* CreateUnit(const CStrW& actorName, uint32_t seed);
// return the units
const std::vector& GetUnits() const { return m_Units; }
void SetObjectManager(CObjectManager& objectManager) { m_ObjectManager = &objectManager; }
+ /**
+ * Mark a specific region of the terrain as dirty.
+ * Coordinates are in terrain tiles, lower inclusive, upper exclusive.
+ */
+ void MakeTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1, int dirtyFlags);
+
private:
// list of all known units
std::vector m_Units;
// graphical object manager; may be NULL if not set up
CObjectManager* m_ObjectManager;
};
#endif
Index: ps/trunk/source/simulation2/components/CCmpVisualActor.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpVisualActor.cpp (revision 26269)
+++ ps/trunk/source/simulation2/components/CCmpVisualActor.cpp (revision 26270)
@@ -1,799 +1,789 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpVisual.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/serialization/SerializedTypes.h"
#include "ICmpFootprint.h"
#include "ICmpIdentity.h"
#include "ICmpMirage.h"
#include "ICmpOwnership.h"
#include "ICmpPosition.h"
#include "ICmpTemplateManager.h"
#include "ICmpTerrain.h"
#include "ICmpUnitMotion.h"
#include "ICmpUnitRenderer.h"
#include "ICmpValueModificationManager.h"
#include "ICmpVisibility.h"
#include "ICmpSound.h"
#include "graphics/Decal.h"
#include "graphics/Model.h"
#include "graphics/ObjectBase.h"
#include "graphics/ObjectEntry.h"
#include "graphics/Unit.h"
#include "graphics/UnitAnimation.h"
#include "graphics/UnitManager.h"
#include "maths/BoundingSphere.h"
#include "maths/Frustum.h"
#include "maths/Matrix3D.h"
#include "maths/Vector3D.h"
#include "ps/CLogger.h"
#include "ps/GameSetup/Config.h"
#include "renderer/Scene.h"
class CCmpVisualActor : public ICmpVisual
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_InterpolatedPositionChanged);
componentManager.SubscribeToMessageType(MT_OwnershipChanged);
componentManager.SubscribeToMessageType(MT_ValueModification);
- componentManager.SubscribeToMessageType(MT_TerrainChanged);
componentManager.SubscribeToMessageType(MT_Create);
componentManager.SubscribeToMessageType(MT_Destroy);
}
DEFAULT_COMPONENT_ALLOCATOR(VisualActor)
private:
std::wstring m_BaseActorName, m_ActorName;
bool m_IsFoundationActor;
// Not initialized in non-visual mode
CUnit* m_Unit;
CModelAbstract::CustomSelectionShape* m_ShapeDescriptor = nullptr;
fixed m_R, m_G, m_B; // shading color
// Current animation state
std::string m_AnimName;
bool m_AnimOnce;
fixed m_AnimSpeed;
std::wstring m_SoundGroup;
fixed m_AnimDesync;
fixed m_AnimSyncRepeatTime; // 0.0 if not synced
fixed m_AnimSyncOffsetTime;
std::map m_VariantSelections;
u32 m_Seed; // seed used for random variations
bool m_ConstructionPreview;
bool m_VisibleInAtlasOnly;
bool m_IsActorOnly; // an in-world entity should not have this or it might not be rendered.
bool m_SilhouetteDisplay;
bool m_SilhouetteOccluder;
bool m_DisableShadows;
ICmpUnitRenderer::tag_t m_ModelTag;
public:
static std::string GetSchema()
{
return
"Display the unit using the engine's actor system."
""
"units/hellenes/infantry_spearman_b.xml"
""
""
"structures/hellenes/barracks.xml"
"structures/fndn_4x4.xml"
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
"0.0"
""
""
""
""
"0.0"
""
""
""
""
"0.0"
""
""
""
""
""
""
"0.0"
""
""
""
""
"0.0"
""
""
""
""
""
""
""
""
"";
}
virtual void Init(const CParamNode& paramNode)
{
m_Unit = NULL;
m_R = m_G = m_B = fixed::FromInt(1);
m_ConstructionPreview = paramNode.GetChild("ConstructionPreview").IsOk();
m_Seed = GetEntityId();
m_IsFoundationActor = paramNode.GetChild("Foundation").IsOk() && paramNode.GetChild("FoundationActor").IsOk();
m_BaseActorName = paramNode.GetChild(m_IsFoundationActor ? "FoundationActor" : "Actor").ToWString();
ParseActorName(m_BaseActorName);
m_VisibleInAtlasOnly = paramNode.GetChild("VisibleInAtlasOnly").ToBool();
m_IsActorOnly = paramNode.GetChild("ActorOnly").IsOk();
m_SilhouetteDisplay = paramNode.GetChild("SilhouetteDisplay").ToBool();
m_SilhouetteOccluder = paramNode.GetChild("SilhouetteOccluder").ToBool();
m_DisableShadows = paramNode.GetChild("DisableShadows").ToBool();
// Initialize the model's selection shape descriptor. This currently relies on the component initialization order; the
// Footprint component must be initialized before this component (VisualActor) to support the ability to use the footprint
// shape for the selection box (instead of the default recursive bounding box). See TypeList.h for the order in
// which components are initialized; if for whatever reason you need to get rid of this dependency, you can always just
// initialize the selection shape descriptor on-demand.
InitSelectionShapeDescriptor(paramNode);
}
virtual void Deinit()
{
if (m_Unit)
{
GetSimContext().GetUnitManager().DeleteUnit(m_Unit);
m_Unit = NULL;
}
}
template
void SerializeCommon(S& serialize)
{
serialize.NumberFixed_Unbounded("r", m_R);
serialize.NumberFixed_Unbounded("g", m_G);
serialize.NumberFixed_Unbounded("b", m_B);
serialize.StringASCII("anim name", m_AnimName, 0, 256);
serialize.Bool("anim once", m_AnimOnce);
serialize.NumberFixed_Unbounded("anim speed", m_AnimSpeed);
serialize.String("sound group", m_SoundGroup, 0, 256);
serialize.NumberFixed_Unbounded("anim desync", m_AnimDesync);
serialize.NumberFixed_Unbounded("anim sync repeat time", m_AnimSyncRepeatTime);
serialize.NumberFixed_Unbounded("anim sync offset time", m_AnimSyncOffsetTime);
Serializer(serialize, "variation", m_VariantSelections);
serialize.NumberU32_Unbounded("seed", m_Seed);
serialize.String("actor", m_ActorName, 0, 256);
// TODO: store actor variables?
}
virtual void Serialize(ISerializer& serialize)
{
// TODO: store the actor name, if !debug and it differs from the template
if (serialize.IsDebug())
{
serialize.String("base actor", m_BaseActorName, 0, 256);
}
SerializeCommon(serialize);
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
{
Init(paramNode);
u32 oldSeed = GetActorSeed();
SerializeCommon(deserialize);
InitModel();
// If we serialized a different seed or different actor, reload actor
if (oldSeed != GetActorSeed() || m_BaseActorName != m_ActorName)
ReloadActor();
else
ReloadUnitAnimation();
if (m_Unit)
{
CmpPtr cmpOwnership(GetEntityHandle());
if (cmpOwnership)
m_Unit->GetModel().SetPlayerID(cmpOwnership->GetOwner());
}
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_OwnershipChanged:
{
RecomputeActorName();
if (!m_Unit)
break;
const CMessageOwnershipChanged& msgData = static_cast (msg);
m_Unit->GetModel().SetPlayerID(msgData.to);
break;
}
- case MT_TerrainChanged:
- {
- if (!m_Unit)
- break;
-
- const CMessageTerrainChanged& msgData = static_cast (msg);
- m_Unit->GetModel().SetTerrainDirty(msgData.i0, msgData.j0, msgData.i1, msgData.j1);
- break;
- }
case MT_ValueModification:
{
// Mirages don't respond to technology modifications.
CmpPtr cmpMirage(GetEntityHandle());
if (cmpMirage)
return;
const CMessageValueModification& msgData = static_cast (msg);
if (msgData.component != L"VisualActor")
break;
RecomputeActorName();
break;
}
case MT_InterpolatedPositionChanged:
{
const CMessageInterpolatedPositionChanged& msgData = static_cast (msg);
if (m_ModelTag.valid())
{
CmpPtr cmpModelRenderer(GetSystemEntity());
cmpModelRenderer->UpdateUnitPos(m_ModelTag, msgData.inWorld, msgData.pos0, msgData.pos1);
}
break;
}
case MT_Create:
{
InitModel();
SelectAnimation("idle");
break;
}
case MT_Destroy:
{
if (m_ModelTag.valid())
{
CmpPtr cmpModelRenderer(GetSystemEntity());
cmpModelRenderer->RemoveUnit(m_ModelTag);
m_ModelTag = ICmpUnitRenderer::tag_t();
}
break;
}
}
}
virtual CBoundingBoxAligned GetBounds() const
{
if (!m_Unit)
return CBoundingBoxAligned::EMPTY;
return m_Unit->GetModel().GetWorldBounds();
}
virtual CUnit* GetUnit()
{
return m_Unit;
}
virtual CBoundingBoxOriented GetSelectionBox() const
{
if (!m_Unit)
return CBoundingBoxOriented::EMPTY;
return m_Unit->GetModel().GetSelectionBox();
}
virtual CVector3D GetPosition() const
{
if (!m_Unit)
return CVector3D(0, 0, 0);
return m_Unit->GetModel().GetTransform().GetTranslation();
}
virtual std::wstring GetProjectileActor() const
{
if (!m_Unit)
return L"";
return m_Unit->GetObject().m_ProjectileModelName;
}
virtual CFixedVector3D GetProjectileLaunchPoint() const
{
if (!m_Unit)
return CFixedVector3D();
if (m_Unit->GetModel().ToCModel())
{
// Ensure the prop transforms are correct
CmpPtr cmpUnitRenderer(GetSystemEntity());
CmpPtr cmpPosition(GetEntityHandle());
if (cmpUnitRenderer && cmpPosition)
{
float frameOffset = cmpUnitRenderer->GetFrameOffset();
CMatrix3D transform(cmpPosition->GetInterpolatedTransform(frameOffset));
m_Unit->GetModel().SetTransform(transform);
m_Unit->GetModel().ValidatePosition();
}
CModelAbstract* ammo = m_Unit->GetModel().ToCModel()->FindFirstAmmoProp();
if (ammo)
{
CVector3D vector = ammo->GetTransform().GetTranslation();
return CFixedVector3D(fixed::FromFloat(vector.X), fixed::FromFloat(vector.Y), fixed::FromFloat(vector.Z));
}
}
return CFixedVector3D();
}
virtual void SetVariant(const CStr& key, const CStr& selection)
{
if (m_VariantSelections[key] == selection)
return;
m_VariantSelections[key] = selection;
if (m_Unit)
{
m_Unit->SetEntitySelection(key, selection);
if (m_Unit->GetAnimation())
m_Unit->GetAnimation()->ReloadAnimation();
}
}
virtual std::string GetAnimationName() const
{
return m_AnimName;
}
virtual void SelectAnimation(const std::string& name, bool once = false, fixed speed = fixed::FromInt(1))
{
m_AnimName = name;
m_AnimOnce = once;
m_AnimSpeed = speed;
m_SoundGroup = L"";
m_AnimDesync = fixed::FromInt(1)/20; // TODO: make this an argument
m_AnimSyncRepeatTime = fixed::Zero();
m_AnimSyncOffsetTime = fixed::Zero();
SetVariant("animation", m_AnimName);
CmpPtr cmpSound(GetEntityHandle());
if (cmpSound)
m_SoundGroup = cmpSound->GetSoundGroup(wstring_from_utf8(m_AnimName));
if (!m_Unit || !m_Unit->GetAnimation() || !m_Unit->GetID())
return;
m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
}
virtual void SelectMovementAnimation(const std::string& name, fixed speed)
{
ENSURE(name == "idle" || name == "walk" || name == "run");
if (m_AnimName != "idle" && m_AnimName != "walk" && m_AnimName != "run")
return;
if (m_AnimName == name && speed == m_AnimSpeed)
return;
SelectAnimation(name, false, speed);
}
virtual void SetAnimationSyncRepeat(fixed repeattime)
{
m_AnimSyncRepeatTime = repeattime;
if (m_Unit && m_Unit->GetAnimation())
m_Unit->GetAnimation()->SetAnimationSyncRepeat(m_AnimSyncRepeatTime.ToFloat());
}
virtual void SetAnimationSyncOffset(fixed actiontime)
{
m_AnimSyncOffsetTime = actiontime;
if (m_Unit && m_Unit->GetAnimation())
m_Unit->GetAnimation()->SetAnimationSyncOffset(m_AnimSyncOffsetTime.ToFloat());
}
virtual void SetShadingColor(fixed r, fixed g, fixed b, fixed a)
{
m_R = r;
m_G = g;
m_B = b;
UNUSED2(a); // TODO: why is this even an argument?
if (m_Unit)
{
CModelAbstract& model = m_Unit->GetModel();
model.SetShadingColor(CColor(m_R.ToFloat(), m_G.ToFloat(), m_B.ToFloat(), 1.0f));
}
}
virtual void SetVariable(const std::string& name, float value)
{
if (m_Unit)
m_Unit->GetModel().SetEntityVariable(name, value);
}
virtual u32 GetActorSeed() const
{
return m_Seed;
}
virtual void SetActorSeed(u32 seed)
{
if (seed == m_Seed)
return;
m_Seed = seed;
ReloadActor();
}
virtual void RecomputeActorName()
{
CmpPtr cmpValueModificationManager(GetSystemEntity());
std::wstring newActorName;
if (m_IsFoundationActor)
newActorName = cmpValueModificationManager->ApplyModifications(L"VisualActor/FoundationActor", m_BaseActorName, GetEntityId());
else
newActorName = cmpValueModificationManager->ApplyModifications(L"VisualActor/Actor", m_BaseActorName, GetEntityId());
if (newActorName != m_ActorName)
{
ParseActorName(newActorName);
ReloadActor();
}
}
virtual bool HasConstructionPreview() const
{
return m_ConstructionPreview;
}
virtual void Hotload(const VfsPath& name)
{
if (!m_Unit)
return;
if (!name.empty() && name != m_ActorName)
return;
ReloadActor();
}
private:
// Replace {phenotype} with the correct value in m_ActorName
void ParseActorName(std::wstring base);
/// Helper function shared by component init and actor reloading
void InitModel();
/// Helper method; initializes the model selection shape descriptor from XML. Factored out for readability of @ref Init.
void InitSelectionShapeDescriptor(const CParamNode& paramNode);
// ReloadActor is used when the actor or seed changes.
void ReloadActor();
// ReloadUnitAnimation is used for a minimal reloading upon deserialization, when the actor and seed are identical.
// It is also used by ReloadActor.
void ReloadUnitAnimation();
};
REGISTER_COMPONENT_TYPE(VisualActor)
// ------------------------------------------------------------------------------------------------------------------
void CCmpVisualActor::ParseActorName(std::wstring base)
{
CmpPtr cmpIdentity(GetEntityHandle());
const std::wstring pattern = L"{phenotype}";
if (cmpIdentity)
{
size_t pos = base.find(pattern);
while (pos != std::string::npos)
{
base.replace(pos, pattern.size(), cmpIdentity->GetPhenotype());
pos = base.find(pattern, pos + pattern.size());
}
}
m_ActorName = base;
}
void CCmpVisualActor::InitModel()
{
if (!GetSimContext().HasUnitManager())
return;
std::wstring actorName = m_ActorName;
if (actorName.find(L".xml") == std::wstring::npos)
actorName += L".xml";
m_Unit = GetSimContext().GetUnitManager().CreateUnit(actorName, GetActorSeed());
if (!m_Unit)
return;
CModelAbstract& model = m_Unit->GetModel();
if (model.ToCModel())
{
u32 modelFlags = 0;
if (m_SilhouetteDisplay)
modelFlags |= MODELFLAG_SILHOUETTE_DISPLAY;
if (m_SilhouetteOccluder)
modelFlags |= MODELFLAG_SILHOUETTE_OCCLUDER;
CmpPtr cmpVisibility(GetEntityHandle());
if (cmpVisibility && cmpVisibility->GetAlwaysVisible())
modelFlags |= MODELFLAG_IGNORE_LOS;
model.ToCModel()->AddFlagsRec(modelFlags);
}
if (m_DisableShadows)
{
if (model.ToCModel())
model.ToCModel()->RemoveShadowsRec();
else if (model.ToCModelDecal())
model.ToCModelDecal()->RemoveShadows();
}
m_Unit->SetID(GetEntityId());
bool floating = m_Unit->GetObject().m_Base->m_Properties.m_FloatOnWater;
CmpPtr cmpPosition(GetEntityHandle());
if (cmpPosition)
cmpPosition->SetActorFloating(floating);
if (!m_ModelTag.valid())
{
CmpPtr cmpModelRenderer(GetSystemEntity());
if (cmpModelRenderer)
{
// TODO: this should account for all possible props, animations, etc,
// else we might accidentally cull the unit when it should be visible
CBoundingBoxAligned bounds = m_Unit->GetModel().GetWorldBoundsRec();
CBoundingSphere boundSphere = CBoundingSphere::FromSweptBox(bounds);
int flags = 0;
if (m_IsActorOnly)
flags |= ICmpUnitRenderer::ACTOR_ONLY;
if (m_VisibleInAtlasOnly)
flags |= ICmpUnitRenderer::VISIBLE_IN_ATLAS_ONLY;
m_ModelTag = cmpModelRenderer->AddUnit(GetEntityHandle(), m_Unit, boundSphere, flags);
}
}
// the model is now responsible for cleaning up the descriptor
if (m_ShapeDescriptor != nullptr)
m_Unit->GetModel().SetCustomSelectionShape(m_ShapeDescriptor);
}
void CCmpVisualActor::InitSelectionShapeDescriptor(const CParamNode& paramNode)
{
// by default, we don't need a custom selection shape and we can just keep the default behaviour
m_ShapeDescriptor = nullptr;
const CParamNode& shapeNode = paramNode.GetChild("SelectionShape");
if (shapeNode.IsOk())
{
if (shapeNode.GetChild("Bounds").IsOk())
{
// default; no need to take action
}
else if (shapeNode.GetChild("Footprint").IsOk())
{
CmpPtr cmpFootprint(GetEntityHandle());
if (cmpFootprint)
{
ICmpFootprint::EShape fpShape; // fp stands for "footprint"
entity_pos_t fpSize0, fpSize1, fpHeight; // fp stands for "footprint"
cmpFootprint->GetShape(fpShape, fpSize0, fpSize1, fpHeight);
float size0 = fpSize0.ToFloat();
float size1 = fpSize1.ToFloat();
// TODO: we should properly distinguish between CIRCLE and SQUARE footprint shapes here, but since cylinders
// aren't implemented yet and are almost indistinguishable from boxes for small enough sizes anyway,
// we'll just use boxes for either case. However, for circular footprints the size0 and size1 values both
// represent the radius, so we do have to adjust them to match the size1 and size0's of square footprints
// (which represent the full width and depth).
if (fpShape == ICmpFootprint::CIRCLE)
{
size0 *= 2;
size1 *= 2;
}
m_ShapeDescriptor = new CModelAbstract::CustomSelectionShape;
m_ShapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
m_ShapeDescriptor->m_Size0 = size0;
m_ShapeDescriptor->m_Size1 = size1;
m_ShapeDescriptor->m_Height = fpHeight.ToFloat();
}
else
{
LOGERROR("[VisualActor] Cannot apply footprint-based SelectionShape; Footprint component not initialized.");
}
}
else if (shapeNode.GetChild("Box").IsOk())
{
// TODO: we might need to support the ability to specify a different box center in the future
m_ShapeDescriptor = new CModelAbstract::CustomSelectionShape;
m_ShapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
m_ShapeDescriptor->m_Size0 = shapeNode.GetChild("Box").GetChild("@width").ToFixed().ToFloat();
m_ShapeDescriptor->m_Size1 = shapeNode.GetChild("Box").GetChild("@depth").ToFixed().ToFloat();
m_ShapeDescriptor->m_Height = shapeNode.GetChild("Box").GetChild("@height").ToFixed().ToFloat();
}
else if (shapeNode.GetChild("Cylinder").IsOk())
{
LOGWARNING("[VisualActor] TODO: Cylinder selection shapes are not yet implemented; defaulting to recursive bounding boxes");
}
else
{
// shouldn't happen by virtue of validation against schema
LOGERROR("[VisualActor] No selection shape specified");
}
}
}
void CCmpVisualActor::ReloadActor()
{
if (!m_Unit)
return;
// Save some data from the old unit
CColor shading = m_Unit->GetModel().GetShadingColor();
player_id_t playerID = m_Unit->GetModel().GetPlayerID();
// Replace with the new unit
GetSimContext().GetUnitManager().DeleteUnit(m_Unit);
// HACK: selection shape needs template data, but rather than storing all that data
// in the component, we load the template here and pass it into a helper function
CmpPtr cmpTemplateManager(GetSystemEntity());
const CParamNode* node = cmpTemplateManager->LoadLatestTemplate(GetEntityId());
ENSURE(node && node->GetChild("VisualActor").IsOk());
InitSelectionShapeDescriptor(node->GetChild("VisualActor"));
InitModel();
if (!m_Unit)
{
if (m_ModelTag.valid())
{
CmpPtr cmpModelRenderer(GetSystemEntity());
if (cmpModelRenderer)
cmpModelRenderer->RemoveUnit(m_ModelTag);
m_ModelTag = ICmpUnitRenderer::tag_t{};
}
return;
}
ReloadUnitAnimation();
m_Unit->GetModel().SetShadingColor(shading);
m_Unit->GetModel().SetPlayerID(playerID);
if (m_ModelTag.valid())
{
CmpPtr cmpModelRenderer(GetSystemEntity());
CBoundingBoxAligned bounds = m_Unit->GetModel().GetWorldBoundsRec();
CBoundingSphere boundSphere = CBoundingSphere::FromSweptBox(bounds);
cmpModelRenderer->UpdateUnit(m_ModelTag, m_Unit, boundSphere);
}
}
void CCmpVisualActor::ReloadUnitAnimation()
{
if (!m_Unit)
return;
m_Unit->SetEntitySelection(m_VariantSelections);
if (!m_Unit->GetAnimation())
return;
m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
// We'll lose the exact synchronisation but we should at least make sure it's going at the correct rate
if (!m_AnimSyncRepeatTime.IsZero())
m_Unit->GetAnimation()->SetAnimationSyncRepeat(m_AnimSyncRepeatTime.ToFloat());
if (!m_AnimSyncOffsetTime.IsZero())
m_Unit->GetAnimation()->SetAnimationSyncOffset(m_AnimSyncOffsetTime.ToFloat());
}
Index: ps/trunk/source/tools/atlas/GameInterface/Handlers/ElevationHandlers.cpp
===================================================================
--- ps/trunk/source/tools/atlas/GameInterface/Handlers/ElevationHandlers.cpp (revision 26269)
+++ ps/trunk/source/tools/atlas/GameInterface/Handlers/ElevationHandlers.cpp (revision 26270)
@@ -1,454 +1,459 @@
-/* Copyright (C) 2019 Wildfire Games.
+/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "MessageHandler.h"
#include "../CommandProc.h"
+#include "graphics/RenderableObject.h"
#include "graphics/Terrain.h"
+#include "graphics/UnitManager.h"
#include "ps/CStr.h"
#include "ps/Game.h"
#include "ps/World.h"
#include "maths/MathUtil.h"
-#include "graphics/RenderableObject.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpTerrain.h"
#include "../Brushes.h"
#include "../DeltaArray.h"
namespace AtlasMessage {
class TerrainArray : public DeltaArray2D
{
public:
void Init()
{
m_Heightmap = g_Game->GetWorld()->GetTerrain()->GetHeightMap();
m_VertsPerSide = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
}
void RaiseVertex(ssize_t x, ssize_t y, int amount)
{
// Ignore out-of-bounds vertices
if (size_t(x) >= size_t(m_VertsPerSide) || size_t(y) >= size_t(m_VertsPerSide))
return;
set(x, y, static_cast(Clamp(get(x,y) + amount, 0, 65535)));
}
void MoveVertexTowards(ssize_t x, ssize_t y, int target, int amount)
{
if (size_t(x) >= size_t(m_VertsPerSide) || size_t(y) >= size_t(m_VertsPerSide))
return;
int h = get(x,y);
if (h < target)
h = std::min(target, h + amount);
else if (h > target)
h = std::max(target, h - amount);
else
return;
set(x, y, static_cast(Clamp(h, 0, 65535)));
}
void SetVertex(ssize_t x, ssize_t y, u16 value)
{
if (size_t(x) >= size_t(m_VertsPerSide) || size_t(y) >= size_t(m_VertsPerSide))
return;
set(x,y, value);
}
u16 GetVertex(ssize_t x, ssize_t y)
{
return get(Clamp(x, 0, m_VertsPerSide - 1), Clamp(y, 0, m_VertsPerSide - 1));
}
protected:
u16 getOld(ssize_t x, ssize_t y)
{
return m_Heightmap[y*m_VertsPerSide + x];
}
void setNew(ssize_t x, ssize_t y, const u16& val)
{
m_Heightmap[y*m_VertsPerSide + x] = val;
}
u16* m_Heightmap;
ssize_t m_VertsPerSide;
};
//////////////////////////////////////////////////////////////////////////
BEGIN_COMMAND(AlterElevation)
{
TerrainArray m_TerrainDelta;
ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
cAlterElevation()
{
m_TerrainDelta.Init();
}
void MakeDirty()
{
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
+ g_Game->GetWorld()->GetUnitManager().MakeTerrainDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (cmpTerrain)
cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
}
void Do()
{
int amount = (int)msg->amount;
// If the framerate is very high, 'amount' is often very
// small (even zero) so the integer truncation is significant
static float roundingError = 0.0;
roundingError += msg->amount - (float)amount;
if (roundingError >= 1.f)
{
amount += (int)roundingError;
roundingError -= (float)(int)roundingError;
}
static CVector3D previousPosition;
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace(previousPosition);
previousPosition = g_CurrentBrush.m_Centre;
ssize_t x0, y0;
g_CurrentBrush.GetBottomLeft(x0, y0);
for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
{
for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
{
// TODO: proper variable raise amount (store floats in terrain delta array?)
float b = g_CurrentBrush.Get(dx, dy);
if (b)
m_TerrainDelta.RaiseVertex(x0+dx, y0+dy, (int)(amount*b));
}
}
m_i0 = x0 - 1;
m_j0 = y0 - 1;
m_i1 = x0 + g_CurrentBrush.m_W;
m_j1 = y0 + g_CurrentBrush.m_H;
MakeDirty();
}
void Undo()
{
m_TerrainDelta.Undo();
MakeDirty();
}
void Redo()
{
m_TerrainDelta.Redo();
MakeDirty();
}
void MergeIntoPrevious(cAlterElevation* prev)
{
prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
prev->m_i0 = std::min(prev->m_i0, m_i0);
prev->m_j0 = std::min(prev->m_j0, m_j0);
prev->m_i1 = std::max(prev->m_i1, m_i1);
prev->m_j1 = std::max(prev->m_j1, m_j1);
}
};
END_COMMAND(AlterElevation)
//////////////////////////////////////////////////////////////////////////
BEGIN_COMMAND(SmoothElevation)
{
TerrainArray m_TerrainDelta;
ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
cSmoothElevation()
{
m_TerrainDelta.Init();
}
void MakeDirty()
{
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
+ g_Game->GetWorld()->GetUnitManager().MakeTerrainDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (cmpTerrain)
cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
}
void Do()
{
int amount = (int)msg->amount;
// If the framerate is very high, 'amount' is often very
// small (even zero) so the integer truncation is significant
static float roundingError = 0.0;
roundingError += msg->amount - (float)amount;
if (roundingError >= 1.f)
{
amount += (int)roundingError;
roundingError -= (float)(int)roundingError;
}
static CVector3D previousPosition;
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace(previousPosition);
previousPosition = g_CurrentBrush.m_Centre;
ssize_t x0, y0;
g_CurrentBrush.GetBottomLeft(x0, y0);
if (g_CurrentBrush.m_H > 2)
{
std::vector terrainDeltas;
ssize_t num = (g_CurrentBrush.m_H - 2) * (g_CurrentBrush.m_W - 2);
terrainDeltas.resize(num);
// For each vertex, compute the average of the 9 adjacent vertices
for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
{
for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
{
float delta = m_TerrainDelta.GetVertex(x0+dx, y0+dy) / 9.0f;
ssize_t x1_min = std::max((ssize_t)1, dx - 1);
ssize_t x1_max = std::min(dx + 1, g_CurrentBrush.m_W - 2);
ssize_t y1_min = std::max((ssize_t)1, dy - 1);
ssize_t y1_max = std::min(dy + 1, g_CurrentBrush.m_H - 2);
for (ssize_t yy = y1_min; yy <= y1_max; ++yy)
{
for (ssize_t xx = x1_min; xx <= x1_max; ++xx)
{
ssize_t index = (yy-1)*(g_CurrentBrush.m_W-2) + (xx-1);
terrainDeltas[index] += delta;
}
}
}
}
// Move each vertex towards the computed average of its neighbours
for (ssize_t dy = 1; dy < g_CurrentBrush.m_H - 1; ++dy)
{
for (ssize_t dx = 1; dx < g_CurrentBrush.m_W - 1; ++dx)
{
ssize_t index = (dy-1)*(g_CurrentBrush.m_W-2) + (dx-1);
float b = g_CurrentBrush.Get(dx, dy);
if (b)
m_TerrainDelta.MoveVertexTowards(x0+dx, y0+dy, (int)terrainDeltas[index], (int)(amount*b));
}
}
}
m_i0 = x0;
m_j0 = y0;
m_i1 = x0 + g_CurrentBrush.m_W - 1;
m_j1 = y0 + g_CurrentBrush.m_H - 1;
MakeDirty();
}
void Undo()
{
m_TerrainDelta.Undo();
MakeDirty();
}
void Redo()
{
m_TerrainDelta.Redo();
MakeDirty();
}
void MergeIntoPrevious(cSmoothElevation* prev)
{
prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
prev->m_i0 = std::min(prev->m_i0, m_i0);
prev->m_j0 = std::min(prev->m_j0, m_j0);
prev->m_i1 = std::max(prev->m_i1, m_i1);
prev->m_j1 = std::max(prev->m_j1, m_j1);
}
};
END_COMMAND(SmoothElevation)
//////////////////////////////////////////////////////////////////////////
BEGIN_COMMAND(FlattenElevation)
{
TerrainArray m_TerrainDelta;
ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
cFlattenElevation()
{
m_TerrainDelta.Init();
}
void MakeDirty()
{
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
+ g_Game->GetWorld()->GetUnitManager().MakeTerrainDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (cmpTerrain)
cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
}
void Do()
{
int amount = (int)msg->amount;
static CVector3D previousPosition;
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace(previousPosition);
previousPosition = g_CurrentBrush.m_Centre;
ssize_t xc, yc;
g_CurrentBrush.GetCentre(xc, yc);
u16 height = m_TerrainDelta.GetVertex(xc, yc);
ssize_t x0, y0;
g_CurrentBrush.GetBottomLeft(x0, y0);
for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
{
for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
{
float b = g_CurrentBrush.Get(dx, dy);
if (b)
m_TerrainDelta.MoveVertexTowards(x0+dx, y0+dy, height, 1 + (int)(b*amount));
}
}
m_i0 = x0 - 1;
m_j0 = y0 - 1;
m_i1 = x0 + g_CurrentBrush.m_W;
m_j1 = y0 + g_CurrentBrush.m_H;
MakeDirty();
}
void Undo()
{
m_TerrainDelta.Undo();
MakeDirty();
}
void Redo()
{
m_TerrainDelta.Redo();
MakeDirty();
}
void MergeIntoPrevious(cFlattenElevation* prev)
{
prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
prev->m_i0 = std::min(prev->m_i0, m_i0);
prev->m_j0 = std::min(prev->m_j0, m_j0);
prev->m_i1 = std::max(prev->m_i1, m_i1);
prev->m_j1 = std::max(prev->m_j1, m_j1);
}
};
END_COMMAND(FlattenElevation)
BEGIN_COMMAND(PikeElevation)
{
TerrainArray m_TerrainDelta;
ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
cPikeElevation()
{
m_TerrainDelta.Init();
}
void MakeDirty()
{
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
+ g_Game->GetWorld()->GetUnitManager().MakeTerrainDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (cmpTerrain)
cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
}
void Do()
{
int amount = (int)msg->amount;
// If the framerate is very high, 'amount' is often very
// small (even zero) so the integer truncation is significant
static float roundingError = 0.0;
roundingError += msg->amount - (float)amount;
if (roundingError >= 1.f)
{
amount += (int)roundingError;
roundingError -= (float)(int)roundingError;
}
static CVector3D previousPosition;
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace(previousPosition);
previousPosition = g_CurrentBrush.m_Centre;
ssize_t x0, y0;
g_CurrentBrush.GetBottomLeft(x0, y0);
float h = ((float) g_CurrentBrush.m_H - 1) / 2.f;
for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
{
for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
{
float b = g_CurrentBrush.Get(dx, dy);
if (b)
{
float x = (float)dx - ((float)g_CurrentBrush.m_H - 1) / 2.f;
float y = (float)dy - ((float)g_CurrentBrush.m_W - 1) / 2.f;
float distance = Clamp(1 - static_cast(sqrt(x * x + y * y)) / h, 0.01f, 1.0f);
distance *= distance;
m_TerrainDelta.RaiseVertex(x0 + dx, y0 + dy, (int)(amount * distance));
}
}
}
m_i0 = x0 - 1;
m_j0 = y0 - 1;
m_i1 = x0 + g_CurrentBrush.m_W;
m_j1 = y0 + g_CurrentBrush.m_H;
MakeDirty();
}
void Undo()
{
m_TerrainDelta.Undo();
MakeDirty();
}
void Redo()
{
m_TerrainDelta.Redo();
MakeDirty();
}
void MergeIntoPrevious(cPikeElevation* prev)
{
prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
prev->m_i0 = std::min(prev->m_i0, m_i0);
prev->m_j0 = std::min(prev->m_j0, m_j0);
prev->m_i1 = std::max(prev->m_i1, m_i1);
prev->m_j1 = std::max(prev->m_j1, m_j1);
}
};
END_COMMAND(PikeElevation)
}
Index: ps/trunk/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp
===================================================================
--- ps/trunk/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp (revision 26269)
+++ ps/trunk/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp (revision 26270)
@@ -1,581 +1,575 @@
/* Copyright (C) 2022 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "MessageHandler.h"
#include "../CommandProc.h"
#include "graphics/Patch.h"
#include "graphics/TerrainTextureManager.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/Terrain.h"
#include "graphics/TextureManager.h"
#include "ps/Game.h"
#include "ps/World.h"
#include "lib/tex/tex.h"
#include "ps/Filesystem.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpPathfinder.h"
#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/helpers/Grid.h"
#include "../Brushes.h"
#include "../DeltaArray.h"
#include "../View.h"
#include
namespace AtlasMessage
{
namespace
{
sTerrainTexturePreview MakeEmptyTerrainTexturePreview()
{
sTerrainTexturePreview preview{};
preview.name = std::wstring();
preview.loaded = false;
preview.imageHeight = 0;
preview.imageWidth = 0;
preview.imageData = {};
return preview;
}
bool CompareTerrain(const sTerrainTexturePreview& a, const sTerrainTexturePreview& b)
{
return (wcscmp(a.name.c_str(), b.name.c_str()) < 0);
}
sTerrainTexturePreview GetPreview(CTerrainTextureEntry* tex, size_t width, size_t height)
{
sTerrainTexturePreview preview;
preview.name = tex->GetTag().FromUTF8();
const size_t previewBPP = 3;
std::vector buffer(width * height * previewBPP);
// It's not good to shrink the entire texture to fit the small preview
// window, since it's the fine details in the texture that are
// interesting; so just go down one mipmap level, then crop a chunk
// out of the middle.
std::shared_ptr fileData;
size_t fileSize;
Tex texture;
const bool canUsePreview =
!tex->GetDiffuseTexturePath().empty() &&
g_VFS->LoadFile(tex->GetDiffuseTexturePath(), fileData, fileSize) == INFO::OK &&
texture.decode(fileData, fileSize) == INFO::OK &&
// Check that we can fit the texture into the preview size before any transform.
texture.m_Width >= width && texture.m_Height >= height &&
// Transform to a single format that we can process.
texture.transform_to((texture.m_Flags | TEX_MIPMAPS) & ~(TEX_DXT | TEX_GREY | TEX_BGR)) == INFO::OK &&
(texture.m_Bpp == 24 || texture.m_Bpp == 32);
if (canUsePreview)
{
size_t level = 0;
while ((texture.m_Width >> (level + 1)) >= width && (texture.m_Height >> (level + 1)) >= height)
++level;
// Extract the middle section (as a representative preview),
// and copy into buffer.
u8* data = texture.GetMipLevelData(level);
ENSURE(data);
const size_t levelWidth = texture.m_Width >> level;
const size_t levelHeight = texture.m_Height >> level;
const size_t dataShiftX = (levelWidth - width) / 2;
const size_t dataShiftY = (levelHeight - height) / 2;
for (size_t y = 0; y < height; ++y)
for (size_t x = 0; x < width; ++x)
{
const size_t bufferOffset = (y * width + x) * previewBPP;
const size_t dataOffset = ((y + dataShiftY) * levelWidth + x + dataShiftX) * texture.m_Bpp / 8;
buffer[bufferOffset + 0] = data[dataOffset + 0];
buffer[bufferOffset + 1] = data[dataOffset + 1];
buffer[bufferOffset + 2] = data[dataOffset + 2];
}
preview.loaded = true;
}
else
{
// Too small to preview. Just use a flat color instead.
const u32 baseColor = tex->GetBaseColor();
for (size_t i = 0; i < width * height; ++i)
{
buffer[i * previewBPP + 0] = (baseColor >> 16) & 0xff;
buffer[i * previewBPP + 1] = (baseColor >> 8) & 0xff;
buffer[i * previewBPP + 2] = (baseColor >> 0) & 0xff;
}
preview.loaded = tex->GetTexture()->IsLoaded();
}
preview.imageWidth = width;
preview.imageHeight = height;
preview.imageData = buffer;
return preview;
}
} // anonymous namespace
QUERYHANDLER(GetTerrainGroups)
{
const CTerrainTextureManager::TerrainGroupMap &groups = g_TexMan.GetGroups();
std::vector groupNames;
for (CTerrainTextureManager::TerrainGroupMap::const_iterator it = groups.begin(); it != groups.end(); ++it)
groupNames.push_back(it->first.FromUTF8());
msg->groupNames = groupNames;
}
QUERYHANDLER(GetTerrainGroupTextures)
{
std::vector names;
CTerrainGroup* group = g_TexMan.FindGroup(CStrW(*msg->groupName).ToUTF8());
if (group)
{
for (std::vector::const_iterator it = group->GetTerrains().begin(); it != group->GetTerrains().end(); ++it)
names.emplace_back((*it)->GetTag().FromUTF8());
}
std::sort(names.begin(), names.end());
msg->names = names;
}
QUERYHANDLER(GetTerrainGroupPreviews)
{
std::vector previews;
CTerrainGroup* group = g_TexMan.FindGroup(CStrW(*msg->groupName).ToUTF8());
for (std::vector::const_iterator it = group->GetTerrains().begin(); it != group->GetTerrains().end(); ++it)
{
previews.push_back(GetPreview(*it, msg->imageWidth, msg->imageHeight));
}
// Sort the list alphabetically by name
std::sort(previews.begin(), previews.end(), CompareTerrain);
msg->previews = previews;
}
QUERYHANDLER(GetTerrainPassabilityClasses)
{
CmpPtr cmpPathfinder(*AtlasView::GetView_Game()->GetSimulation2(), SYSTEM_ENTITY);
if (cmpPathfinder)
{
std::map nonPathfindingClasses, pathfindingClasses;
cmpPathfinder->GetPassabilityClasses(nonPathfindingClasses, pathfindingClasses);
std::vector classNames;
for (std::map::iterator it = nonPathfindingClasses.begin(); it != nonPathfindingClasses.end(); ++it)
classNames.push_back(CStr(it->first).FromUTF8());
msg->classNames = classNames;
}
}
QUERYHANDLER(GetTerrainTexture)
{
ssize_t x, y;
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
g_CurrentBrush.GetCentre(x, y);
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
CMiniPatch* tile = terrain->GetTile(x, y);
if (tile)
{
CTerrainTextureEntry* tex = tile->GetTextureEntry();
msg->texture = tex->GetTag().FromUTF8();
}
else
{
msg->texture = std::wstring();
}
}
QUERYHANDLER(GetTerrainTexturePreview)
{
CTerrainTextureEntry* tex = g_TexMan.FindTexture(CStrW(*msg->name).ToUTF8());
if (tex)
msg->preview = GetPreview(tex, msg->imageWidth, msg->imageHeight);
else
msg->preview = MakeEmptyTerrainTexturePreview();
}
//////////////////////////////////////////////////////////////////////////
namespace
{
struct TerrainTile
{
TerrainTile(CTerrainTextureEntry* t, ssize_t p) : tex(t), priority(p) {}
CTerrainTextureEntry* tex;
ssize_t priority;
};
class TerrainArray : public DeltaArray2D
{
public:
void Init()
{
m_Terrain = g_Game->GetWorld()->GetTerrain();
m_VertsPerSide = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
}
void UpdatePriority(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priorityScale, ssize_t& priority)
{
CMiniPatch* tile = m_Terrain->GetTile(x, y);
if (!tile)
return; // tile was out-of-bounds
// If this tile matches the current texture, we just want to match its
// priority; otherwise we want to exceed its priority
if (tile->GetTextureEntry() == tex)
priority = std::max(priority, tile->GetPriority()*priorityScale);
else
priority = std::max(priority, tile->GetPriority()*priorityScale + 1);
}
CTerrainTextureEntry* GetTexEntry(ssize_t x, ssize_t y)
{
if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
return NULL;
return get(x, y).tex;
}
ssize_t GetPriority(ssize_t x, ssize_t y)
{
if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
return 0;
return get(x, y).priority;
}
void PaintTile(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priority)
{
// Ignore out-of-bounds tiles
if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
return;
set(x,y, TerrainTile(tex, priority));
}
ssize_t GetTilesPerSide()
{
return m_VertsPerSide-1;
}
protected:
TerrainTile getOld(ssize_t x, ssize_t y)
{
CMiniPatch* mp = m_Terrain->GetTile(x, y);
ENSURE(mp);
return TerrainTile(mp->Tex, mp->Priority);
}
void setNew(ssize_t x, ssize_t y, const TerrainTile& val)
{
CMiniPatch* mp = m_Terrain->GetTile(x, y);
ENSURE(mp);
mp->Tex = val.tex;
mp->Priority = val.priority;
}
CTerrain* m_Terrain;
ssize_t m_VertsPerSide;
};
}
BEGIN_COMMAND(PaintTerrain)
{
TerrainArray m_TerrainDelta;
ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
cPaintTerrain()
{
m_TerrainDelta.Init();
}
void MakeDirty()
{
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_INDICES);
}
void Do()
{
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
ssize_t x0, y0;
g_CurrentBrush.GetBottomLeft(x0, y0);
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(CStrW(*msg->texture).ToUTF8());
if (! texentry)
{
debug_warn(L"Can't find texentry"); // TODO: nicer error handling
return;
}
// Priority system: If the new tile should have a high priority,
// set it to one plus the maximum priority of all surrounding tiles
// that aren't included in the brush (so that it's definitely the highest).
// Similar for low priority.
ssize_t priorityScale = (msg->priority == ePaintTerrainPriority::HIGH ? +1 : -1);
ssize_t priority = 0;
for (ssize_t dy = -1; dy < g_CurrentBrush.m_H+1; ++dy)
{
for (ssize_t dx = -1; dx < g_CurrentBrush.m_W+1; ++dx)
{
if (!(g_CurrentBrush.Get(dx, dy) > 0.5f)) // ignore tiles that will be painted over
m_TerrainDelta.UpdatePriority(x0+dx, y0+dy, texentry, priorityScale, priority);
}
}
for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
{
for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
{
if (g_CurrentBrush.Get(dx, dy) > 0.5f) // TODO: proper solid brushes
m_TerrainDelta.PaintTile(x0+dx, y0+dy, texentry, priority*priorityScale);
}
}
m_i0 = x0 - 1;
m_j0 = y0 - 1;
m_i1 = x0 + g_CurrentBrush.m_W + 1;
m_j1 = y0 + g_CurrentBrush.m_H + 1;
MakeDirty();
}
void Undo()
{
m_TerrainDelta.Undo();
MakeDirty();
}
void Redo()
{
m_TerrainDelta.Redo();
MakeDirty();
}
void MergeIntoPrevious(cPaintTerrain* prev)
{
prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
prev->m_i0 = std::min(prev->m_i0, m_i0);
prev->m_j0 = std::min(prev->m_j0, m_j0);
prev->m_i1 = std::max(prev->m_i1, m_i1);
prev->m_j1 = std::max(prev->m_j1, m_j1);
}
};
END_COMMAND(PaintTerrain)
//////////////////////////////////////////////////////////////////////////
BEGIN_COMMAND(ReplaceTerrain)
{
TerrainArray m_TerrainDelta;
ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
cReplaceTerrain()
{
m_TerrainDelta.Init();
}
void MakeDirty()
{
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_INDICES);
- CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
- if (cmpTerrain)
- cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
}
void Do()
{
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
ssize_t x0, y0;
g_CurrentBrush.GetBottomLeft(x0, y0);
m_i0 = m_i1 = x0;
m_j0 = m_j1 = y0;
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(CStrW(*msg->texture).ToUTF8());
if (! texentry)
{
debug_warn(L"Can't find texentry"); // TODO: nicer error handling
return;
}
CTerrainTextureEntry* replacedTex = m_TerrainDelta.GetTexEntry(x0, y0);
// Don't bother if we're not making a change
if (texentry == replacedTex)
{
return;
}
ssize_t tiles = m_TerrainDelta.GetTilesPerSide();
for (ssize_t j = 0; j < tiles; ++j)
{
for (ssize_t i = 0; i < tiles; ++i)
{
if (m_TerrainDelta.GetTexEntry(i, j) == replacedTex)
{
m_i0 = std::min(m_i0, i-1);
m_j0 = std::min(m_j0, j-1);
m_i1 = std::max(m_i1, i+2);
m_j1 = std::max(m_j1, j+2);
m_TerrainDelta.PaintTile(i, j, texentry, m_TerrainDelta.GetPriority(i, j));
}
}
}
MakeDirty();
}
void Undo()
{
m_TerrainDelta.Undo();
MakeDirty();
}
void Redo()
{
m_TerrainDelta.Redo();
MakeDirty();
}
};
END_COMMAND(ReplaceTerrain)
//////////////////////////////////////////////////////////////////////////
BEGIN_COMMAND(FillTerrain)
{
TerrainArray m_TerrainDelta;
ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
cFillTerrain()
{
m_TerrainDelta.Init();
}
void MakeDirty()
{
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_INDICES);
- CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
- if (cmpTerrain)
- cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
}
void Do()
{
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
ssize_t x0, y0;
g_CurrentBrush.GetBottomLeft(x0, y0);
m_i0 = m_i1 = x0;
m_j0 = m_j1 = y0;
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(CStrW(*msg->texture).ToUTF8());
if (! texentry)
{
debug_warn(L"Can't find texentry"); // TODO: nicer error handling
return;
}
CTerrainTextureEntry* replacedTex = m_TerrainDelta.GetTexEntry(x0, y0);
// Don't bother if we're not making a change
if (texentry == replacedTex)
{
return;
}
ssize_t tiles = m_TerrainDelta.GetTilesPerSide();
// Simple 4-way flood fill algorithm using queue and a grid to keep track of visited tiles,
// almost as fast as loop for filling whole map, much faster for small patches
SparseGrid visited(tiles, tiles);
std::queue > queue;
// Initial tile
queue.push(std::make_pair((u16)x0, (u16)y0));
visited.set(x0, y0, true);
while(!queue.empty())
{
// Check front of queue
std::pair t = queue.front();
queue.pop();
u16 i = t.first;
u16 j = t.second;
if (m_TerrainDelta.GetTexEntry(i, j) == replacedTex)
{
// Found a tile to replace: adjust bounds and paint it
m_i0 = std::min(m_i0, (ssize_t)i-1);
m_j0 = std::min(m_j0, (ssize_t)j-1);
m_i1 = std::max(m_i1, (ssize_t)i+2);
m_j1 = std::max(m_j1, (ssize_t)j+2);
m_TerrainDelta.PaintTile(i, j, texentry, m_TerrainDelta.GetPriority(i, j));
// Visit 4 adjacent tiles (could visit 8 if we want to count diagonal adjacency)
if (i > 0 && !visited.get(i-1, j))
{
visited.set(i-1, j, true);
queue.push(std::make_pair(i-1, j));
}
if (i < (tiles-1) && !visited.get(i+1, j))
{
visited.set(i+1, j, true);
queue.push(std::make_pair(i+1, j));
}
if (j > 0 && !visited.get(i, j-1))
{
visited.set(i, j-1, true);
queue.push(std::make_pair(i, j-1));
}
if (j < (tiles-1) && !visited.get(i, j+1))
{
visited.set(i, j+1, true);
queue.push(std::make_pair(i, j+1));
}
}
}
MakeDirty();
}
void Undo()
{
m_TerrainDelta.Undo();
MakeDirty();
}
void Redo()
{
m_TerrainDelta.Redo();
MakeDirty();
}
};
END_COMMAND(FillTerrain)
} // namespace AtlasMessage