diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js
index 5a566aa817..197bf6ca19 100644
--- a/binaries/data/mods/public/gui/session/input.js
+++ b/binaries/data/mods/public/gui/session/input.js
@@ -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"))
diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml
index 48ba31fb31..f65afe3d7c 100644
--- a/binaries/data/mods/public/gui/session/session.xml
+++ b/binaries/data/mods/public/gui/session/session.xml
@@ -293,7 +293,14 @@
diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
index b417e95c1a..6f60277a85 100644
--- a/source/gui/scripting/ScriptFunctions.cpp
+++ b/source/gui/scripting/ScriptFunctions.cpp
@@ -148,9 +148,9 @@ void PostNetworkCommand(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal cmd)
cmpCommandQueue->PostNetworkCommand(cmd2);
}
-std::vector PickEntitiesAtPoint(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x, int y)
+std::vector 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 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("PostNetworkCommand");
// Entity picking
- scriptInterface.RegisterFunction, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint");
+ scriptInterface.RegisterFunction, int, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint");
scriptInterface.RegisterFunction, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect");
scriptInterface.RegisterFunction, int, &PickFriendlyEntitiesOnScreen>("PickFriendlyEntitiesOnScreen");
scriptInterface.RegisterFunction, std::string, bool, bool, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities");
diff --git a/source/simulation2/components/CCmpRangeManager.cpp b/source/simulation2/components/CCmpRangeManager.cpp
index 1daafcdfca..49590e814f 100644
--- a/source/simulation2/components/CCmpRangeManager.cpp
+++ b/source/simulation2/components/CCmpRangeManager.cpp
@@ -97,17 +97,17 @@ static u32 CalcSharedLosMask(std::vector 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 cmpSourcePosition(query.source);
+ CmpPtr 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 cmpSourcePosition(q.source);
@@ -944,7 +949,7 @@ public:
if (!TestEntityQuery(q, it->first, it->second))
continue;
-
+
CmpPtr 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::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 getParabolicRangeForm(CFixedVector3D pos, entity_pos_t maxRange, entity_pos_t cutoff, entity_pos_t minAngle, entity_pos_t maxAngle, int numberOfSteps)
+ virtual std::vector 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 r;
-
+
CmpPtr cmpTerrain(GetSystemEntity());
CmpPtr 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& 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 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 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,
diff --git a/source/simulation2/components/ICmpRangeManager.h b/source/simulation2/components/ICmpRangeManager.h
index ea41e147b4..61584343a2 100644
--- a/source/simulation2/components/ICmpRangeManager.h
+++ b/source/simulation2/components/ICmpRangeManager.h
@@ -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 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.
diff --git a/source/simulation2/helpers/Selection.cpp b/source/simulation2/helpers/Selection.cpp
index b6d0a0ba7c..4cc7a7ebbc 100644
--- a/source/simulation2/helpers/Selection.cpp
+++ b/source/simulation2/helpers/Selection.cpp
@@ -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 EntitySelection::PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, player_id_t player, bool allowEditorSelectables)
+std::vector 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 cmpRangeManager(simulation, SYSTEM_ENTITY);
ENSURE(cmpRangeManager);
- std::vector > 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> 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 cmpSelectable(simulation, ents[i]);
+ if (!cmpSelectable)
+ continue;
+
+ CEntityHandle handle = cmpSelectable->GetEntityHandle();
// Check if this entity is only selectable in Atlas
- if (!allowEditorSelectables && static_cast(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 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 EntitySelection::PickEntitiesAtPoint(CSimulation2& simu
std::vector 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 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 EntitySelection::PickSimilarEntities(CSimulation2& simu
const std::string& templateName, player_id_t owner, bool includeOffScreen, bool matchRank,
bool allowEditorSelectables, bool allowFoundations)
{
+ PROFILE2("PickSimilarEntities");
CmpPtr cmpTemplateManager(simulation, SYSTEM_ENTITY);
CmpPtr cmpRangeManager(simulation, SYSTEM_ENTITY);
diff --git a/source/simulation2/helpers/Selection.h b/source/simulation2/helpers/Selection.h
index 484ee085be..908727bb63 100644
--- a/source/simulation2/helpers/Selection.h
+++ b/source/simulation2/helpers/Selection.h
@@ -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 PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, player_id_t player, bool allowEditorSelectables);
+std::vector 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,
diff --git a/source/simulation2/helpers/Spatial.h b/source/simulation2/helpers/Spatial.h
index bd1540d66c..cd3b8d8a40 100644
--- a/source/simulation2/helpers/Spatial.h
+++ b/source/simulation2/helpers/Spatial.h
@@ -18,6 +18,7 @@
#ifndef INCLUDED_SPATIAL
#define INCLUDED_SPATIAL
+#include "simulation2/system/Component.h"
#include "simulation2/serialization/SerializeTemplates.h"
#include "ps/CLogger.h"