# Basic bandbox selection of units in new simulation system
This was SVN commit r7349.
This commit is contained in:
parent
86205b4d8e
commit
06928f7694
@ -6,8 +6,9 @@ const SDL_BUTTON_RIGHT = 3;
|
||||
|
||||
|
||||
var INPUT_NORMAL = 0;
|
||||
var INPUT_DRAGGING = 1;
|
||||
var INPUT_BUILDING_PLACEMENT = 2;
|
||||
var INPUT_SELECTING = 1;
|
||||
var INPUT_BANDBOXING = 2;
|
||||
var INPUT_BUILDING_PLACEMENT = 3;
|
||||
|
||||
var inputState = INPUT_NORMAL;
|
||||
|
||||
@ -46,7 +47,7 @@ function findGatherType(gatherer, supply)
|
||||
*/
|
||||
function determineAction(x, y)
|
||||
{
|
||||
var selection = getEntitySelection();
|
||||
var selection = g_Selection.toList();
|
||||
|
||||
// No action if there's no selection
|
||||
if (!selection.length)
|
||||
@ -118,6 +119,10 @@ Selection methods: (not all currently implemented)
|
||||
|
||||
*/
|
||||
|
||||
// TODO: it'd probably be nice to have a better state-machine system
|
||||
|
||||
var selectionDragStart;
|
||||
|
||||
function handleInputAfterGui(ev)
|
||||
{
|
||||
switch (inputState)
|
||||
@ -125,19 +130,16 @@ function handleInputAfterGui(ev)
|
||||
case INPUT_NORMAL:
|
||||
switch (ev.type)
|
||||
{
|
||||
case "mousemotion":
|
||||
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
|
||||
g_Selection.setHighlightList(ents);
|
||||
return false;
|
||||
|
||||
case "mousebuttondown":
|
||||
if (ev.button == SDL_BUTTON_LEFT)
|
||||
{
|
||||
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
|
||||
if (!ents.length)
|
||||
{
|
||||
resetEntitySelection();
|
||||
return true;
|
||||
}
|
||||
|
||||
resetEntitySelection();
|
||||
addEntitySelection([ents[0]]);
|
||||
|
||||
selectionDragStart = [ ev.x, ev.y ];
|
||||
inputState = INPUT_SELECTING;
|
||||
return true;
|
||||
}
|
||||
else if (ev.button == SDL_BUTTON_RIGHT)
|
||||
@ -146,7 +148,7 @@ function handleInputAfterGui(ev)
|
||||
if (!action)
|
||||
break;
|
||||
|
||||
var selection = getEntitySelection();
|
||||
var selection = g_Selection.toList();
|
||||
|
||||
switch (action.type)
|
||||
{
|
||||
@ -164,6 +166,92 @@ function handleInputAfterGui(ev)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case INPUT_SELECTING:
|
||||
switch (ev.type)
|
||||
{
|
||||
case "mousemotion":
|
||||
// If the mouse moved further than a limit, switch to bandbox mode
|
||||
var dragDeltaX = ev.x - selectionDragStart[0];
|
||||
var dragDeltaY = ev.y - selectionDragStart[1];
|
||||
var maxDragDelta = 4;
|
||||
if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta)
|
||||
{
|
||||
inputState = INPUT_BANDBOXING;
|
||||
return false;
|
||||
}
|
||||
|
||||
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
|
||||
g_Selection.setHighlightList(ents);
|
||||
return false;
|
||||
|
||||
case "mousebuttonup":
|
||||
if (ev.button == SDL_BUTTON_LEFT)
|
||||
{
|
||||
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
|
||||
if (!ents.length)
|
||||
{
|
||||
g_Selection.reset();
|
||||
|
||||
inputState = INPUT_NORMAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
g_Selection.reset();
|
||||
g_Selection.addList([ents[0]]);
|
||||
|
||||
inputState = INPUT_NORMAL;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case INPUT_BANDBOXING:
|
||||
switch (ev.type)
|
||||
{
|
||||
case "mousemotion":
|
||||
var x0 = selectionDragStart[0];
|
||||
var y0 = selectionDragStart[1];
|
||||
var x1 = ev.x;
|
||||
var y1 = ev.y;
|
||||
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
|
||||
if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
|
||||
|
||||
var bandbox = getGUIObjectByName("bandbox");
|
||||
bandbox.size = [x0, y0, x1, y1].join(" ");
|
||||
bandbox.hidden = false;
|
||||
|
||||
var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID());
|
||||
g_Selection.setHighlightList(ents);
|
||||
|
||||
return false;
|
||||
|
||||
case "mousebuttonup":
|
||||
if (ev.button == SDL_BUTTON_LEFT)
|
||||
{
|
||||
var x0 = selectionDragStart[0];
|
||||
var y0 = selectionDragStart[1];
|
||||
var x1 = ev.x;
|
||||
var y1 = ev.y;
|
||||
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
|
||||
if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
|
||||
|
||||
var bandbox = getGUIObjectByName("bandbox");
|
||||
bandbox.hidden = true;
|
||||
|
||||
var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID());
|
||||
g_Selection.setHighlightList([]);
|
||||
g_Selection.reset();
|
||||
g_Selection.addList(ents);
|
||||
|
||||
inputState = INPUT_NORMAL;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
@ -195,6 +283,7 @@ function handleInputAfterGui(ev)
|
||||
inputState = INPUT_NORMAL;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1,51 +1,88 @@
|
||||
var g_Selection = {}; // { id: 1, id: 1, ... } for each selected entity ID 'id'
|
||||
var g_ActiveSelectionColour = { r:1, g:1, b:1, a:1 };
|
||||
var g_HighlightSelectionColour = { r:1, g:1, b:1, a:0.5 };
|
||||
var g_InactiveSelectionColour = { r:1, g:1, b:1, a:0 };
|
||||
|
||||
var g_ActiveSelectionColour = { r:1, g:1, b:1, a:1 };
|
||||
var g_InactiveSelectionColour = { r:0, g:0, b:0, a:0 };
|
||||
|
||||
function setHighlight(ent, colour)
|
||||
function _setHighlight(ents, colour)
|
||||
{
|
||||
Engine.GuiInterfaceCall("SetSelectionHighlight", { "entity":ent, "colour":colour });
|
||||
Engine.GuiInterfaceCall("SetSelectionHighlight", { "entities":ents, "colour":colour });
|
||||
}
|
||||
|
||||
function toggleEntitySelection(ent)
|
||||
function EntitySelection()
|
||||
{
|
||||
if (g_Selection[ent])
|
||||
this.selected = {}; // { id: 1, id: 1, ... } for each selected entity ID 'id'
|
||||
this.highlighted = {}; // { id: 1, ... } for mouseover-highlighted entity IDs
|
||||
}
|
||||
|
||||
EntitySelection.prototype.toggle = function(ent)
|
||||
{
|
||||
if (this.selected[ent])
|
||||
{
|
||||
setHighlight(ent, g_InactiveSelectionColour);
|
||||
delete g_Selection[ent];
|
||||
_setHighlight([ent], g_InactiveSelectionColour);
|
||||
delete this.selected[ent];
|
||||
}
|
||||
else
|
||||
{
|
||||
setHighlight(ent, g_ActiveSelectionColour);
|
||||
g_Selection[ent] = 1;
|
||||
_setHighlight([ent], g_ActiveSelectionColour);
|
||||
this.selected[ent] = 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function addEntitySelection(ents)
|
||||
EntitySelection.prototype.addList = function(ents)
|
||||
{
|
||||
var added = [];
|
||||
for each (var ent in ents)
|
||||
{
|
||||
if (!g_Selection[ent])
|
||||
if (!this.selected[ent])
|
||||
{
|
||||
setHighlight(ent, g_ActiveSelectionColour);
|
||||
g_Selection[ent] = 1;
|
||||
added.push(ent);
|
||||
this.selected[ent] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
_setHighlight(added, g_ActiveSelectionColour);
|
||||
};
|
||||
|
||||
function resetEntitySelection()
|
||||
EntitySelection.prototype.reset = function()
|
||||
{
|
||||
for (var ent in g_Selection)
|
||||
setHighlight(ent, g_InactiveSelectionColour);
|
||||
_setHighlight(this.toList(), g_InactiveSelectionColour);
|
||||
this.selected = {};
|
||||
};
|
||||
|
||||
g_Selection = {};
|
||||
}
|
||||
|
||||
function getEntitySelection()
|
||||
EntitySelection.prototype.toList = function()
|
||||
{
|
||||
var ents = [];
|
||||
for (var ent in g_Selection)
|
||||
for (var ent in this.selected)
|
||||
ents.push(+ent); // convert from string to number and push
|
||||
return ents;
|
||||
}
|
||||
};
|
||||
|
||||
EntitySelection.prototype.setHighlightList = function(ents)
|
||||
{
|
||||
var removed = [];
|
||||
var added = [];
|
||||
|
||||
// Remove highlighting for the old units (excluding ones that are actively selected too)
|
||||
for (var ent in this.highlighted)
|
||||
if (!this.selected[ent])
|
||||
removed.push(ent);
|
||||
|
||||
// Add new highlighting
|
||||
for each (var ent in ents)
|
||||
if (!this.selected[ent])
|
||||
added.push(ent);
|
||||
|
||||
_setHighlight(removed, g_InactiveSelectionColour);
|
||||
_setHighlight(added, g_HighlightSelectionColour);
|
||||
|
||||
// TODO: this could be a bit more efficient by only changing the ones that
|
||||
// have entered/left the highlight list
|
||||
|
||||
// Store the new list
|
||||
this.highlighted = {};
|
||||
for each (var ent in ents)
|
||||
this.highlighted[ent] = 1;
|
||||
};
|
||||
|
||||
|
||||
var g_Selection = new EntitySelection();
|
||||
|
||||
|
||||
|
@ -6,7 +6,7 @@ function init(initData, hotloadData)
|
||||
{
|
||||
if (hotloadData)
|
||||
{
|
||||
g_Selection = hotloadData.selection;
|
||||
g_Selection.selected = hotloadData.selection;
|
||||
}
|
||||
|
||||
onSimulationUpdate();
|
||||
@ -15,7 +15,7 @@ function init(initData, hotloadData)
|
||||
// Return some data that we'll use when hotloading this file after changes
|
||||
function getHotloadData()
|
||||
{
|
||||
return { selection: g_Selection };
|
||||
return { selection: g_Selection.selected };
|
||||
}
|
||||
|
||||
function onTick()
|
||||
@ -48,7 +48,7 @@ function updateDebug(simState)
|
||||
var debug = getGUIObjectByName("debug");
|
||||
var text = uneval(simState);
|
||||
|
||||
var selection = getEntitySelection();
|
||||
var selection = g_Selection.toList();
|
||||
if (selection.length)
|
||||
{
|
||||
var entState = Engine.GuiInterfaceCall("GetEntityState", selection[0]);
|
||||
@ -88,7 +88,7 @@ function updateUnitDisplay()
|
||||
var detailsPanel = getGUIObjectByName("selectionDetails");
|
||||
var commandsPanel = getGUIObjectByName("unitCommands");
|
||||
|
||||
var selection = getEntitySelection();
|
||||
var selection = g_Selection.toList();
|
||||
if (selection.length == 0)
|
||||
{
|
||||
detailsPanel.hidden = true;
|
||||
|
@ -230,4 +230,7 @@
|
||||
|
||||
</object>
|
||||
|
||||
<!-- Selection bandbox -->
|
||||
<object name="bandbox" type="image" sprite="bandbox" ghost="true" hidden="true"/>
|
||||
|
||||
</objects>
|
||||
|
@ -87,5 +87,16 @@
|
||||
<image backcolor="blue"/>
|
||||
</sprite>
|
||||
|
||||
<sprite name="bandbox">
|
||||
<image backcolor="black" size="0 0 100% 1"/>
|
||||
<image backcolor="black" size="100%-1 0 100% 100%"/>
|
||||
<image backcolor="black" size="0 100%-1 100% 100%"/>
|
||||
<image backcolor="black" size="0 0 1 100%"/>
|
||||
<image backcolor="white" size="1 1 100%-1 2"/>
|
||||
<image backcolor="white" size="100%-2 1 100%-1 100%-1"/>
|
||||
<image backcolor="white" size="1 100%-2 100%-1 100%-1"/>
|
||||
<image backcolor="white" size="1 1 2 100%-1"/>
|
||||
</sprite>
|
||||
|
||||
</sprites>
|
||||
|
||||
|
@ -119,9 +119,12 @@ GuiInterface.prototype.GetTemplateData = function(player, name)
|
||||
|
||||
GuiInterface.prototype.SetSelectionHighlight = function(player, cmd)
|
||||
{
|
||||
var cmpSelectable = Engine.QueryInterface(cmd.entity, IID_Selectable);
|
||||
if (cmpSelectable)
|
||||
cmpSelectable.SetSelectionHighlight(cmd.colour);
|
||||
for each (var ent in cmd.entities)
|
||||
{
|
||||
var cmpSelectable = Engine.QueryInterface(ent, IID_Selectable);
|
||||
if (cmpSelectable)
|
||||
cmpSelectable.SetSelectionHighlight(cmd.colour);
|
||||
}
|
||||
};
|
||||
|
||||
GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
|
||||
|
@ -55,7 +55,7 @@ class CCamera
|
||||
// everytime the view or projection matrices are
|
||||
// altered.
|
||||
void UpdateFrustum ();
|
||||
CFrustum GetFrustum () { return m_ViewFrustum; }
|
||||
const CFrustum& GetFrustum () { return m_ViewFrustum; }
|
||||
|
||||
void SetViewPort (SViewPort *viewport);
|
||||
const SViewPort& GetViewPort () const { return m_ViewPort; }
|
||||
|
@ -163,6 +163,11 @@ std::vector<entity_id_t> PickEntitiesAtPoint(void* UNUSED(cbdata), int x, int y)
|
||||
return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y);
|
||||
}
|
||||
|
||||
std::vector<entity_id_t> PickFriendlyEntitiesInRect(void* UNUSED(cbdata), int x0, int y0, int x1, int y1, int player)
|
||||
{
|
||||
return EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, player);
|
||||
}
|
||||
|
||||
CFixedVector3D GetTerrainAtPoint(void* UNUSED(cbdata), int x, int y)
|
||||
{
|
||||
CVector3D pos = g_Game->GetView()->GetCamera()->GetWorldCoordinates(x, y, false);
|
||||
@ -200,6 +205,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
|
||||
|
||||
// Entity picking
|
||||
scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint");
|
||||
scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect");
|
||||
scriptInterface.RegisterFunction<CFixedVector3D, int, int, &GetTerrainAtPoint>("GetTerrainAtPoint");
|
||||
|
||||
// Misc functions
|
||||
|
@ -54,6 +54,7 @@
|
||||
#include "simulation/FormationManager.h"
|
||||
#include "simulation/Simulation.h"
|
||||
#include "simulation/TerritoryManager.h"
|
||||
#include "simulation2/Simulation2.h"
|
||||
|
||||
#include "ps/CLogger.h"
|
||||
#define LOG_CATEGORY L"world"
|
||||
@ -1139,6 +1140,9 @@ void MouseButtonUpHandler(const SDL_Event_* ev, int clicks)
|
||||
|
||||
InReaction InteractInputHandler( const SDL_Event_* ev )
|
||||
{
|
||||
if (g_UseSimulation2)
|
||||
return IN_PASS;
|
||||
|
||||
if (!g_app_has_focus || !g_Game)
|
||||
return IN_PASS;
|
||||
|
||||
|
@ -65,6 +65,7 @@ void OverlayRenderer::RenderOverlays()
|
||||
}
|
||||
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
for (size_t i = 0; i < m->lines.size(); ++i)
|
||||
{
|
||||
@ -79,4 +80,6 @@ void OverlayRenderer::RenderOverlays()
|
||||
glInterleavedArrays(GL_V3F, sizeof(float)*3, &line->m_Coords[0]);
|
||||
glDrawArrays(GL_LINE_STRIP, 0, line->m_Coords.size()/3);
|
||||
}
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
@ -32,7 +32,7 @@
|
||||
// Set the maximum number of function arguments that can be handled
|
||||
// (This should be as small as possible (for compiler efficiency),
|
||||
// but as large as necessary for all wrapped functions)
|
||||
#define SCRIPT_INTERFACE_MAX_ARGS 5
|
||||
#define SCRIPT_INTERFACE_MAX_ARGS 6
|
||||
|
||||
struct ScriptInterface_impl;
|
||||
class ScriptClass;
|
||||
|
@ -147,6 +147,13 @@ public:
|
||||
return m_Unit->GetModel()->GetBounds();
|
||||
}
|
||||
|
||||
virtual CVector3D GetPosition()
|
||||
{
|
||||
if (!m_Unit)
|
||||
return CVector3D(0, 0, 0);
|
||||
return m_Unit->GetModel()->GetTransform().GetTranslation();
|
||||
}
|
||||
|
||||
virtual void SelectAnimation(std::string name, bool once, float speed)
|
||||
{
|
||||
if (!m_Unit)
|
||||
|
@ -34,6 +34,19 @@ public:
|
||||
*/
|
||||
virtual CBound GetBounds() = 0;
|
||||
|
||||
/**
|
||||
* Get the world-space position of the base point of the object's visual representation.
|
||||
* (Not safe for use in simulation code.)
|
||||
*/
|
||||
virtual CVector3D GetPosition() = 0;
|
||||
|
||||
/**
|
||||
* Start playing the given animation. If there are multiple possible animations then it will
|
||||
* pick one at random (not network-synchronised).
|
||||
* @param name animation name (e.g. "idle", "walk", "melee"; the names are determined by actor XML files)
|
||||
* @param once if true then the animation will play once and freeze at the final frame, else it will loop
|
||||
* @param speed some kind of animation speed multiplier (TODO: work out exactly what the scale is)
|
||||
*/
|
||||
virtual void SelectAnimation(std::string name, bool once, float speed) = 0;
|
||||
|
||||
DECLARE_INTERFACE_TYPE(Visual)
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include "graphics/Camera.h"
|
||||
#include "simulation2/Simulation2.h"
|
||||
#include "simulation2/components/ICmpOwnership.h"
|
||||
#include "simulation2/components/ICmpSelectable.h"
|
||||
#include "simulation2/components/ICmpVisual.h"
|
||||
|
||||
@ -69,3 +70,50 @@ std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simu
|
||||
hitEnts.push_back(hits[i].second);
|
||||
return hitEnts;
|
||||
}
|
||||
|
||||
std::vector<entity_id_t> EntitySelection::PickEntitiesInRect(CSimulation2& simulation, CCamera& camera, int sx0, int sy0, int sx1, int sy1, int owner)
|
||||
{
|
||||
// Make sure sx0 <= sx1, and sy0 <= sy1
|
||||
if (sx0 > sx1)
|
||||
std::swap(sx0, sx1);
|
||||
if (sy0 > sy1)
|
||||
std::swap(sy0, sy1);
|
||||
|
||||
std::vector<entity_id_t> hitEnts;
|
||||
|
||||
const CSimulation2::InterfaceList& ents = simulation.GetEntitiesWithInterface(IID_Selectable);
|
||||
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
|
||||
{
|
||||
entity_id_t ent = it->first;
|
||||
|
||||
// Ignore entities not owned by 'owner'
|
||||
CmpPtr<ICmpOwnership> cmpOwnership(simulation.GetSimContext(), ent);
|
||||
if (cmpOwnership.null() || cmpOwnership->GetOwner() != owner)
|
||||
continue;
|
||||
|
||||
// Find the current interpolated model position.
|
||||
// (We just use the centre position and not the whole bounding box, because maybe
|
||||
// that's better for users trying to select objects in busy areas)
|
||||
|
||||
CmpPtr<ICmpVisual> cmpVisual(simulation.GetSimContext(), ent);
|
||||
if (cmpVisual.null())
|
||||
continue;
|
||||
|
||||
CVector3D position = cmpVisual->GetPosition();
|
||||
|
||||
// Reject if it's not on-screen (e.g. it's behind the camera)
|
||||
|
||||
if (!camera.GetFrustum().IsPointVisible(position))
|
||||
continue;
|
||||
|
||||
// Compare screen-space coordinates
|
||||
float x, y;
|
||||
camera.GetScreenCoordinates(position, x, y);
|
||||
int ix = (int)x;
|
||||
int iy = (int)y;
|
||||
if (sx0 <= ix && ix <= sx1 && sy0 <= iy && iy <= sy1)
|
||||
hitEnts.push_back(ent);
|
||||
}
|
||||
|
||||
return hitEnts;
|
||||
}
|
||||
|
@ -39,6 +39,13 @@ namespace EntitySelection
|
||||
*/
|
||||
std::vector<entity_id_t> PickEntitiesAtPoint(CSimulation2& simulation, CCamera& camera, int screenX, int screenY);
|
||||
|
||||
/**
|
||||
* Finds all selectable entities within the given screen coordinate rectangle,
|
||||
* that belong to player @p owner.
|
||||
* Returns unordered list.
|
||||
*/
|
||||
std::vector<entity_id_t> PickEntitiesInRect(CSimulation2& simulation, CCamera& camera, int sx0, int sy0, int sx1, int sy1, int owner);
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // INCLUDED_SELECTION
|
||||
|
Loading…
Reference in New Issue
Block a user