1
0
forked from 0ad/0ad

# Basic bandbox selection of units in new simulation system

This was SVN commit r7349.
This commit is contained in:
Ykkrosh 2010-03-11 20:01:16 +00:00
parent 86205b4d8e
commit 06928f7694
15 changed files with 281 additions and 50 deletions

View File

@ -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;
}

View File

@ -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();

View File

@ -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;

View File

@ -230,4 +230,7 @@
</object>
<!-- Selection bandbox -->
<object name="bandbox" type="image" sprite="bandbox" ghost="true" hidden="true"/>
</objects>

View File

@ -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>

View File

@ -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)

View File

@ -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; }

View File

@ -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

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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)

View File

@ -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)

View File

@ -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;
}

View File

@ -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