1
0
forked from 0ad/0ad

Adds multiple entity selection to Atlas (including move/delete). Fixes #678.

Makes all actors selectable in Atlas and gives them selection rings (an
EditorOnly flag is used in the template for Atlas-only selectables).
Adds player colors to Atlas selection rings.
Fixes decal selection by falling back to old-style AABBs. Refs #1032.
Replaces UnitManager selections with EntitySelection helpers.
Adds DrawOverlays to Atlas views, for Atlas-specific rendering.
Fixes bug where selection rings conflicted with Move/rotate tool in
Atlas simulation test.

This was SVN commit r11177.
This commit is contained in:
historic_bruno 2012-02-27 05:32:35 +00:00
parent 8651452d33
commit 7d9e98b00e
26 changed files with 632 additions and 186 deletions

View File

@ -20,7 +20,9 @@
<Obstruction>
<Static width="59" depth="15"/>
</Obstruction>
<Selectable disable=""/>
<Selectable>
<EditorOnly/>
</Selectable>
<Vision>
<Range>72</Range>
<RetainInFog>true</RetainInFog>

View File

@ -20,7 +20,9 @@
<Obstruction>
<Static width="59" depth="15"/>
</Obstruction>
<Selectable disable=""/>
<Selectable>
<EditorOnly/>
</Selectable>
<Vision>
<Range>72</Range>
<RetainInFog>true</RetainInFog>

View File

@ -21,7 +21,9 @@
<Floating>false</Floating>
<TurnRate>6.0</TurnRate>
</Position>
<Selectable/>
<Selectable>
<EditorOnly disable=""/>
</Selectable>
<TerritoryInfluence>
<OverrideCost>64</OverrideCost>
<Root>false</Root>

View File

@ -21,7 +21,9 @@
<Floating>false</Floating>
<TurnRate>6.0</TurnRate>
</Position>
<Selectable/>
<Selectable>
<EditorOnly disable=""/>
</Selectable>
<TerritoryInfluence>
<OverrideCost>0</OverrideCost>
<Root>false</Root>

View File

@ -8,4 +8,7 @@
<Floating>false</Floating>
<TurnRate>6.0</TurnRate>
</Position>
<Selectable>
<EditorOnly/>
</Selectable>
</Entity>

View File

@ -16,7 +16,9 @@
<Amount>200</Amount>
<Type>food.fruit</Type>
</ResourceSupply>
<Selectable/>
<Selectable>
<EditorOnly disable=""/>
</Selectable>
<Sound>
<SoundGroups>
<select>interface/select/resource/sel_fruit.xml</select>

View File

@ -15,7 +15,9 @@
<Amount>200</Amount>
<Type>wood.tree</Type>
</ResourceSupply>
<Selectable/>
<Selectable>
<EditorOnly disable=""/>
</Selectable>
<Sound>
<SoundGroups>
<select>interface/select/resource/sel_tree.xml</select>

View File

@ -15,7 +15,9 @@
<Amount>1000</Amount>
<Type>metal.ore</Type>
</ResourceSupply>
<Selectable/>
<Selectable>
<EditorOnly disable=""/>
</Selectable>
<Sound>
<SoundGroups>
<select>interface/select/resource/sel_metal.xml</select>

View File

@ -15,7 +15,9 @@
<Amount>1000</Amount>
<Type>stone.rock</Type>
</ResourceSupply>
<Selectable/>
<Selectable>
<EditorOnly disable=""/>
</Selectable>
<Sound>
<SoundGroups>
<select>interface/select/resource/sel_stone.xml</select>

View File

@ -22,5 +22,7 @@
<Amount>500</Amount>
<Type>stone.ruins</Type>
</ResourceSupply>
<Selectable/>
<Selectable>
<EditorOnly disable=""/>
</Selectable>
</Entity>

View File

@ -22,5 +22,7 @@
<Amount>300</Amount>
<Type>treasure.metal</Type>
</ResourceSupply>
<Selectable/>
<Selectable>
<EditorOnly disable=""/>
</Selectable>
</Entity>

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -85,42 +85,6 @@ void CUnitManager::DeleteAll()
m_Units.clear();
}
///////////////////////////////////////////////////////////////////////////////
// PickUnit: iterate through units testing given ray against bounds of each
// unit; return the closest unit, or null if everything missed
CUnit* CUnitManager::PickUnit(const CVector3D& origin, const CVector3D& dir) const
{
// closest object found so far
CUnit* hit = 0;
// closest approach offset (easier to pick small stuff in forests than standard ScEd style selection)
float minrel = FLT_MAX;
for (size_t i=0; i<m_Units.size(); i++) {
CUnit* unit = m_Units[i];
float tmin, tmax;
const CBoundingBoxOriented& selectionBox = unit->GetModel().GetSelectionBox();
if (selectionBox.RayIntersect(origin, dir, tmin, tmax))
{
// Point of closest approach
// TODO: this next bit is virtually identical to Selection::PickEntitiesAtPoint; might be useful to factor it out and
// reuse it
CVector3D delta = selectionBox.m_Center - origin;
float distance = delta.Dot(dir);
CVector3D closest = origin + dir * distance;
CVector3D offset = selectionBox.m_Center - closest;
float rel = offset.Length();
if (rel < minrel) {
hit = unit;
minrel = rel;
}
}
}
return hit;
}
///////////////////////////////////////////////////////////////////////////////
// CreateUnit: create a new unit and add it to the world
CUnit* CUnitManager::CreateUnit(const CStrW& actorName, uint32_t seed, const std::set<CStr8>& selections)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -55,10 +55,6 @@ public:
// return the units
const std::vector<CUnit*>& GetUnits() const { return m_Units; }
// iterate through units testing given ray against bounds of each unit;
// return the closest unit, or null if everything missed
CUnit* PickUnit(const CVector3D& origin, const CVector3D& dir) const;
void SetObjectManager(CObjectManager& objectManager) { m_ObjectManager = &objectManager; }
private:

View File

@ -128,17 +128,17 @@ void PostNetworkCommand(void* cbdata, CScriptVal cmd)
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, g_Game->GetPlayerID());
return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetPlayerID(), false);
}
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);
return EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, player, false);
}
std::vector<entity_id_t> PickSimilarFriendlyEntities(void* UNUSED(cbdata), std::string templateName, bool includeOffScreen, bool matchRank)
{
return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), includeOffScreen, matchRank);
return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), includeOffScreen, matchRank, false);
}
CFixedVector3D GetTerrainAtPoint(void* UNUSED(cbdata), int x, int y)

View File

@ -99,6 +99,10 @@
#include "ps/GameSetup/CmdLineArgs.h"
#include "ps/GameSetup/HWDetect.h"
#include "tools/atlas/GameInterface/GameLoop.h"
#include "tools/atlas/GameInterface/View.h"
#if !(OS_WIN || OS_MACOSX || OS_ANDROID) // assume all other platforms use X11 for wxWidgets
#define MUST_INIT_X11 1
#include <X11/Xlib.h>
@ -232,6 +236,13 @@ void Render()
ogl_WarnIfError();
// If we're in Atlas game view, render special overlays (e.g. editor bandbox)
if (g_GameLoop && g_GameLoop->view)
{
g_GameLoop->view->DrawOverlays();
ogl_WarnIfError();
}
// Text:
glDisable(GL_DEPTH_TEST);

View File

@ -48,6 +48,7 @@ public:
SOverlayLine m_Overlay;
SOverlayLine* m_DebugBoundingBoxOverlay;
SOverlayLine* m_DebugSelectionBoxOverlay;
bool m_EditorOnly;
CCmpSelectable()
: m_DebugBoundingBoxOverlay(NULL), m_DebugSelectionBoxOverlay(NULL)
@ -66,11 +67,16 @@ public:
return
"<a:help>Allows this entity to be selected by the player.</a:help>"
"<a:example/>"
"<empty/>";
"<optional>"
"<element name='EditorOnly' a:help='If this element is present, the entity is only selectable in Atlas'>"
"<empty/>"
"</element>"
"</optional>";
}
virtual void Init(const CParamNode& UNUSED(paramNode))
virtual void Init(const CParamNode& paramNode)
{
m_EditorOnly = paramNode.GetChild("EditorOnly").IsOk();
}
virtual void Deinit()
@ -112,6 +118,11 @@ public:
}
}
virtual bool IsEditorOnly()
{
return m_EditorOnly;
}
virtual void SetSelectionHighlight(CColor color)
{
m_Overlay.m_Color = color;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -369,9 +369,9 @@ void CCmpTemplateManager::ConstructTemplateActor(const std::string& actorName, C
// Copy the actor template
out = m_TemplateFileData[templateName];
// Initialise the actor's name
// Initialise the actor's name and make it an Atlas selectable entity.
std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(wstring_from_utf8(actorName)));
std::string xml = "<Entity><VisualActor><Actor>" + name + "</Actor></VisualActor></Entity>";
std::string xml = "<Entity><VisualActor><Actor>" + name + "</Actor></VisualActor><Selectable><EditorOnly/></Selectable></Entity>";
CParamNode::LoadXMLString(out, xml.c_str());
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -25,6 +25,11 @@ struct CColor;
class ICmpSelectable : public IComponent
{
public:
/**
* Returns true if the entity is only selectable in Atlas editor, e.g. a decorative visual actor.
*/
virtual bool IsEditorOnly() = 0;
/**
* Set the color of the selection highlight (typically a circle/square
* around the unit). Set a = 0 to disable.

View File

@ -28,7 +28,7 @@
#include "simulation2/components/ICmpSelectable.h"
#include "simulation2/components/ICmpVisual.h"
std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, int player)
std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, player_id_t player, bool allowEditorSelectables)
{
CVector3D origin, dir;
camera.BuildCameraRay(screenX, screenY, origin, dir);
@ -43,6 +43,10 @@ std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simu
{
entity_id_t ent = it->first;
// Check if this entity is only selectable in Atlas
if (static_cast<ICmpSelectable*>(it->second)->IsEditorOnly() && !allowEditorSelectables)
continue;
// Ignore entities hidden by LOS (or otherwise hidden, e.g. when not IsInWorld)
if (cmpRangeManager->GetLosVisibility(ent, player) == ICmpRangeManager::VIS_HIDDEN)
continue;
@ -51,18 +55,39 @@ std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simu
if (!cmpVisual)
continue;
CVector3D center;
float tmin, tmax;
CBoundingBoxOriented selectionBox = cmpVisual->GetSelectionBox();
if (selectionBox.IsEmpty())
continue;
{
if (!allowEditorSelectables)
continue;
float tmin, tmax;
if (!selectionBox.RayIntersect(origin, dir, tmin, tmax))
continue;
// Fall back to using old AABB selection method for decals
// see: http://trac.wildfiregames.com/ticket/1032
CBoundingBoxAligned aABBox = cmpVisual->GetBounds();
if (aABBox.IsEmpty())
continue;
if (!aABBox.RayIntersect(origin, dir, tmin, tmax))
continue;
aABBox.GetCentre(center);
}
else
{
if (!selectionBox.RayIntersect(origin, dir, tmin, tmax))
continue;
center = selectionBox.m_Center;
}
// Find the perpendicular distance from the object's centre to the picker ray
CVector3D closest = origin + dir * (selectionBox.m_Center - origin).Dot(dir);
float dist2 = (closest - selectionBox.m_Center).LengthSquared();
float dist2;
CVector3D closest = origin + dir * (center - origin).Dot(dir);
dist2 = (closest - center).LengthSquared();
hits.push_back(std::make_pair(dist2, ent));
}
@ -78,7 +103,7 @@ std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simu
return hitEnts;
}
std::vector<entity_id_t> EntitySelection::PickEntitiesInRect(CSimulation2& simulation, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, int owner)
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)
{
// Make sure sx0 <= sx1, and sy0 <= sy1
if (sx0 > sx1)
@ -96,13 +121,17 @@ std::vector<entity_id_t> EntitySelection::PickEntitiesInRect(CSimulation2& simul
{
entity_id_t ent = it->first;
// Check if this entity is only selectable in Atlas
if (static_cast<ICmpSelectable*>(it->second)->IsEditorOnly() && !allowEditorSelectables)
continue;
// Ignore entities hidden by LOS (or otherwise hidden, e.g. when not IsInWorld)
if (cmpRangeManager->GetLosVisibility(ent, owner) == ICmpRangeManager::VIS_HIDDEN)
continue;
// Ignore entities not owned by 'owner'
CmpPtr<ICmpOwnership> cmpOwnership(simulation.GetSimContext(), ent);
if (!cmpOwnership || cmpOwnership->GetOwner() != owner)
if (owner != INVALID_PLAYER && (!cmpOwnership || cmpOwnership->GetOwner() != owner))
continue;
// Find the current interpolated model position.
@ -132,7 +161,7 @@ std::vector<entity_id_t> EntitySelection::PickEntitiesInRect(CSimulation2& simul
return hitEnts;
}
std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName, int owner, bool includeOffScreen, bool matchRank)
std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName, player_id_t owner, bool includeOffScreen, bool matchRank, bool allowEditorSelectables)
{
CmpPtr<ICmpTemplateManager> cmpTemplateManager(simulation, SYSTEM_ENTITY);
CmpPtr<ICmpRangeManager> cmpRangeManager(simulation, SYSTEM_ENTITY);
@ -144,6 +173,10 @@ std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simu
{
entity_id_t ent = it->first;
// Check if this entity is only selectable in Atlas
if (static_cast<ICmpSelectable*>(it->second)->IsEditorOnly() && !allowEditorSelectables)
continue;
if (matchRank)
{
// Exact template name matching
@ -156,14 +189,14 @@ std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simu
if (cmpRangeManager->GetLosVisibility(ent, owner) == ICmpRangeManager::VIS_HIDDEN)
continue;
// Ignore entities not owned by 'owner'
CmpPtr<ICmpOwnership> cmpOwnership(simulation.GetSimContext(), ent);
if (!cmpOwnership || cmpOwnership->GetOwner() != owner)
continue;
// Ignore entities not owned by 'owner'
CmpPtr<ICmpOwnership> cmpOwnership(simulation.GetSimContext(), ent);
if (owner != INVALID_PLAYER && (!cmpOwnership || cmpOwnership->GetOwner() != owner))
continue;
// Ignore off screen entities
if (!includeOffScreen)
{
if (!includeOffScreen)
{
// Find the current interpolated model position.
CmpPtr<ICmpVisual> cmpVisual(simulation.GetSimContext(), ent);
if (!cmpVisual)
@ -173,7 +206,7 @@ std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simu
// Reject if it's not on-screen (e.g. it's behind the camera)
if (!camera.GetFrustum().IsPointVisible(position))
continue;
}
}
if (!matchRank)
{

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -24,6 +24,7 @@
*/
#include "simulation2/system/Entity.h"
#include "Player.h"
#include <vector>
@ -37,24 +38,33 @@ namespace EntitySelection
* Finds all selectable entities under the given screen coordinates.
* Returns list ordered by closeness of picking, closest first.
* Restricted to entities in the LOS of @p player, but with any owner.
* (For Atlas selections this value is ignored as the whole map is revealed).
* If @p allowEditorSelectables then all entities with the IID_Selectable interface
* will be selected, else only selectable entities without the EditorOnly flag set.
*/
std::vector<entity_id_t> PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, int player);
std::vector<entity_id_t> PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, player_id_t player, bool allowEditorSelectables);
/**
* Finds all selectable entities within the given screen coordinate rectangle,
* that belong to player @p owner.
* If @p owner is INVALID_PLAYER then ownership is ignored.
* If @p allowEditorSelectables then all entities with the IID_Selectable interface
* will be selected, else only selectable entities without the EditorOnly flag set.
* Returns unordered list.
*/
std::vector<entity_id_t> PickEntitiesInRect(CSimulation2& simulation, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, int owner);
std::vector<entity_id_t> PickEntitiesInRect(CSimulation2& simulation, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, player_id_t owner, bool allowEditorSelectables);
/**
* Finds all entities with the given entity template name, that belong to player @p owner.
* If @p owner is INVALID_PLAYER then ownership is ignored.
* If @p includeOffScreen then all entities visible in the world will be selected,
* else only entities visible on the screen will be selected.
* If @p matchRank then only entities that exactly match @p templateName will be selected,
* else entities with matching SelectionGroupName will be selected.
* If @p allowEditorSelectables then all entities with the IID_Selectable interface
* will be selected, else only selectable entities without the EditorOnly flag set.
*/
std::vector<entity_id_t> PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName, int owner, bool includeOffScreen, bool matchRank);
std::vector<entity_id_t> PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName, player_id_t owner, bool includeOffScreen, bool matchRank, bool allowEditorSelectables);
} // namespace

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -31,9 +31,16 @@ class TransformObject : public StateDrivenTool<TransformObject>
DECLARE_DYNAMIC_CLASS(TransformObject);
int m_dx, m_dy;
AtlasMessage::ObjectID m_lastSelected;
wxPoint m_startPoint;
// TODO: If we don't plan to change hotkeys, just replace with evt.ShiftDown(), etc.
static const wxKeyCode SELECTION_ADD_HOTKEY = WXK_SHIFT;
static const wxKeyCode SELECTION_REMOVE_HOTKEY = WXK_CONTROL; // COMMAND on Macs
static const wxKeyCode SELECTION_ACTORS_HOTKEY = WXK_ALT;
public:
TransformObject()
TransformObject() : m_lastSelected(0)
{
SetState(&Waiting);
}
@ -52,37 +59,77 @@ public:
{
bool OnMouse(TransformObject* obj, wxMouseEvent& evt)
{
if (evt.LeftDown())
if (evt.LeftDClick() && AtlasMessage::ObjectIDIsValid(obj->m_lastSelected))
{
SET_STATE(SelectSimilar);
return true;
}
else if (evt.LeftDown())
{
bool selectionAdd = wxGetKeyState(SELECTION_ADD_HOTKEY);
bool selectionRemove = wxGetKeyState(SELECTION_REMOVE_HOTKEY);
bool selectionActors = wxGetKeyState(SELECTION_ACTORS_HOTKEY);
// New selection - never merge with movements of other objects
ScenarioEditor::GetCommandProc().FinaliseLastCommand();
// Select the object clicked on:
AtlasMessage::qPickObject qry(Position(evt.GetPosition()));
AtlasMessage::qPickObject qry(Position(evt.GetPosition()), selectionActors);
qry.Post();
// TODO: handle multiple selections
g_SelectedObjects.clear();
// Check they actually clicked on a valid object
if (AtlasMessage::ObjectIDIsValid(qry.id))
{
g_SelectedObjects.push_back(qry.id);
// Remember the screen-space offset of the mouse from the
// object's centre, so we can add that back when moving it
// (instead of just moving the object's centre to directly
// beneath the mouse)
obj->m_dx = qry.offsetx;
obj->m_dy = qry.offsety;
SET_STATE(Dragging);
std::vector<AtlasMessage::ObjectID>::iterator it = std::find(g_SelectedObjects.begin(), g_SelectedObjects.end(), qry.id);
bool objectIsSelected = (it != g_SelectedObjects.end());
if (selectionRemove)
{
// Remove from selection
if (objectIsSelected)
g_SelectedObjects.erase(it);
}
else if (!objectIsSelected)
{
// Add to selection
if (!selectionAdd)
g_SelectedObjects.clear();
g_SelectedObjects.push_back(qry.id);
}
obj->m_lastSelected = qry.id;
// If we're selecting the whole group
if (!selectionAdd && !selectionRemove && !g_SelectedObjects.empty())
{
// Remember the screen-space offset of the mouse from the
// object's centre, so we can add that back when moving it
// (instead of just moving the object's centre to directly
// beneath the mouse)
obj->m_dx = qry.offsetx;
obj->m_dy = qry.offsety;
SET_STATE(Dragging);
}
g_SelectedObjects.NotifyObservers();
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
}
g_SelectedObjects.NotifyObservers();
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
else
{
// Bandboxing
obj->m_lastSelected = 0;
obj->m_startPoint = evt.GetPosition();
SET_STATE(Bandboxing);
}
return true;
}
else if ((evt.Dragging() && evt.RightIsDown()) || evt.RightDown())
else if (g_SelectedObjects.size() == 1 && ((evt.Dragging() && evt.RightIsDown()) || evt.RightDown()))
{
// TODO: Rotation of selections with multiple objects?
// Dragging with right mouse button -> rotate objects to look
// at mouse
Position pos (evt.GetPosition());
@ -91,6 +138,12 @@ public:
return true;
}
else if (evt.Moving())
{
// Prevent certain events from reaching game UI in this mode
// to prevent selection ring confusion
return true;
}
else
return false;
}
@ -132,8 +185,23 @@ public:
else if (evt.Dragging())
{
Position pos (evt.GetPosition() + wxPoint(obj->m_dx, obj->m_dy));
for (size_t i = 0; i < g_SelectedObjects.size(); ++i)
POST_COMMAND(MoveObject, (g_SelectedObjects[i], pos));
POST_COMMAND(MoveObjects, (g_SelectedObjects, obj->m_lastSelected, pos));
return true;
}
else
return false;
}
bool OnKey(TransformObject* obj, wxKeyEvent& evt, KeyEventType type)
{
if (type == KEY_UP && evt.GetKeyCode() == WXK_ESCAPE)
{
// Cancel move action
ScenarioEditor::GetCommandProc().FinaliseLastCommand();
ScenarioEditor::GetCommandProc().Undo();
SET_STATE(Waiting);
return true;
}
else
@ -141,6 +209,132 @@ public:
}
}
Dragging;
struct sBandboxing : public State
{
bool OnMouse(TransformObject* obj, wxMouseEvent& evt)
{
if (evt.LeftIsDown() && evt.Dragging())
{
// Update bandbox overlay
POST_MESSAGE(SetBandbox, (true, obj->m_startPoint.x, obj->m_startPoint.y, evt.GetPosition().x, evt.GetPosition().y));
return true;
}
else if (evt.LeftUp())
{
bool selectionAdd = wxGetKeyState(SELECTION_ADD_HOTKEY);
bool selectionRemove = wxGetKeyState(SELECTION_REMOVE_HOTKEY);
bool selectionActors = wxGetKeyState(SELECTION_ACTORS_HOTKEY);
// Now we have both corners of the box, pick objects
AtlasMessage::qPickObjectsInRect qry(Position(obj->m_startPoint), Position(evt.GetPosition()), selectionActors);
qry.Post();
std::vector<AtlasMessage::ObjectID> ids = *qry.ids;
if (!selectionAdd && !selectionRemove)
{
// Just copy new selections (clears list if no selections)
g_SelectedObjects = ids;
}
else
{
for (size_t i = 0; i < ids.size(); ++i)
{
std::vector<AtlasMessage::ObjectID>::iterator it = std::find(g_SelectedObjects.begin(), g_SelectedObjects.end(), ids[i]);
bool objectIsSelected = (it != g_SelectedObjects.end());
if (selectionRemove)
{
// Remove from selection
if (objectIsSelected)
g_SelectedObjects.erase(it);
}
else if (!objectIsSelected)
{
// Add to selection
g_SelectedObjects.push_back(ids[i]);
}
}
}
POST_MESSAGE(SetBandbox, (false, 0, 0, 0, 0));
g_SelectedObjects.NotifyObservers();
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
SET_STATE(Waiting);
return true;
}
else
return false;
}
bool OnKey(TransformObject* obj, wxKeyEvent& evt, KeyEventType type)
{
if (type == KEY_UP && evt.GetKeyCode() == WXK_ESCAPE)
{
// Clear bandbox and return to waiting state
POST_MESSAGE(SetBandbox, (false, 0, 0, 0, 0));
SET_STATE(Waiting);
return true;
}
else
return false;
}
}
Bandboxing;
struct sSelectSimilar : public State
{
bool OnMouse(TransformObject* obj, wxMouseEvent& evt)
{
if (evt.LeftUp())
{
bool selectionAdd = wxGetKeyState(SELECTION_ADD_HOTKEY);
bool selectionRemove = wxGetKeyState(SELECTION_REMOVE_HOTKEY);
// Select similar objects
AtlasMessage::qPickSimilarObjects qry(obj->m_lastSelected);
qry.Post();
std::vector<AtlasMessage::ObjectID> ids = *qry.ids;
if (!selectionAdd && !selectionRemove)
{
// Just copy new selections (clears list if no selections)
g_SelectedObjects = ids;
}
else
{
for (size_t i = 0; i < ids.size(); ++i)
{
std::vector<AtlasMessage::ObjectID>::iterator it = std::find(g_SelectedObjects.begin(), g_SelectedObjects.end(), ids[i]);
bool objectIsSelected = (it != g_SelectedObjects.end());
if (selectionRemove)
{
// Remove from selection
if (objectIsSelected)
g_SelectedObjects.erase(it);
}
else if (!objectIsSelected)
{
// Add to selection
g_SelectedObjects.push_back(ids[i]);
}
}
}
g_SelectedObjects.NotifyObservers();
POST_MESSAGE(SetSelectionPreview, (g_SelectedObjects));
SET_STATE(Waiting);
return true;
}
else
return false;
}
}
SelectSimilar;
};
IMPLEMENT_DYNAMIC_CLASS(TransformObject, StateDrivenTool<TransformObject>);

View File

@ -18,6 +18,7 @@
#include "precompiled.h"
#include <cfloat>
#include <map>
#include "MessageHandler.h"
#include "../CommandProc.h"
@ -31,7 +32,6 @@
#include "graphics/ObjectManager.h"
#include "graphics/Terrain.h"
#include "graphics/Unit.h"
#include "graphics/UnitManager.h"
#include "lib/ogl.h"
#include "maths/MathUtil.h"
#include "maths/Matrix3D.h"
@ -43,8 +43,11 @@
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpPlayer.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpSelectable.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/helpers/Selection.h"
namespace AtlasMessage {
@ -55,22 +58,6 @@ namespace
{
return wcscmp(a.name.c_str(), b.name.c_str()) < 0;
}
bool IsFloating(const CUnit* unit)
{
if (! unit)
return false;
CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), unit->GetID());
if (!cmpPosition)
return false;
return cmpPosition->IsFloating();
}
CUnitManager& GetUnitManager()
{
return g_Game->GetWorld()->GetUnitManager();
}
}
QUERYHANDLER(GetObjectsList)
@ -111,20 +98,57 @@ static std::vector<ObjectID> g_Selection;
MESSAGEHANDLER(SetSelectionPreview)
{
CSimulation2& sim = *g_Game->GetSimulation2();
// Cache player colours for performance
typedef std::map<player_id_t, CColor> PlayerColourMap;
PlayerColourMap playerColours;
CmpPtr<ICmpPlayerManager> cmpPlayerManager(sim, SYSTEM_ENTITY);
// Clear old selection rings
for (size_t i = 0; i < g_Selection.size(); ++i)
{
CmpPtr<ICmpSelectable> cmpSelectable(*g_Game->GetSimulation2(), g_Selection[i]);
CmpPtr<ICmpSelectable> cmpSelectable(sim, g_Selection[i]);
if (cmpSelectable)
cmpSelectable->SetSelectionHighlight(CColor(1, 1, 1, 0));
}
g_Selection = *msg->ids;
// Set new selection rings
for (size_t i = 0; i < g_Selection.size(); ++i)
{
CmpPtr<ICmpSelectable> cmpSelectable(*g_Game->GetSimulation2(), g_Selection[i]);
if (cmpSelectable)
cmpSelectable->SetSelectionHighlight(CColor(1, 1, 1, 1));
entity_id_t ent = g_Selection[i];
CmpPtr<ICmpSelectable> cmpSelectable(sim, ent);
if (!cmpSelectable)
continue;
// Default to white for ownerless entities
CColor colour(1.0f, 1.0f, 1.0f, 1.0f);
CmpPtr<ICmpOwnership> cmpOwnership(sim, ent);
if (cmpOwnership && cmpPlayerManager)
{
player_id_t owner = cmpOwnership->GetOwner();
if (playerColours.find(owner) != playerColours.end())
{
colour = playerColours[owner];
}
else
{
// Add colour to cache
entity_id_t playerEnt = cmpPlayerManager->GetPlayerByID(owner);
CmpPtr<ICmpPlayer> cmpPlayer(sim, playerEnt);
if (cmpPlayer)
{
colour = cmpPlayer->GetColour();
playerColours[owner] = colour;
}
}
}
cmpSelectable->SetSelectionHighlight(colour);
}
}
@ -401,71 +425,129 @@ QUERYHANDLER(PickObject)
float x, y;
msg->pos->GetScreenSpace(x, y);
CVector3D rayorigin, raydir;
g_Game->GetView()->GetCamera()->BuildCameraRay((int)x, (int)y, rayorigin, raydir);
// Normally this function would be called with a player ID to check LOS,
// but in Atlas the entire map is revealed, so just pass INVALID_PLAYER
std::vector<AtlasMessage::ObjectID> ents = EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, INVALID_PLAYER, msg->selectActors);
CUnit* target = GetUnitManager().PickUnit(rayorigin, raydir);
if (target)
msg->id = target->GetID();
else
msg->id = INVALID_ENTITY;
if (target)
// Multiple entities may have been picked, but they are sorted by distance,
// so only take the first one
if (!ents.empty())
{
// Get screen coordinates of the point on the ground underneath the
// object's model-centre, so that callers know the offset to use when
// working out the screen coordinates to move the object to.
CVector3D centre = target->GetModel().GetTransform().GetTranslation();
msg->id = ents[0];
centre.Y = g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(centre.X, centre.Z);
if (IsFloating(target))
centre.Y = std::max(centre.Y, g_Renderer.GetWaterManager()->m_WaterHeight);
// Calculate offset of object from original mouse click position
// so it gets moved by that offset
CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), (entity_id_t)ents[0]);
if (!cmpPosition || !cmpPosition->IsInWorld())
{
// error
msg->offsetx = msg->offsety = 0;
}
else
{
CFixedVector3D fixed = cmpPosition->GetPosition();
CVector3D centre = CVector3D(fixed.X.ToFloat(), fixed.Y.ToFloat(), fixed.Z.ToFloat());
float cx, cy;
g_Game->GetView()->GetCamera()->GetScreenCoordinates(centre, cx, cy);
float cx, cy;
g_Game->GetView()->GetCamera()->GetScreenCoordinates(centre, cx, cy);
msg->offsetx = (int)(cx - x);
msg->offsety = (int)(cy - y);
msg->offsetx = (int)(cx - x);
msg->offsety = (int)(cy - y);
}
}
else
{
msg->offsetx = msg->offsety = 0;
// No entity picked
msg->id = INVALID_ENTITY;
}
}
BEGIN_COMMAND(MoveObject)
QUERYHANDLER(PickObjectsInRect)
{
CVector3D m_PosOld, m_PosNew;
float x0, y0, x1, y1;
msg->start->GetScreenSpace(x0, y0);
msg->end->GetScreenSpace(x1, y1);
// Since owner selections are meaningless in Atlas, use INVALID_PLAYER
msg->ids = EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, INVALID_PLAYER, msg->selectActors);
}
QUERYHANDLER(PickSimilarObjects)
{
CmpPtr<ICmpTemplateManager> cmpTemplateManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
ENSURE(cmpTemplateManager);
std::string templateName = cmpTemplateManager->GetCurrentTemplateName((entity_id_t)msg->id);
// Since owner selections are meaningless in Atlas, use INVALID_PLAYER
msg->ids = EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, INVALID_PLAYER, false, true, true);
}
BEGIN_COMMAND(MoveObjects)
{
// Mapping from object to position
typedef std::map<AtlasMessage::ObjectID, CVector3D> ObjectPositionMap;
ObjectPositionMap m_PosOld, m_PosNew;
void Do()
{
CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), (entity_id_t)msg->id);
if (!cmpPosition)
{
// error
m_PosOld = m_PosNew = CVector3D(0, 0, 0);
}
else
{
m_PosNew = GetUnitPos(msg->pos, cmpPosition->IsFloating());
std::vector<ObjectID> ids = *msg->ids;
CFixedVector3D pos = cmpPosition->GetPosition();
m_PosOld = CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat());
// All selected objects move relative to a pivot object,
// so get its position and whether it's floating
CVector3D pivotPos(0, 0, 0);
bool pivotFloating = false;
CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), (entity_id_t)msg->pivot);
if (cmpPosition && cmpPosition->IsInWorld())
{
pivotFloating = cmpPosition->IsFloating();
CFixedVector3D pivotFixed = cmpPosition->GetPosition();
pivotPos = CVector3D(pivotFixed.X.ToFloat(), pivotFixed.Y.ToFloat(), pivotFixed.Z.ToFloat());
}
// Calculate directional vector of movement for pivot object,
// we apply the same movement to all objects
CVector3D targetPos = GetUnitPos(msg->pos, pivotFloating);
CVector3D dir = targetPos - pivotPos;
for (size_t i = 0; i < ids.size(); ++i)
{
entity_id_t id = (entity_id_t)ids[i];
CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), id);
if (!cmpPosition || !cmpPosition->IsInWorld())
{
// error
m_PosOld[id] = m_PosNew[id] = CVector3D(0, 0, 0);
}
else
{
// Calculate this object's position
CFixedVector3D posFixed = cmpPosition->GetPosition();
CVector3D pos = CVector3D(posFixed.X.ToFloat(), posFixed.Y.ToFloat(), posFixed.Z.ToFloat());
m_PosNew[id] = pos + dir;
m_PosOld[id] = pos;
}
}
SetPos(m_PosNew);
}
void SetPos(CVector3D& pos)
void SetPos(ObjectPositionMap& map)
{
CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), (entity_id_t)msg->id);
if (!cmpPosition)
return;
ObjectPositionMap::iterator it;
for (it = map.begin(); it != map.end(); ++it)
{
CmpPtr<ICmpPosition> cmpPosition(*g_Game->GetSimulation2(), (entity_id_t)it->first);
if (!cmpPosition)
return;
cmpPosition->JumpTo(entity_pos_t::FromFloat(pos.X), entity_pos_t::FromFloat(pos.Z));
// Set 2D position, ignoring height
CVector3D pos = it->second;
cmpPosition->JumpTo(entity_pos_t::FromFloat(pos.X), entity_pos_t::FromFloat(pos.Z));
}
}
void Redo()
@ -478,14 +560,14 @@ BEGIN_COMMAND(MoveObject)
SetPos(m_PosOld);
}
void MergeIntoPrevious(cMoveObject* prev)
void MergeIntoPrevious(cMoveObjects* prev)
{
// TODO: do something valid if prev unit != this unit
ENSURE(prev->msg->id == msg->id);
// TODO: do something valid if prev selection != this selection
ENSURE(*(prev->msg->ids) == *(msg->ids));
prev->m_PosNew = m_PosNew;
}
};
END_COMMAND(MoveObject)
END_COMMAND(MoveObjects)
BEGIN_COMMAND(RotateObject)
@ -645,4 +727,9 @@ QUERYHANDLER(GetPlayerObjects)
msg->ids = ids;
}
MESSAGEHANDLER(SetBandbox)
{
View::GetView_Game()->SetBandbox(msg->show, (float)msg->sx0, (float)msg->sy0, (float)msg->sx1, (float)msg->sy1);
}
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -484,15 +484,31 @@ COMMAND(FillTerrain, NOMERGE,
//////////////////////////////////////////////////////////////////////////
QUERY(PickObject,
((Position, pos))
,
((ObjectID, id))
((int, offsetx)) // offset of object centre from input position
((int, offsety)) //
);
COMMAND(MoveObject, MERGE,
((Position, pos))
((bool, selectActors))
,
((ObjectID, id))
((int, offsetx)) // offset of object centre from input position
((int, offsety)) //
);
QUERY(PickObjectsInRect,
((Position, start))
((Position, end))
((bool, selectActors))
,
((std::vector<ObjectID>, ids))
);
QUERY(PickSimilarObjects,
((ObjectID, id))
,
((std::vector<ObjectID>, ids))
);
COMMAND(MoveObjects, MERGE,
((std::vector<ObjectID>, ids))
((ObjectID, pivot))
((Position, pos))
);
@ -512,11 +528,11 @@ MESSAGE(SetSelectionPreview,
);
QUERY(GetObjectSettings,
((int, view)) // eRenderView
((ObjectID, id))
,
((sObjectSettings, settings))
);
((int, view)) // eRenderView
((ObjectID, id))
,
((sObjectSettings, settings))
);
COMMAND(SetObjectSettings, NOMERGE,
((int, view)) // eRenderView
@ -530,6 +546,14 @@ QUERY(GetPlayerObjects,
((std::vector<ObjectID>, ids))
);
MESSAGE(SetBandbox,
((bool, show))
((int, sx0))
((int, sy0))
((int, sx1))
((int, sy1))
);
//////////////////////////////////////////////////////////////////////////
QUERY(GetCinemaPaths,

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -84,7 +84,7 @@ SHAREABLE_STRUCT(Colour);
typedef size_t ObjectID;
inline bool ObjectIDIsValid(ObjectID id) { return (id != ~(size_t)0); }
inline bool ObjectIDIsValid(ObjectID id) { return (id != 0); }
struct sCinemaSplineNode

View File

@ -166,7 +166,7 @@ static void delete_pair_2nd(std::pair<T,S> v)
}
ViewGame::ViewGame()
: m_SpeedMultiplier(0.f)
: m_SpeedMultiplier(0.f)
{
ENSURE(g_Game);
}
@ -262,6 +262,49 @@ void ViewGame::Render()
Atlas_GLSwapBuffers((void*)g_GameLoop->glCanvas);
}
void ViewGame::DrawOverlays()
{
#if CONFIG2_GLES
#warning TODO: implement Atlas game overlays for GLES
#else
// Set up transform for overlays
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
CMatrix3D transform;
transform.SetIdentity();
transform.Scale(1.0f, -1.f, 1.0f);
transform.Translate(0.0f, (float)g_yres, -1000.0f);
CMatrix3D proj;
proj.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f);
transform = proj * transform;
glLoadMatrixf(&transform._11);
if (m_BandboxArray.size() > 0)
{
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
// Render bandbox as array of lines
glVertexPointer(2, GL_FLOAT, sizeof(SBandboxVertex), &m_BandboxArray[0].x);
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(SBandboxVertex), &m_BandboxArray[0].r);
glDrawArrays(GL_LINES, 0, m_BandboxArray.size());
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
}
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
#endif
}
void ViewGame::SetParam(const std::wstring& name, bool value)
{
if (name == L"priorities")
@ -353,6 +396,39 @@ std::wstring ViewGame::DumpState(bool binary)
}
}
void ViewGame::SetBandbox(bool visible, float x0, float y0, float x1, float y1)
{
m_BandboxArray.clear();
if (visible)
{
// Make sure corners are arranged in correct order
if (x0 > x1)
std::swap(x0, x1);
if (y0 > y1)
std::swap(y0, y1);
// Bandbox is draw as lines comprising two rectangles
SBandboxVertex vert[] = {
// Black - outer rectangle
SBandboxVertex(x0, y0, 0, 0, 0, 255), SBandboxVertex(x1, y0, 0, 0, 0, 255), SBandboxVertex(x1, y1, 0, 0, 0, 255), SBandboxVertex(x0, y1, 0, 0, 0, 255),
// White - inner rectangle
SBandboxVertex(x0+1.0f, y0+1.0f, 255, 255, 255, 255), SBandboxVertex(x1-1.0f, y0+1.0f, 255, 255, 255, 255), SBandboxVertex(x1-1.0f, y1-1.0f, 255, 255, 255, 255), SBandboxVertex(x0+1.0f, y1-1.0f, 255, 255, 255, 255)
};
for (size_t i = 0; i < 4; ++i)
{
m_BandboxArray.push_back(vert[i]);
m_BandboxArray.push_back(vert[(i+1)%4]);
}
for (size_t i = 0; i < 4; ++i)
{
m_BandboxArray.push_back(vert[i+4]);
m_BandboxArray.push_back(vert[(i+1)%4+4]);
}
}
}
//////////////////////////////////////////////////////////////////////////
ViewNone* view_None = NULL;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -37,6 +37,7 @@ public:
virtual ~View();
virtual void Update(float UNUSED(frameLength)) { };
virtual void Render() { };
virtual void DrawOverlays() { };
virtual CCamera& GetCamera() = 0;
virtual CSimulation2* GetSimulation2() { return NULL; }
virtual entity_id_t GetEntityId(AtlasMessage::ObjectID obj) { return (entity_id_t)obj; }
@ -76,6 +77,7 @@ public:
virtual ~ViewGame();
virtual void Update(float frameLength);
virtual void Render();
virtual void DrawOverlays();
virtual CCamera& GetCamera();
virtual CSimulation2* GetSimulation2();
virtual bool WantsHighFramerate();
@ -87,11 +89,21 @@ public:
void SaveState(const std::wstring& label);
void RestoreState(const std::wstring& label);
std::wstring DumpState(bool binary);
void SetBandbox(bool visible, float x0, float y0, float x1, float y1);
private:
float m_SpeedMultiplier;
std::map<std::wstring, SimState*> m_SavedStates;
std::string m_DisplayPassability;
typedef struct SBandboxVertex
{
SBandboxVertex(float x, float y, u8 r, u8 g, u8 b, u8 a) : x(x), y(y), r(r), g(g), b(b), a(a) {}
u8 r, g, b, a;
float x, y;
} SBandboxVertex;
std::vector<SBandboxVertex> m_BandboxArray;
};
class ActorViewer;