Make PickEntitiesAtPoint faster yielding 1-3% overall performance improvement. Fixes #2358. Also removes some trailing whitespace.

This was SVN commit r14532.
This commit is contained in:
JoshuaJB 2014-01-07 04:05:10 +00:00
parent 729a795a44
commit 8226d75715
8 changed files with 103 additions and 63 deletions

View File

@ -36,6 +36,8 @@ var placementSupport = new PlacementSupport();
var mouseX = 0;
var mouseY = 0;
var mouseIsOverObject = false;
// Distance to search for a selatable entity in. Bigger numbers are slower.
var SELECTION_SEARCH_RADIUS = 100;
// Number of pixels the mouse can move before the action is considered a drag
var maxDragDelta = 4;
@ -495,7 +497,7 @@ function determineAction(x, y, fromMinimap)
var targets = [];
var target = undefined;
if (!fromMinimap)
targets = Engine.PickEntitiesAtPoint(x, y);
targets = Engine.PickEntitiesAtPoint(x, y, SELECTION_SEARCH_RADIUS);
if (targets.length)
target = targets[0];
@ -1108,7 +1110,7 @@ function handleInputAfterGui(ev)
{
case "mousemotion":
// Highlight the first hovered entity (if any)
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y, SELECTION_SEARCH_RADIUS);
if (ents.length)
g_Selection.setHighlightList([ents[0]]);
else
@ -1162,7 +1164,7 @@ function handleInputAfterGui(ev)
{
case "mousemotion":
// Highlight the first hovered entity (if any)
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y, SELECTION_SEARCH_RADIUS);
if (ents.length)
g_Selection.setHighlightList([ents[0]]);
else
@ -1212,14 +1214,14 @@ function handleInputAfterGui(ev)
return false;
}
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y, SELECTION_SEARCH_RADIUS);
g_Selection.setHighlightList(ents);
return false;
case "mousebuttonup":
if (ev.button == SDL_BUTTON_LEFT)
{
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y, SELECTION_SEARCH_RADIUS);
if (!ents.length)
{
if (!Engine.HotkeyIsPressed("selection.add") && !Engine.HotkeyIsPressed("selection.remove"))

View File

@ -293,7 +293,14 @@
<object size="0 128 100%-18 144" type="text" style="devCommandsText">Restrict camera</object>
<object size="100%-16 128 100% 144" type="checkbox" style="StoneCrossBox" checked="true">
<action on="Press">Engine.GameView_SetConstrainCameraEnabled(this.checked);</action>
<action on="Press">
Engine.GameView_SetConstrainCameraEnabled(this.checked);
// Make selection more durable at the expense of speed if unchecked.
if (this.checked)
SELECTION_SEARCH_RADIUS -= 200;
else
SELECTION_SEARCH_RADIUS += 200;
</action>
</object>
<object size="0 144 100%-18 160" type="text" style="devCommandsText">Reveal map</object>

View File

@ -148,9 +148,9 @@ void PostNetworkCommand(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal cmd)
cmpCommandQueue->PostNetworkCommand(cmd2);
}
std::vector<entity_id_t> PickEntitiesAtPoint(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x, int y)
std::vector<entity_id_t> PickEntitiesAtPoint(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x, int y, int range)
{
return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetPlayerID(), false);
return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetPlayerID(), false, range);
}
std::vector<entity_id_t> PickFriendlyEntitiesInRect(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x0, int y0, int x1, int y1, int player)
@ -804,7 +804,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<void, CScriptVal, &PostNetworkCommand>("PostNetworkCommand");
// Entity picking
scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint");
scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint");
scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect");
scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, &PickFriendlyEntitiesOnScreen>("PickFriendlyEntitiesOnScreen");
scriptInterface.RegisterFunction<std::vector<entity_id_t>, std::string, bool, bool, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities");

View File

@ -97,17 +97,17 @@ static u32 CalcSharedLosMask(std::vector<player_id_t> players)
* Checks whether v is in a parabolic range of (0,0,0)
* The highest point of the paraboloid is (0,range/2,0)
* and the circle of distance 'range' around (0,0,0) on height y=0 is part of the paraboloid
*
*
* Avoids sqrting and overflowing.
*/
static bool InParabolicRange(CFixedVector3D v, fixed range)
static bool InParabolicRange(CFixedVector3D v, fixed range)
{
i32 x = v.X.GetInternalValue(); // abs(x) <= 2^31
i32 z = v.Z.GetInternalValue();
u64 xx = (u64)FIXED_MUL_I64_I32_I32(x, x); // xx <= 2^62
u64 zz = (u64)FIXED_MUL_I64_I32_I32(z, z);
i64 d2 = (xx + zz) >> 1; // d2 <= 2^62 (no overflow)
i32 y = v.Y.GetInternalValue();
i32 c = range.GetInternalValue();
i32 c_2 = c >> 1;
@ -327,7 +327,7 @@ public:
// will get confused when trying to run from enemies
m_LosRevealAll.resize(MAX_LOS_PLAYER_ID+2,false);
m_SharedLosMasks.resize(MAX_LOS_PLAYER_ID+2,0);
m_LosCircular = false;
m_TerrainVerticesPerSide = 0;
@ -601,6 +601,11 @@ public:
debug_warn(L"inconsistent subdivs");
}
SpatialSubdivision* GetSubdivision()
{
return & m_Subdivision;
}
// Reinitialise subdivisions and LOS data, based on entity data
void ResetDerivedData(bool skipLosState)
{
@ -844,7 +849,7 @@ public:
if (!query.enabled)
continue;
CmpPtr<ICmpPosition> cmpSourcePosition(query.source);
CmpPtr<ICmpPosition> cmpSourcePosition(query.source);
if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
continue;
@ -858,9 +863,9 @@ public:
removed.clear();
// Return the 'added' list sorted by distance from the entity
// (Don't bother sorting 'removed' because they might not even have positions or exist any more)
std::set_difference(results.begin(), results.end(), query.lastMatch.begin(), query.lastMatch.end(),
std::set_difference(results.begin(), results.end(), query.lastMatch.begin(), query.lastMatch.end(),
std::back_inserter(added));
std::set_difference(query.lastMatch.begin(), query.lastMatch.end(), results.begin(), results.end(),
std::set_difference(query.lastMatch.begin(), query.lastMatch.end(), results.begin(), results.end(),
std::back_inserter(removed));
if (added.empty() && removed.empty())
continue;
@ -927,7 +932,7 @@ public:
}
}
// Not the entire world, so check a parabolic range, or a regular range
else if (q.parabolic)
else if (q.parabolic)
{
// elevationBonus is part of the 3D position, as the source is really that much heigher
CmpPtr<ICmpPosition> cmpSourcePosition(q.source);
@ -944,7 +949,7 @@ public:
if (!TestEntityQuery(q, it->first, it->second))
continue;
CmpPtr<ICmpPosition> cmpSecondPosition(GetSimContext(), ents[i]);
if (!cmpSecondPosition || !cmpSecondPosition->IsInWorld())
continue;
@ -952,8 +957,8 @@ public:
// Restrict based on precise distance
if (!InParabolicRange(
CFixedVector3D(it->second.x, secondPosition.Y, it->second.z)
- pos3d,
CFixedVector3D(it->second.x, secondPosition.Y, it->second.z)
- pos3d,
q.maxRange))
continue;
@ -968,12 +973,12 @@ public:
}
}
// check a regular range (i.e. not the entire world, and not parabolic)
else
else
{
// Get a quick list of entities that are potentially in range
SpatialQueryArray ents;
m_Subdivision.GetNear(ents, pos, q.maxRange);
for (int i = 0; i < ents.size(); ++i)
{
EntityMap<EntityData>::const_iterator it = m_EntityData.find(ents[i]);
@ -1002,7 +1007,7 @@ public:
virtual entity_pos_t GetElevationAdaptedRange(CFixedVector3D pos, CFixedVector3D rot, entity_pos_t range, entity_pos_t elevationBonus, entity_pos_t angle)
{
entity_pos_t r = entity_pos_t::Zero() ;
pos.Y += elevationBonus;
entity_pos_t orientation = rot.Y;
@ -1022,26 +1027,26 @@ public:
{
r = r + CFixedVector2D(coords[2*i],coords[2*i+1]).Length() / part;
}
return r;
}
virtual std::vector<entity_pos_t> getParabolicRangeForm(CFixedVector3D pos, entity_pos_t maxRange, entity_pos_t cutoff, entity_pos_t minAngle, entity_pos_t maxAngle, int numberOfSteps)
virtual std::vector<entity_pos_t> getParabolicRangeForm(CFixedVector3D pos, entity_pos_t maxRange, entity_pos_t cutoff, entity_pos_t minAngle, entity_pos_t maxAngle, int numberOfSteps)
{
// angle = 0 goes in the positive Z direction
entity_pos_t precision = entity_pos_t::FromInt((int)TERRAIN_TILE_SIZE)/8;
std::vector<entity_pos_t> r;
CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
entity_pos_t waterLevel = cmpWaterManager->GetWaterLevel(pos.X,pos.Z);
entity_pos_t thisHeight = pos.Y > waterLevel ? pos.Y : waterLevel;
if (cmpTerrain)
if (cmpTerrain)
{
for (int i = 0; i < numberOfSteps; i++)
{
@ -1064,7 +1069,7 @@ public:
r.push_back(maxVector.Y);
continue;
}
// Loop until vectors come close enough
while ((maxVector - minVector).CompareLength(precision) > 0)
{
@ -1083,26 +1088,26 @@ public:
minVector = newVector;
minDistance = newDistance;
}
else
else
{
// new vector is out parabolic range, so this is a new maxVector
maxVector = newVector;
maxDistance = newDistance;
}
}
r.push_back(maxVector.X);
r.push_back(maxVector.Y);
}
r.push_back(r[0]);
r.push_back(r[1]);
r.push_back(r[1]);
}
return r;
}
Query ConstructQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, u8 flagsMask)
@ -1150,7 +1155,7 @@ public:
return;
static CColor disabledRingColour(1, 0, 0, 1); // red
static CColor enabledRingColour(0, 1, 0, 1); // green
static CColor subdivColour(0, 0, 1, 1); // blue
static CColor subdivColour(0, 0, 1, 1); // blue
static CColor rayColour(1, 1, 0, 0.2f);
if (m_DebugOverlayDirty)
@ -1173,26 +1178,26 @@ public:
m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColour : disabledRingColour);
SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.maxRange.ToFloat(), m_DebugOverlayLines.back(), true);
}
else
{
else
{
// elevation bonus is part of the 3D position. As if the unit is really that much higher
CFixedVector3D pos = cmpSourcePosition->GetPosition();
CFixedVector3D pos = cmpSourcePosition->GetPosition();
pos.Y += q.elevationBonus;
std::vector<entity_pos_t> coords;
// Get the outline from cache if possible
if (ParabolicRangesOutlines.find(q.source.GetId()) != ParabolicRangesOutlines.end())
{
EntityParabolicRangeOutline e = ParabolicRangesOutlines[q.source.GetId()];
if (e.position == pos && e.range == q.maxRange)
if (e.position == pos && e.range == q.maxRange)
{
// outline is cached correctly, use it
coords = e.outline;
coords = e.outline;
}
else
{
// outline was cached, but important parameters changed
// outline was cached, but important parameters changed
// (position, elevation, range)
// update it
coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
@ -1202,9 +1207,9 @@ public:
ParabolicRangesOutlines[q.source.GetId()] = e;
}
}
else
else
{
// outline wasn't cached (first time you enable the range overlay
// outline wasn't cached (first time you enable the range overlay
// or you created a new entiy)
// cache a new outline
coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
@ -1215,10 +1220,10 @@ public:
e.outline = coords;
ParabolicRangesOutlines[q.source.GetId()] = e;
}
CColor thiscolor = q.enabled ? enabledRingColour : disabledRingColour;
// draw the outline (piece by piece)
// draw the outline (piece by piece)
for (size_t i = 3; i < coords.size(); i += 2)
{
std::vector<float> c;
@ -1268,7 +1273,7 @@ public:
{
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = subdivColour;
float xpos = x*divSize + divSize/2;
float zpos = y*divSize + divSize/2;
SimRender::ConstructSquareOnGround(GetSimContext(), xpos, zpos, divSize, divSize, 0.0f,

View File

@ -24,6 +24,7 @@
#include "simulation2/system/Interface.h"
#include "simulation2/helpers/Position.h"
#include "simulation2/helpers/Player.h"
#include "simulation2/helpers/Spatial.h"
#include "graphics/Terrain.h" // for TERRAIN_TILE_SIZE
@ -72,6 +73,12 @@ public:
*/
typedef u32 tag_t;
/**
* Access the spatial subdivision kept by the range manager.
* @return pointer to spatial subdivision structure.
*/
virtual SpatialSubdivision* GetSubdivision() = 0;
/**
* Set the bounds of the world.
* Entities should not be outside the bounds (else efficiency will suffer).
@ -118,12 +125,12 @@ public:
entity_pos_t minRange, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface, u8 flags) = 0;
/**
* Construct an active query of a paraboloic form around the unit.
* Construct an active query of a paraboloic form around the unit.
* The query will be disabled by default.
* @param source the entity around which the range will be computed.
* @param minRange non-negative minimum horizontal distance in metres (inclusive). MinRange doesn't do parabolic checks.
* @param maxRange non-negative maximum distance in metres (inclusive) for units on the same elevation;
* or -1.0 to ignore distance.
* @param maxRange non-negative maximum distance in metres (inclusive) for units on the same elevation;
* or -1.0 to ignore distance.
* For units on a different elevation, a physical correct paraboloid with height=maxRange/2 above the unit is used to query them
* @param elevationBonus extra bonus so the source can be placed higher and shoot further
* @param owners list of player IDs that matching entities may have; -1 matches entities with no owner.

View File

@ -27,26 +27,43 @@
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/components/ICmpSelectable.h"
#include "simulation2/components/ICmpVisual.h"
#include "simulation2/helpers/Spatial.h"
#include "ps/CLogger.h"
#include "ps/Profiler2.h"
std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, player_id_t player, bool allowEditorSelectables)
std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, player_id_t player, bool allowEditorSelectables, int range)
{
PROFILE2("PickEntitiesAtPoint");
CVector3D origin, dir;
camera.BuildCameraRay(screenX, screenY, origin, dir);
CmpPtr<ICmpRangeManager> cmpRangeManager(simulation, SYSTEM_ENTITY);
ENSURE(cmpRangeManager);
std::vector<std::pair<float, entity_id_t> > hits; // (dist^2, entity) pairs
/* We try to approximate where the mouse is hovering by drawing a ray from
* the center of the camera and through the mouse then taking the position
* at which the ray intersects the terrain. */
// TODO: Do this smarter without being slow.
CVector3D pos3d = camera.GetWorldCoordinates(screenX, screenY, true);
// Change the position to 2D by removing the terrain height.
CFixedVector2D pos(fixed::FromFloat(pos3d.X), fixed::FromFloat(pos3d.Z));
const CSimulation2::InterfaceListUnordered& ents = simulation.GetEntitiesWithInterfaceUnordered(IID_Selectable);
for (CSimulation2::InterfaceListUnordered::const_iterator it = ents.begin(); it != ents.end(); ++it)
// Get a rough group of entities using our approximated origin.
SpatialQueryArray ents;
cmpRangeManager->GetSubdivision()->GetNear(ents, pos, entity_pos_t::FromInt(range));
// Filter for relevent entities and calculate precise distances.
std::vector<std::pair<float, entity_id_t>> hits; // (dist^2, entity) pairs
for (int i = 0; i < ents.size(); ++i)
{
entity_id_t ent = it->first;
CEntityHandle handle = it->second->GetEntityHandle();
CmpPtr<ICmpSelectable> cmpSelectable(simulation, ents[i]);
if (!cmpSelectable)
continue;
CEntityHandle handle = cmpSelectable->GetEntityHandle();
// Check if this entity is only selectable in Atlas
if (!allowEditorSelectables && static_cast<ICmpSelectable*>(it->second)->IsEditorOnly())
if (!allowEditorSelectables && cmpSelectable->IsEditorOnly())
continue;
// Ignore entities hidden by LOS (or otherwise hidden, e.g. when not IsInWorld)
@ -86,12 +103,11 @@ std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simu
}
// Find the perpendicular distance from the object's centre to the picker ray
float dist2;
CVector3D closest = origin + dir * (center - origin).Dot(dir);
dist2 = (closest - center).LengthSquared();
hits.push_back(std::make_pair(dist2, ent));
hits.push_back(std::make_pair(dist2, ents[i]));
}
// Sort hits by distance
@ -107,6 +123,7 @@ std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simu
std::vector<entity_id_t> EntitySelection::PickEntitiesInRect(CSimulation2& simulation, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, player_id_t owner, bool allowEditorSelectables)
{
PROFILE2("PickEntitiesInRect");
// Make sure sx0 <= sx1, and sy0 <= sy1
if (sx0 > sx1)
std::swap(sx0, sx1);
@ -148,7 +165,6 @@ std::vector<entity_id_t> EntitySelection::PickEntitiesInRect(CSimulation2& simul
CVector3D position = cmpVisual->GetPosition();
// Reject if it's not on-screen (e.g. it's behind the camera)
if (!camera.GetFrustum().IsPointVisible(position))
continue;
@ -168,6 +184,7 @@ std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simu
const std::string& templateName, player_id_t owner, bool includeOffScreen, bool matchRank,
bool allowEditorSelectables, bool allowFoundations)
{
PROFILE2("PickSimilarEntities");
CmpPtr<ICmpTemplateManager> cmpTemplateManager(simulation, SYSTEM_ENTITY);
CmpPtr<ICmpRangeManager> cmpRangeManager(simulation, SYSTEM_ENTITY);

View File

@ -43,10 +43,11 @@ namespace EntitySelection
* this value is ignored as the whole map is revealed.
* @param allowEditorSelectables if true, all entities with the ICmpSelectable interface
* will be selected (including decorative actors), else only those selectable ingame.
* @param range Approximate range to check for entity in.
*
* @return ordered list of selected entities with the closest first.
*/
std::vector<entity_id_t> PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, player_id_t player, bool allowEditorSelectables);
std::vector<entity_id_t> PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, player_id_t player, bool allowEditorSelectables, int range = 200);
/**
* Finds all selectable entities within the given screen coordinate rectangle,

View File

@ -18,6 +18,7 @@
#ifndef INCLUDED_SPATIAL
#define INCLUDED_SPATIAL
#include "simulation2/system/Component.h"
#include "simulation2/serialization/SerializeTemplates.h"
#include "ps/CLogger.h"