Changeset View
Changeset View
Standalone View
Standalone View
source/simulation2/components/CCmpUnitMotion_System.cpp
Show All 29 Lines | ||||||||||||
void CCmpUnitMotionManager::Register(CCmpUnitMotion* component, entity_id_t ent, bool formationController) | void CCmpUnitMotionManager::Register(CCmpUnitMotion* component, entity_id_t ent, bool formationController) | |||||||||||
{ | { | |||||||||||
MotionState state = { | MotionState state = { | |||||||||||
CmpPtr<ICmpPosition>(GetSimContext(), ent), | CmpPtr<ICmpPosition>(GetSimContext(), ent), | |||||||||||
component, | component, | |||||||||||
CFixedVector2D(), | CFixedVector2D(), | |||||||||||
CFixedVector2D(), | CFixedVector2D(), | |||||||||||
CFixedVector2D(fixed::FromInt(0), fixed::FromInt(0)), | ||||||||||||
fixed::Zero(), | fixed::Zero(), | |||||||||||
fixed::Zero(), | fixed::Zero(), | |||||||||||
false, | false, | |||||||||||
false, | ||||||||||||
false, | ||||||||||||
false | false | |||||||||||
ImarokUnsubmitted Not Done Inline Actions
Imarok: | ||||||||||||
}; | }; | |||||||||||
if (!formationController) | if (!formationController) | |||||||||||
m_Units.insert(ent, state); | m_Units.insert(ent, state); | |||||||||||
else | else | |||||||||||
m_FormationControllers.insert(ent, state); | m_FormationControllers.insert(ent, state); | |||||||||||
} | } | |||||||||||
void CCmpUnitMotionManager::Unregister(entity_id_t ent) | void CCmpUnitMotionManager::Unregister(entity_id_t ent) | |||||||||||
Show All 25 Lines | ||||||||||||
void CCmpUnitMotionManager::MoveFormations(fixed dt) | void CCmpUnitMotionManager::MoveFormations(fixed dt) | |||||||||||
{ | { | |||||||||||
Move(m_FormationControllers, dt); | Move(m_FormationControllers, dt); | |||||||||||
} | } | |||||||||||
void CCmpUnitMotionManager::Move(EntityMap<MotionState>& ents, fixed dt) | void CCmpUnitMotionManager::Move(EntityMap<MotionState>& ents, fixed dt) | |||||||||||
{ | { | |||||||||||
m_MovingUnits.clear(); | PROFILE2("MotionMgr_Move"); | |||||||||||
std::set<std::vector<EntityMap<MotionState>::iterator>*> assigned; | ||||||||||||
Not Done Inline ActionsInclude set ? Does it have to be ordered? Stan: Include set ? Does it have to be ordered? | ||||||||||||
for (EntityMap<MotionState>::iterator it = ents.begin(); it != ents.end(); ++it) | for (EntityMap<MotionState>::iterator it = ents.begin(); it != ents.end(); ++it) | |||||||||||
{ | { | |||||||||||
it->second.cmpUnitMotion->PreMove(it->second); | if (!it->second.cmpPosition->IsInWorld()) | |||||||||||
if (!it->second.needUpdate) | { | |||||||||||
it->second.needUpdate = false; | ||||||||||||
continue; | continue; | |||||||||||
m_MovingUnits.push_back(it); | } | |||||||||||
else | ||||||||||||
it->second.cmpUnitMotion->PreMove(it->second); | ||||||||||||
it->second.initialPos = it->second.cmpPosition->GetPosition2D(); | it->second.initialPos = it->second.cmpPosition->GetPosition2D(); | |||||||||||
it->second.initialAngle = it->second.cmpPosition->GetRotation().Y; | it->second.initialAngle = it->second.cmpPosition->GetRotation().Y; | |||||||||||
it->second.pos = it->second.initialPos; | it->second.pos = it->second.initialPos; | |||||||||||
it->second.angle = it->second.initialAngle; | it->second.angle = it->second.initialAngle; | |||||||||||
ENSURE(it->second.pos.X.ToInt_RoundToZero() / 20 < m_MovingUnits.width() && it->second.pos.Y.ToInt_RoundToZero() / 20 < m_MovingUnits.height()); | ||||||||||||
std::vector<EntityMap<MotionState>::iterator>& subdiv = m_MovingUnits.get(it->second.pos.X.ToInt_RoundToZero() / 20, it->second.pos.Y.ToInt_RoundToZero() / 20); | ||||||||||||
subdiv.emplace_back(it); | ||||||||||||
assigned.emplace(&subdiv); | ||||||||||||
} | } | |||||||||||
for (EntityMap<MotionState>::iterator& it : m_MovingUnits) | for (std::vector<EntityMap<MotionState>::iterator>* vec : assigned) | |||||||||||
{ | for (EntityMap<MotionState>::iterator& it : *vec) | |||||||||||
if (it->second.needUpdate) | ||||||||||||
it->second.cmpUnitMotion->Move(it->second, dt); | it->second.cmpUnitMotion->Move(it->second, dt); | |||||||||||
it->second.cmpUnitMotion->PostMove(it->second, dt); | ||||||||||||
if (&ents == &m_Units) | ||||||||||||
{ | ||||||||||||
PROFILE2("MotionMgr_Pushing"); | ||||||||||||
for (std::vector<EntityMap<MotionState>::iterator>* vec : assigned) | ||||||||||||
{ | ||||||||||||
ENSURE(!vec->empty()); | ||||||||||||
std::vector<EntityMap<MotionState>::iterator>::iterator cit1 = vec->begin(); | ||||||||||||
do | ||||||||||||
{ | ||||||||||||
std::vector<EntityMap<MotionState>::iterator>::iterator cit2 = cit1; | ||||||||||||
while(++cit2 != vec->end()) | ||||||||||||
Push(**cit1, **cit2, dt); | ||||||||||||
} | ||||||||||||
while(++cit1 != vec->end()); | ||||||||||||
} | ||||||||||||
Not Done Inline ActionsCan we resize the vector before pushing in it? Stan: Can we resize the vector before pushing in it? | ||||||||||||
Done Inline Actions? wraitii: ? | ||||||||||||
Not Done Inline ActionsSomething like Stan: Something like
https://www.cplusplus.com/reference/vector/vector/resize/ | ||||||||||||
Done Inline ActionsYou seem to be misunderstanding what "push" does here, it applies the pushing, there's no insertion to speak of. That's done in the "emplace" call above, and I believe I've assumed that it'll settle to a correct value fast enough. wraitii: You seem to be misunderstanding what "push" does here, it applies the pushing, there's no… | ||||||||||||
} | ||||||||||||
{ | ||||||||||||
PROFILE2("MotionMgr_PushAdjust"); | ||||||||||||
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | ||||||||||||
for (std::vector<EntityMap<MotionState>::iterator>* vec : assigned) | ||||||||||||
{ | ||||||||||||
for (EntityMap<MotionState>::iterator& it : *vec) | ||||||||||||
{ | ||||||||||||
if (!it->second.needUpdate) | ||||||||||||
continue; | ||||||||||||
// Prevent pushed units from crossing uncrossable boundaries | ||||||||||||
// (we can assume that normal movement didn't push units into impassable terrain). | ||||||||||||
if ((it->second.push.X != entity_pos_t::Zero() || it->second.push.Y != entity_pos_t::Zero()) && | ||||||||||||
!cmpPathfinder->CheckMovement(it->second.cmpUnitMotion->GetObstructionFilter(), | ||||||||||||
it->second.pos.X, it->second.pos.Y, | ||||||||||||
it->second.pos.X + it->second.push.X, it->second.pos.Y + it->second.push.Y, | ||||||||||||
it->second.cmpUnitMotion->m_Clearance, | ||||||||||||
it->second.cmpUnitMotion->m_PassClass)) | ||||||||||||
{ | ||||||||||||
// Mark them as obstructed - this could possibly be optimised | ||||||||||||
// perhaps it'd make more sense to mark the pushers as blocked. | ||||||||||||
it->second.wasObstructed = true; | ||||||||||||
it->second.wentStraight = false; | ||||||||||||
it->second.push = CFixedVector2D(); | ||||||||||||
} | ||||||||||||
// Only apply pushing if the effect is significant enough. | ||||||||||||
if (it->second.push.CompareLength(entity_pos_t::FromInt(1)/10) > 0) | ||||||||||||
Not Done Inline ActionsConstant somewhere? Freagarach: Constant somewhere? | ||||||||||||
Done Inline ActionsYeah there's a bunch of magic values to constantify in here. wraitii: Yeah there's a bunch of magic values to constantify in here. | ||||||||||||
{ | ||||||||||||
// If there was an attempt at movement, and the pushed movement is in a sufficiently different direction | ||||||||||||
// (measured by an extremely arbitrary dot product) | ||||||||||||
Not Done Inline Actions
Stan: # Can you do FromFloat(0.1) ?
# Shouldn't there be some kind of epsilon?
# constantify… | ||||||||||||
Done Inline Actions
wraitii: 1. Pretty sure that's risky since it could differ based on the compiler - haven't actually… | ||||||||||||
Done Inline Actions
Stan: 2. Forgot `entity_pos_t` is CFixed, thought it was float. Nvm then. | ||||||||||||
// then mark the unit as obstructed still. | ||||||||||||
if (it->second.pos != it->second.initialPos && | ||||||||||||
(it->second.pos - it->second.initialPos).Dot(it->second.pos + it->second.push - it->second.initialPos) < entity_pos_t::FromInt(1)/2) | ||||||||||||
{ | ||||||||||||
it->second.wasObstructed = true; | ||||||||||||
it->second.wentStraight = false; | ||||||||||||
} | } | |||||||||||
else | ||||||||||||
it->second.pos += it->second.push; | ||||||||||||
} | ||||||||||||
it->second.push = CFixedVector2D(); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
} | ||||||||||||
{ | ||||||||||||
PROFILE2("MotionMgr_PostMove"); | ||||||||||||
for (EntityMap<MotionState>::value_type& data : ents) | ||||||||||||
{ | ||||||||||||
if (!data.second.needUpdate) | ||||||||||||
continue; | ||||||||||||
data.second.cmpUnitMotion->PostMove(data.second, dt); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
for (std::vector<EntityMap<MotionState>::iterator>* vec : assigned) | ||||||||||||
vec->clear(); | ||||||||||||
} | ||||||||||||
// TODO: ought to better simulate in-flight pushing, e.g. if units would cross in-between turns. | ||||||||||||
void CCmpUnitMotionManager::Push(EntityMap<MotionState>::value_type& a, EntityMap<MotionState>::value_type& b, fixed dt) | ||||||||||||
{ | ||||||||||||
// The hard problem for pushing is knowing when to actually use the pathfinder to go around unpushable obstacles. | ||||||||||||
// For simplicitly, the current logic separates moving & stopped entities: | ||||||||||||
// moving entities will push moving entities, but not stopped ones, and vice-versa. | ||||||||||||
// this still delivers most of the value of pushing, without a lot of the complexity. | ||||||||||||
int movingPush = a.second.isMoving + b.second.isMoving; | ||||||||||||
Not Done Inline ActionsDon't use std::clamp, it's terrible with msvc, use our version. Stan: Don't use std::clamp, it's terrible with msvc, use our version. | ||||||||||||
if (movingPush == 1) | ||||||||||||
return; | ||||||||||||
// Treat the clearances as a circle - they're defined as squares, so we'll slightly overcompensate the diagonal | ||||||||||||
// (they're also full-width instead of half, so we want to divide by two. sqrt(2)/2 is about 0.71 < 5/7). | ||||||||||||
entity_pos_t combinedClearance = (a.second.cmpUnitMotion->m_Clearance + b.second.cmpUnitMotion->m_Clearance) * 5 / 7; | ||||||||||||
entity_pos_t maxDist = combinedClearance; | ||||||||||||
if (movingPush) | ||||||||||||
maxDist += entity_pos_t::FromInt(1); | ||||||||||||
CFixedVector2D offset = a.second.pos - b.second.pos; | ||||||||||||
if (offset.CompareLength(maxDist) > 0) | ||||||||||||
return; | ||||||||||||
entity_pos_t offsetLength = offset.Length(); | ||||||||||||
// If the offset is small enough that precision would be problematic, pick an arbitrary vector instead. | ||||||||||||
if (offsetLength <= entity_pos_t::Epsilon() * 10) | ||||||||||||
{ | ||||||||||||
// Throw in some 'randomness' so that clumped units unclump more naturally. | ||||||||||||
bool dir = a.first % 2; | ||||||||||||
offset.X = entity_pos_t::FromInt(dir ? 1 : 0); | ||||||||||||
offset.Y = entity_pos_t::FromInt(dir ? 0 : 1); | ||||||||||||
offsetLength = entity_pos_t::FromInt(1); | ||||||||||||
} | ||||||||||||
else | ||||||||||||
{ | ||||||||||||
offset.X = offset.X / offsetLength; | ||||||||||||
offset.Y = offset.Y / offsetLength; | ||||||||||||
} | ||||||||||||
// If the units are moving in opposite direction, check if they might have phased through each other. | ||||||||||||
// If it looks like yes, move them perpendicularily so it looks like they avoid each other. | ||||||||||||
// NB: this isn't very precise, nor will it catch 100% of intersections - it's meant as a cheap improvement. | ||||||||||||
if (movingPush && (a.second.pos - a.second.initialPos).Dot(b.second.pos - b.second.initialPos) < entity_pos_t::Zero()) | ||||||||||||
// Perform some finer checking. | ||||||||||||
if (Geometry::TestRayAASquare(a.second.initialPos - b.second.initialPos, a.second.pos - b.second.initialPos, | ||||||||||||
CFixedVector2D(combinedClearance, combinedClearance)) | ||||||||||||
|| | ||||||||||||
Geometry::TestRayAASquare(a.second.initialPos - b.second.pos, a.second.pos - b.second.pos, | ||||||||||||
CFixedVector2D(combinedClearance, combinedClearance))) | ||||||||||||
{ | ||||||||||||
offset = offset.Perpendicular(); | ||||||||||||
offsetLength = fixed::Zero(); | ||||||||||||
} | ||||||||||||
// The formula expects 'normal' pushing if the two entities edges are touching. | ||||||||||||
entity_pos_t distanceFactor = movingPush ? (maxDist - offsetLength) / (maxDist - combinedClearance) : combinedClearance - offsetLength + entity_pos_t::FromInt(1); | ||||||||||||
distanceFactor = std::clamp(distanceFactor, entity_pos_t::Zero(), entity_pos_t::FromInt(2)); | ||||||||||||
// Mark both as needing an update so they actually get moved. | ||||||||||||
a.second.needUpdate = true; | ||||||||||||
b.second.needUpdate = true; | ||||||||||||
CFixedVector2D pushingDir = offset.Multiply(distanceFactor); | ||||||||||||
// Divide by an arbitrary constant to avoid pushing too much. | ||||||||||||
a.second.push += pushingDir.Multiply(movingPush ? dt : dt / 2); | ||||||||||||
b.second.push -= pushingDir.Multiply(movingPush ? dt : dt / 2); | ||||||||||||
} | } |
Wildfire Games · Phabricator