1
0
forked from 0ad/0ad

Rendering marker lines between buildings and rally points

Added command button to focus on rally point
(implements #524)

This was SVN commit r10704.
This commit is contained in:
vts 2011-12-10 07:07:04 +00:00
parent fbb0e3995f
commit db864f10c3
40 changed files with 2127 additions and 233 deletions

View File

@ -8,8 +8,23 @@
</variant>
</group>
<group>
<variant name="waypoint_hellenes">
<variant name="hele">
<texture>props/banner_greek.png</texture>
</variant>
<variant name="pers">
<texture>props/banner_persian.png</texture>
</variant>
<variant name="celt">
<texture>props/banner_celt.png</texture>
</variant>
<variant name="cart">
<texture>props/banner_carthage.png</texture>
</variant>
<variant name="iber">
<texture>props/banner_iberians.png</texture>
</variant>
<variant name="rome">
<texture>props/banner_romans.png</texture>
</variant>
</group>
</actor>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -135,7 +135,6 @@ function getActionInfo(action, target)
return entState && entState.rallyPoint;
});
if (!target)
{
if (action == "set-rallypoint" && haveRallyPoints)
@ -1114,6 +1113,24 @@ function performCommand(entity, commandName)
break;
case "unload-all":
unloadAll(entity);
break;
case "focus-rally":
// if the selected building has a rally point set, move the camera to it; otherwise, move to the building itself
// (since that's where units will spawn without a rally point)
var focusTarget = null;
if (entState.rallyPoint && entState.rallyPoint.position)
{
focusTarget = entState.rallyPoint.position;
}
else
{
if (entState.position)
focusTarget = entState.position;
}
if (focusTarget !== null)
Engine.CameraMoveTo(focusTarget.x, focusTarget.z);
break;
default:
break;

View File

@ -270,7 +270,8 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
break;
case COMMAND:
if (item == "unload-all")
// here, "item" is an object with properties .name (command name), .tooltip and .icon (relative to session/icons/single)
if (item.name == "unload-all")
{
var count = unitEntState.garrisonHolder.entities.length;
getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 0 ? count : "");
@ -280,7 +281,7 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = "";
}
tooltip = toTitleCase(item);
tooltip = (item.tooltip ? item.tooltip : toTitleCase(item.name));
break;
default:
@ -334,7 +335,7 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
{
//icon.cell_id = i;
//icon.cell_id = getCommandCellId(item);
icon.sprite = "stretched:session/icons/single/" + getCommandImage(item);
icon.sprite = "stretched:session/icons/single/" + item.icon;
}
else if (template.icon)
@ -477,7 +478,7 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s
var commands = getEntityCommandsList(entState);
if (commands.length)
setupUnitPanel("Command", usedPanels, entState, commands,
function (item) { performCommand(entState.id, item); } );
function (item) { performCommand(entState.id, item.name); } );
if (entState.garrisonHolder)
{

View File

@ -189,23 +189,6 @@ function getFormationCellId(formationName)
}
}
function getCommandImage(commandName)
{
switch (commandName)
{
case "delete":
return "kill_small.png";
case "unload-all":
return "garrison-out.png";
case "garrison":
return "garrison.png";
case "repair":
return "repair.png";
default:
return "";
}
}
function getEntityFormationsList(entState)
{
var civ = g_Players[entState.player].civ;
@ -234,12 +217,47 @@ function getEntityCommandsList(entState)
{
var commands = [];
if (entState.garrisonHolder)
commands.push("unload-all");
{
commands.push({
"name": "unload-all",
"tooltip": "Unload All",
"icon": "garrison-out.png"
});
}
commands.push({
"name": "delete",
"tooltip": "Delete",
"icon": "kill_small.png"
});
if (isUnit(entState))
commands.push("garrison");
{
commands.push({
"name": "garrison",
"tooltip": "Garrison",
"icon": "garrison.png"
});
}
if (entState.buildEntities)
commands.push("repair");
commands.push("delete");
{
commands.push({
"name": "repair",
"tooltip": "Repair",
"icon": "repair.png"
});
}
if (entState.rallyPoint)
{
commands.push({
"name": "focus-rally",
"tooltip": "Focus on Rally Point",
"icon": "focus-rally.png"
});
}
return commands;
}

View File

@ -3,18 +3,25 @@ PARAM objectColor = program.local[0];
TEMP base;
TEMP mask;
TEMP color;
TEMP los;
// Combine base texture and color, using mask texture
TEX base, fragment.texcoord[0], texture[0], 2D;
TEX mask, fragment.texcoord[0], texture[1], 2D;
LRP color.rgb, mask, objectColor, base;
// Multiply by LOS texture
TEX los, fragment.texcoord[1], texture[2], 2D;
MUL result.color.rgb, color, los.a;
#ifdef IGNORE_LOS
MOV result.color.rgb, color;
#else
// Multiply RGB by LOS texture (alpha channel)
TEMP los;
TEX los, fragment.texcoord[1], texture[2], 2D;
MUL result.color.rgb, color, los.a;
#endif
// Use alpha from base texture
// Use alpha from base texture, combined with the object color alpha.
// The latter is usually 1, so this basically comes down to base.a
MUL result.color.a, objectColor.a, base.a;
END

View File

@ -26,6 +26,10 @@ GuiInterface.prototype.Init = function()
/*
* All of the functions defined below are called via Engine.GuiInterfaceCall(name, arg)
* from GUI scripts, and executed here with arguments (player, arg).
*
* CAUTION: The input to the functions in this module is not network-synchronised, so it
* mustn't affect the simulation state (i.e. the data that is serialised and can affect
* the behaviour of the rest of the simulation) else it'll cause out-of-sync errors.
*/
/**
@ -223,7 +227,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (cmpRallyPoint)
{
ret.rallyPoint = { };
ret.rallyPoint = {'position': cmpRallyPoint.GetPosition()}; // undefined or {x,z} object
}
var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
@ -418,62 +422,62 @@ GuiInterface.prototype.SetStatusBars = function(player, cmd)
};
/**
* Displays the rally point of a building
* Displays the rally point of a given list of entities (carried in cmd.entities).
*
* The 'cmd' object may carry its own x/z coordinate pair indicating the location where the rally point should
* be rendered, in order to support instantaneously rendering a rally point marker at a specified location
* instead of incurring a delay while PostNetworkCommand processes the set-rallypoint command (see input.js).
* If cmd doesn't carry a custom location, then the position to render the marker at will be read from the
* RallyPoint component.
*/
GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
{
// If there are rally points already displayed, destroy them
for each (var ent in this.rallyPoints)
{
// Hide it first (the destruction won't be instantaneous)
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
cmpPosition.MoveOutOfWorld();
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(player), IID_Player);
Engine.DestroyEntity(ent);
// If there are some rally points already displayed, first hide them
for each (var ent in this.entsRallyPointsDisplayed)
{
var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
if (cmpRallyPointRenderer)
cmpRallyPointRenderer.SetDisplayed(false);
}
this.rallyPoints = [];
var positions = [];
// DisplayRallyPoints is called passing a list of entities for which
// rally points must be displayed
// Show the rally points for the passed entities
for each (var ent in cmd.entities)
{
var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
if (!cmpRallyPointRenderer)
continue;
// entity must have a rally point component to display a rally point marker
// (regardless of whether cmd specifies a custom location)
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (!cmpRallyPoint)
continue;
// Verify the owner
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
continue;
if (!(cmpPlayer && cmpPlayer.CanControlAllUnits()))
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
continue;
// If the command was passed an explicit position, use that and
// override the real rally point position; otherwise use the real position
var pos;
if (cmd.x && cmd.z)
pos = {"x": cmd.x, "z": cmd.z};
pos = cmd;
else
pos = cmpRallyPoint.GetPosition();
pos = cmpRallyPoint.GetPosition(); // may return undefined
if (pos)
{
// TODO: it'd probably be nice if we could draw some kind of line
// between the building and pos, to make the marker easy to find even
// if it's a long way from the building
cmpRallyPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
positions.push(pos);
}
cmpRallyPointRenderer.SetDisplayed(true);
}
// Add rally point entity for each building
for each (var pos in positions)
{
var rallyPoint = Engine.AddLocalEntity("actor|props/special/common/waypoint_flag.xml");
var cmpPosition = Engine.QueryInterface(rallyPoint, IID_Position);
cmpPosition.JumpTo(pos.x, pos.z);
this.rallyPoints.push(rallyPoint);
}
// Remember which entities have their rally points displayed so we can hide them again
this.entsRallyPointsDisplayed = cmd.entities;
};
/**

View File

@ -13,7 +13,7 @@ RallyPoint.prototype.SetPosition = function(x, z)
this.pos = {
"x": x,
"z": z
}
};
};
RallyPoint.prototype.Unset = function()

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity parent="special/actor">
<VisualActor>
<Actor>props/special/common/waypoint_flag.xml</Actor>
</VisualActor>
<Vision>
<AlwaysVisible>true</AlwaysVisible>
</Vision>
</Entity>

View File

@ -34,6 +34,9 @@
<Position>
<Floating>true</Floating>
</Position>
<RallyPointRenderer>
<LinePassabilityClass>ship</LinePassabilityClass>
</RallyPointRenderer>
<Sound>
<SoundGroups>
<select>interface/select/building/sel_dock.xml</select>

View File

@ -53,7 +53,19 @@
<DisableBlockPathfinding>false</DisableBlockPathfinding>
</Obstruction>
<OverlayRenderer/>
<RallyPoint/>
<RallyPoint />
<RallyPointRenderer>
<MarkerTemplate>special/rallypoint</MarkerTemplate>
<LineTexture>art/textures/misc/rallypoint_line.png</LineTexture>
<LineTextureMask>art/textures/misc/rallypoint_line_mask.png</LineTextureMask>
<LineThickness>0.2</LineThickness>
<LineColour r="35" g="86" b="188" />
<LineDashColour r="158" g="11" b="15" />
<LineStartCap>square</LineStartCap>
<LineEndCap>round</LineEndCap>
<LineCostClass>default</LineCostClass>
<LinePassabilityClass>default</LinePassabilityClass>
</RallyPointRenderer>
<Sound>
<SoundGroups>
<select>interface/select/building/sel_universal.xml</select>

View File

@ -36,6 +36,9 @@
<Position>
<Floating>true</Floating>
</Position>
<RallyPointRenderer>
<LinePassabilityClass>ship</LinePassabilityClass>
</RallyPointRenderer>
<ResourceDropsite>
<Types>food wood stone metal</Types>
</ResourceDropsite>

View File

@ -899,7 +899,7 @@ void CGameView::Update(float DeltaTime)
m->ViewCamera.UpdateFrustum();
}
void CGameView::MoveCameraTarget(const CVector3D& target, bool minimap)
void CGameView::MoveCameraTarget(const CVector3D& target)
{
// Maintain the same orientation and level of zoom, if we can
// (do this by working out the point the camera is looking at, saving
@ -912,11 +912,6 @@ void CGameView::MoveCameraTarget(const CVector3D& target, bool minimap)
CVector3D pivot = targetCam.GetFocus();
CVector3D delta = target - pivot;
//If minimap movement, maintain previous zoom level by not changing Y position
// - this prevents strange behavior when moving across changes in terrain height
if (!minimap)
m->PosY.SetValueSmoothly(delta.Y + m->PosY.GetValue());
m->PosX.SetValueSmoothly(delta.X + m->PosX.GetValue());
m->PosZ.SetValueSmoothly(delta.Z + m->PosZ.GetValue());

View File

@ -79,7 +79,7 @@ public:
InReaction HandleEvent(const SDL_Event_* ev);
void MoveCameraTarget(const CVector3D& target, bool minimap = false);
void MoveCameraTarget(const CVector3D& target);
void ResetCameraTarget(const CVector3D& target);
void ResetCameraAngleZoom();
void CameraFollow(entity_id_t entity, bool firstPerson);

View File

@ -0,0 +1,37 @@
/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "ps/CStr.h"
#include "Overlay.h"
SOverlayTexturedLine::LineCapType SOverlayTexturedLine::StrToLineCapType(const std::wstring& str)
{
if (str == L"round")
return LINECAP_ROUND;
else if (str == L"sharp")
return LINECAP_SHARP;
else if (str == L"square")
return LINECAP_SQUARE;
else if (str == L"flat")
return LINECAP_FLAT;
else {
debug_warn(L"[Overlay] Unrecognized line cap type identifier");
return LINECAP_FLAT;
}
}

View File

@ -49,16 +49,43 @@ struct SOverlayLine
*/
struct SOverlayTexturedLine
{
SOverlayTexturedLine() : m_Terrain(NULL), m_Thickness(1.0f) { }
enum LineCapType
{
LINECAP_FLAT, ///< no line ending; abrupt stop of the line (aka. butt ending)
/**
* Semi-circular line ending. The texture is mapped by curving the left vertical edge around the semi-circle's rim. That is,
* the center point has UV coordinates (0.5;0.5), and the rim vertices all have U coordinate 0 and a V coordinate that ranges
* from 0 to 1 as the rim is traversed.
*/
LINECAP_ROUND,
LINECAP_SHARP, ///< sharp point ending
LINECAP_SQUARE, ///< square end that extends half the line width beyond the line end
};
SOverlayTexturedLine()
: m_Terrain(NULL), m_Thickness(1.0f), m_Closed(false), m_AlwaysVisible(false), m_StartCapType(LINECAP_FLAT), m_EndCapType(LINECAP_FLAT)
{}
CTerrain* m_Terrain;
CTexturePtr m_TextureBase;
CTexturePtr m_TextureMask;
CColor m_Color;
std::vector<float> m_Coords; // (x, z) vertex coordinate pairs; y is computed automatically; shape is automatically closed
float m_Thickness; // world-space units
CColor m_Color; ///< Color to apply to the line texture
std::vector<float> m_Coords; ///< (x, z) vertex coordinate pairs; y is computed automatically
float m_Thickness; ///< Half-width of the line, in world-space units
shared_ptr<CRenderData> m_RenderData; // cached renderer data (shared_ptr so that copies/deletes are automatic)
bool m_Closed; ///< Should this line be treated as a closed loop? (if set, the end cap settings are ignored)
bool m_AlwaysVisible; ///< Should this line be rendered even under the SoD?
LineCapType m_StartCapType; ///< LineCapType to be used at the start of the line
LineCapType m_EndCapType; ///< LineCapType to be used at the end of the line
shared_ptr<CRenderData> m_RenderData; ///< Cached renderer data (shared_ptr so that copies/deletes are automatic)
/**
* Converts a string line cap type into its corresponding LineCap enum value, and returns the resulting value.
* If the input string is unrecognized, a warning is issued and a default value is returned.
*/
static LineCapType StrToLineCapType(const std::wstring& str);
};
/**

View File

@ -80,7 +80,7 @@ bool CShaderManager::NewProgram(const char* name, const std::map<CStr, CStr>& ba
if (strncmp(name, "fixed:", 6) == 0)
{
program = CShaderProgramPtr(CShaderProgram::ConstructFFP(name+6));
program = CShaderProgramPtr(CShaderProgram::ConstructFFP(name+6, baseDefines));
if (!program)
return false;
program->Reload();

View File

@ -71,7 +71,7 @@ public:
/**
* Construct an instance of a pre-defined fixed-function pipeline setup.
*/
static CShaderProgram* ConstructFFP(const std::string& id);
static CShaderProgram* ConstructFFP(const std::string& id, const std::map<CStr, CStr>& defines);
typedef const char* attrib_id_t;
typedef const char* texture_id_t;

View File

@ -100,8 +100,10 @@ class CShaderProgramFFP_OverlayLine : public CShaderProgramFFP
ID_objectColor
};
bool m_IgnoreLos;
public:
CShaderProgramFFP_OverlayLine() :
CShaderProgramFFP_OverlayLine(const std::map<CStr, CStr>& defines) :
CShaderProgramFFP(STREAM_POS | STREAM_UV0 | STREAM_UV1)
{
m_UniformIndexes["losTransform"] = ID_losTransform;
@ -111,6 +113,13 @@ public:
m_UniformIndexes["baseTex"] = 0;
m_UniformIndexes["maskTex"] = 1;
m_UniformIndexes["losTex"] = 2;
m_IgnoreLos = (defines.find(CStr("IGNORE_LOS")) != defines.end());
}
bool IsIgnoreLos()
{
return m_IgnoreLos;
}
virtual void Uniform(Binding id, float v0, float v1, float v2, float v3)
@ -145,7 +154,7 @@ public:
// RGB channels:
// Unit 0: Load base texture
// Unit 1: Load mask texture; interpolate with objectColor & base
// Unit 2: Load LOS texture; multiply
// Unit 2: (Load LOS texture; multiply) if not #IGNORE_LOS, pass through otherwise
// Alpha channel:
// Unit 0: Load base texture
// Unit 1: Multiply by objectColor
@ -163,12 +172,15 @@ public:
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
// -----------------------------------------------------------------------------
pglActiveTextureARB(GL_TEXTURE1);
glEnable(GL_TEXTURE_2D);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
// Uniform() sets GL_TEXTURE_ENV_COLOR
// load mask texture; interpolate with objectColor and base; GL_INTERPOLATE takes 3 arguments:
// a0 * a2 + a1 * (1 - a2)
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_INTERPOLATE);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_CONSTANT);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
@ -183,26 +195,41 @@ public:
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA);
// -----------------------------------------------------------------------------
pglActiveTextureARB(GL_TEXTURE2);
glEnable(GL_TEXTURE_2D);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
// Uniform() sets GL_OBJECT_PLANE values
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_ALPHA);
bool ignoreLos = IsIgnoreLos();
if (ignoreLos)
{
// RGB pass through
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
}
else
{
// multiply RGB with LoS texture alpha channel
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
// Uniform() sets GL_OBJECT_PLANE values
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_ALPHA);
}
// alpha pass through
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS);
glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
}
virtual void Unbind()
@ -221,10 +248,10 @@ public:
}
};
/*static*/ CShaderProgram* CShaderProgram::ConstructFFP(const std::string& id)
/*static*/ CShaderProgram* CShaderProgram::ConstructFFP(const std::string& id, const std::map<CStr, CStr>& defines)
{
if (id == "overlayline")
return new CShaderProgramFFP_OverlayLine();
return new CShaderProgramFFP_OverlayLine(defines);
LOGERROR(L"CShaderProgram::ConstructFFP: Invalid id '%hs'", id.c_str());
return NULL;

View File

@ -160,7 +160,7 @@ void CMiniMap::SetCameraPos()
CVector3D target;
GetMouseWorldCoordinates(target.X, target.Z);
target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
g_Game->GetView()->MoveCameraTarget(target, true);
g_Game->GetView()->MoveCameraTarget(target);
}
float CMiniMap::GetAngle()

View File

@ -34,6 +34,7 @@
#include "ps/CConsole.h"
#include "ps/Errors.h"
#include "ps/Game.h"
#include "ps/World.h"
#include "ps/Hotkey.h"
#include "ps/Overlay.h"
#include "ps/ProfileViewer.h"
@ -400,6 +401,23 @@ void CameraFollowFPS(void* UNUSED(cbdata), entity_id_t entityid)
g_Game->GetView()->CameraFollow(entityid, true);
}
/// Move camera to a 2D location
void CameraMoveTo(void* UNUSED(cbdata), entity_pos_t x, entity_pos_t z)
{
// called from JS; must not fail
if(!(g_Game && g_Game->GetWorld() && g_Game->GetView() && g_Game->GetWorld()->GetTerrain()))
return;
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
CVector3D target;
target.X = x.ToFloat();
target.Z = z.ToFloat();
target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
g_Game->GetView()->MoveCameraTarget(target);
}
entity_id_t GetFollowedEntity(void* UNUSED(cbdata))
{
if (g_Game && g_Game->GetView())
@ -519,6 +537,7 @@ void QuickLoad(void* UNUSED(cbdata))
{
g_Game->GetTurnManager()->QuickLoad();
}
void SetBoundingBoxDebugOverlay(void* UNUSED(cbdata), bool enabled)
{
ICmpSelectable::ms_EnableDebugOverlays = enabled;
@ -574,6 +593,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<CScriptVal, &GetMapSettings>("GetMapSettings");
scriptInterface.RegisterFunction<void, entity_id_t, &CameraFollow>("CameraFollow");
scriptInterface.RegisterFunction<void, entity_id_t, &CameraFollowFPS>("CameraFollowFPS");
scriptInterface.RegisterFunction<void, entity_pos_t, entity_pos_t, &CameraMoveTo>("CameraMoveTo");
scriptInterface.RegisterFunction<entity_id_t, &GetFollowedEntity>("GetFollowedEntity");
scriptInterface.RegisterFunction<bool, std::string, &HotkeyIsPressed_>("HotkeyIsPressed");
scriptInterface.RegisterFunction<void, std::wstring, &DisplayErrorDialog>("DisplayErrorDialog");

View File

@ -127,6 +127,32 @@ public:
return CVector2D(X / mag, Y / mag);
}
/**
* Returns a version of this vector rotated counterclockwise by @p angle radians.
*/
CVector2D Rotated(float angle)
{
float c = cosf(angle);
float s = sinf(angle);
return CVector2D(
c*X - s*Y,
s*X + c*Y
);
}
/**
* Rotates this vector counterclockwise by @p angle radians.
*/
void Rotate(float angle)
{
float c = cosf(angle);
float s = sinf(angle);
float newX = c*X - s*Y;
float newY = s*X + c*Y;
X = newX;
Y = newY;
}
public:
float X, Y;
};

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -19,9 +19,11 @@
#include "OverlayRenderer.h"
#include "maths/MathUtil.h"
#include "maths/Quaternion.h"
#include "maths/Vector2D.h"
#include "graphics/LOSTexture.h"
#include "graphics/Overlay.h"
#include "graphics/ShaderManager.h"
#include "graphics/Terrain.h"
#include "graphics/TextureManager.h"
#include "lib/ogl.h"
@ -44,9 +46,8 @@ class CTexturedLineRData : public CRenderData
{
public:
CTexturedLineRData(SOverlayTexturedLine* line) :
m_Line(line), m_VB(NULL), m_VBIndices(NULL)
{
}
m_Line(line), m_VB(NULL), m_VBIndices(NULL), m_Raise(.2f)
{ }
~CTexturedLineRData()
{
@ -58,18 +59,40 @@ public:
struct SVertex
{
SVertex(CVector3D pos, short u, short v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; }
SVertex(CVector3D pos, float u, float v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; }
CVector3D m_Position;
GLshort m_UVs[2];
GLfloat m_UVs[2];
float _padding[3]; // 5 floats up till now, so pad with another 3 floats to get a power of 2
};
cassert(sizeof(SVertex) == 16);
cassert(sizeof(SVertex) == 32);
void Update();
SOverlayTexturedLine* m_Line;
/**
* Creates a line cap of the specified type @p endCapType at the end of the segment going in direction @p normal, and appends
* the vertices to @p verticesOut in GL_TRIANGLES order.
*
* @param corner1 One of the two butt-end corner points of the line to which the cap should be attached.
* @param corner2 One of the two butt-end corner points of the line to which the cap should be attached.
* @param normal Normal vector indicating the direction of the segment to which the cap should be attached.
* @param endCapType The type of end cap to produce.
* @param verticesOut Output vector of vertices for passing to the renderer.
* @param indicesOut Output vector of vertex indices for passing to the renderer.
*/
void CreateLineCap(const CVector3D& corner1, const CVector3D& corner2, const CVector3D& normal,
SOverlayTexturedLine::LineCapType endCapType, std::vector<SVertex>& verticesOut, std::vector<u16>& indicesOut);
/// Small utility function; grabs the centroid of the positions of two vertices
inline CVector3D Centroid(const SVertex& v1, const SVertex& v2)
{
return (v1.m_Position + v2.m_Position) * 0.5;
}
SOverlayTexturedLine* m_Line;
CVertexBuffer::VBChunk* m_VB;
CVertexBuffer::VBChunk* m_VBIndices;
float m_Raise; // small vertical offset of line from terrain to prevent visual glitches
};
OverlayRenderer::OverlayRenderer()
@ -180,69 +203,38 @@ void OverlayRenderer::RenderOverlaysAfterWater()
else
shaderName = "fixed:overlayline";
CShaderManager& shaderManager = g_Renderer.GetShaderManager();
CShaderProgramPtr shaderTexLine(shaderManager.LoadProgram(shaderName, std::map<CStr, CStr>()));
shaderTexLine->Bind();
int streamflags = shaderTexLine->GetStreamFlags();
if (streamflags & STREAM_POS)
glEnableClientState(GL_VERTEX_ARRAY);
if (streamflags & STREAM_UV0)
{
pglClientActiveTextureARB(GL_TEXTURE0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
if (streamflags & STREAM_UV1)
{
pglClientActiveTextureARB(GL_TEXTURE1);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
std::map<CStr, CStr> defAlwaysVisible;
defAlwaysVisible.insert(std::make_pair(CStr("IGNORE_LOS"), CStr("1")));
CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture();
shaderTexLine->BindTexture("losTex", los.GetTexture());
shaderTexLine->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
for (size_t i = 0; i < m->texlines.size(); ++i)
{
SOverlayTexturedLine* line = m->texlines[i];
if (!line->m_RenderData)
continue;
CShaderManager& shaderManager = g_Renderer.GetShaderManager();
CShaderProgramPtr shaderTexLineNormal(shaderManager.LoadProgram(shaderName, std::map<CStr, CStr>()));
CShaderProgramPtr shaderTexLineAlwaysVisible(shaderManager.LoadProgram(shaderName, defAlwaysVisible));
shaderTexLine->BindTexture("baseTex", line->m_TextureBase->GetHandle());
shaderTexLine->BindTexture("maskTex", line->m_TextureMask->GetHandle());
shaderTexLine->Uniform("objectColor", line->m_Color);
// ----------------------------------------------------------------------------------------
CTexturedLineRData* rdata = static_cast<CTexturedLineRData*>(line->m_RenderData.get());
shaderTexLineNormal->Bind();
shaderTexLineNormal->BindTexture("losTex", los.GetTexture());
shaderTexLineNormal->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
GLsizei stride = sizeof(CTexturedLineRData::SVertex);
CTexturedLineRData::SVertex* base = reinterpret_cast<CTexturedLineRData::SVertex*>(rdata->m_VB->m_Owner->Bind());
// batch render only the non-always-visible overlay lines using the normal shader
RenderTexturedOverlayLines(shaderTexLineNormal, false);
if (streamflags & STREAM_POS)
glVertexPointer(3, GL_FLOAT, stride, &base->m_Position[0]);
shaderTexLineNormal->Unbind();
if (streamflags & STREAM_UV0)
{
pglClientActiveTextureARB(GL_TEXTURE0);
glTexCoordPointer(2, GL_SHORT, stride, &base->m_UVs[0]);
}
// ----------------------------------------------------------------------------------------
if (streamflags & STREAM_UV1)
{
pglClientActiveTextureARB(GL_TEXTURE1);
glTexCoordPointer(2, GL_SHORT, stride, &base->m_UVs[0]);
}
shaderTexLineAlwaysVisible->Bind();
// TODO: losTex and losTransform are unused in the always visible shader, but I'm not sure if it's worthwhile messing
// with it just to remove these calls
shaderTexLineAlwaysVisible->BindTexture("losTex", los.GetTexture());
shaderTexLineAlwaysVisible->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
u8* indexBase = rdata->m_VBIndices->m_Owner->Bind();
glDrawElements(GL_QUAD_STRIP, rdata->m_VBIndices->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*rdata->m_VBIndices->m_Index);
// batch render only the always-visible overlay lines using the LoS-ignored shader
RenderTexturedOverlayLines(shaderTexLineAlwaysVisible, true);
g_Renderer.GetStats().m_OverlayTris += rdata->m_VBIndices->m_Count - 2;
}
shaderTexLine->Unbind();
shaderTexLineAlwaysVisible->Unbind();
// TODO: the shader should probably be responsible for unbinding its textures
g_Renderer.BindTexture(1, 0);
@ -260,6 +252,69 @@ void OverlayRenderer::RenderOverlaysAfterWater()
}
}
void OverlayRenderer::RenderTexturedOverlayLines(CShaderProgramPtr shaderTexLine, bool alwaysVisible)
{
int streamflags = shaderTexLine->GetStreamFlags();
if (streamflags & STREAM_POS)
glEnableClientState(GL_VERTEX_ARRAY);
if (streamflags & STREAM_UV0)
{
pglClientActiveTextureARB(GL_TEXTURE0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
if (streamflags & STREAM_UV1)
{
pglClientActiveTextureARB(GL_TEXTURE1);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
for (size_t i = 0; i < m->texlines.size(); ++i)
{
SOverlayTexturedLine* line = m->texlines[i];
// render only those lines matching the requested alwaysVisible status
if (!line->m_RenderData || line->m_AlwaysVisible != alwaysVisible)
continue;
shaderTexLine->BindTexture("baseTex", line->m_TextureBase->GetHandle());
shaderTexLine->BindTexture("maskTex", line->m_TextureMask->GetHandle());
shaderTexLine->Uniform("objectColor", line->m_Color);
CTexturedLineRData* rdata = static_cast<CTexturedLineRData*>(line->m_RenderData.get());
if (!rdata->m_VB || !rdata->m_VBIndices)
continue; // might have failed to allocate
// -- render main line quad strip ----------------------
GLsizei stride = sizeof(CTexturedLineRData::SVertex);
CTexturedLineRData::SVertex* vertexBase = reinterpret_cast<CTexturedLineRData::SVertex*>(rdata->m_VB->m_Owner->Bind());
if (streamflags & STREAM_POS)
glVertexPointer(3, GL_FLOAT, stride, &vertexBase->m_Position[0]);
if (streamflags & STREAM_UV0)
{
pglClientActiveTextureARB(GL_TEXTURE0);
glTexCoordPointer(2, GL_FLOAT, stride, &vertexBase->m_UVs[0]);
}
if (streamflags & STREAM_UV1)
{
pglClientActiveTextureARB(GL_TEXTURE1);
glTexCoordPointer(2, GL_FLOAT, stride, &vertexBase->m_UVs[0]);
}
u8* indexBase = rdata->m_VBIndices->m_Owner->Bind();
glDrawElements(GL_TRIANGLES, rdata->m_VBIndices->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*rdata->m_VBIndices->m_Index);
g_Renderer.GetStats().m_OverlayTris += rdata->m_VBIndices->m_Count/3;
}
}
void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera)
{
PROFILE3_GPU("overlays (fg)");
@ -293,7 +348,6 @@ void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera)
};
glVertexPointer(3, GL_FLOAT, sizeof(float)*3, &pos[0].X);
glDrawArrays(GL_QUADS, 0, (GLsizei)4);
}
@ -312,34 +366,40 @@ void CTexturedLineRData::Update()
g_VBMan.Release(m_VB);
m_VB = NULL;
}
if (m_VBIndices)
{
g_VBMan.Release(m_VBIndices);
m_VBIndices = NULL;
}
CTerrain* terrain = m_Line->m_Terrain;
CmpPtr<ICmpWaterManager> cmpWaterManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
float v = 0.f;
std::vector<SVertex> vertices;
std::vector<u16> indices;
short v = 0;
size_t n = m_Line->m_Coords.size() / 2; // number of line points
bool closed = m_Line->m_Closed;
size_t n = m_Line->m_Coords.size() / 2;
ENSURE(n >= 1);
CTerrain* terrain = m_Line->m_Terrain;
// TODO: this assumes paths are closed loops; probably should extend this to
// handle non-closed paths too
ENSURE(n >= 2); // minimum needed to avoid errors (also minimum value to make sense, can't draw a line between 1 point)
// In each iteration, p1 is the position of vertex i, p0 is i-1, p2 is i+1.
// To avoid slightly expensive terrain computations we cycle these around and
// recompute p2 at the end of each iteration.
CVector3D p0 = CVector3D(m_Line->m_Coords[(n-1)*2], 0, m_Line->m_Coords[(n-1)*2+1]);
CVector3D p1 = CVector3D(m_Line->m_Coords[0], 0, m_Line->m_Coords[1]);
CVector3D p2 = CVector3D(m_Line->m_Coords[(1 % n)*2], 0, m_Line->m_Coords[(1 % n)*2+1]);
CVector3D p0;
CVector3D p1(m_Line->m_Coords[0], 0, m_Line->m_Coords[1]);
CVector3D p2(m_Line->m_Coords[(1 % n)*2], 0, m_Line->m_Coords[(1 % n)*2+1]);
if (closed)
// grab the ending point so as to close the loop
p0 = CVector3D(m_Line->m_Coords[(n-1)*2], 0, m_Line->m_Coords[(n-1)*2+1]);
else
// we don't want to loop around and use the direction towards the other end of the line, so create an artificial p0 that
// extends the p2 -> p1 direction, and use that point instead
p0 = p1 + (p1 - p2);
bool p1floating = false;
bool p2floating = false;
@ -386,23 +446,66 @@ void CTexturedLineRData::Update()
if (fabs(l) > 0.000001f) // avoid unlikely divide-by-zero
b *= m_Line->m_Thickness / l;
// Raise off the terrain a little bit
const float raised = 0.2f;
// Push vertices and indices in GL_TRIANGLES order
//
// NOTE: in order for OpenGL to successfully render these, the winding order needs to be correct. Basically, it means
// that every pair of triangles sharing a side must specify the vertices of that side in the opposite order from the
// other triangle.
// (see http://www.opengl.org/resources/code/samples/sig99/advanced99/notes/node16.html for an illustration)
//
// What the code below does is push the indices for a quad composed of two triangles in each iteration. The two triangles
// of each quad are indexed using the winding orders (BR, BL, TR) and (TR, BL, TR) (where BR is bottom-right of this
// iteration's quad, TR top-right etc).
SVertex vertex1(p1 + b + norm*m_Raise, 0.f, v);
SVertex vertex2(p1 - b + norm*m_Raise, 1.f, v);
vertices.push_back(vertex1);
vertices.push_back(vertex2);
vertices.push_back(SVertex(p1 + b + norm*raised, 0, v));
indices.push_back(vertices.size() - 1);
u16 index1 = vertices.size() - 2; // index of vertex1 in this iteration (TR of this quad)
u16 index2 = vertices.size() - 1; // index of the vertex2 in this iteration (TL of this quad)
vertices.push_back(SVertex(p1 - b + norm*raised, 1, v));
indices.push_back(vertices.size() - 1);
if (i == 0)
{
// initial two vertices to continue building triangles from (n must be >= 2 for this to work)
indices.push_back(index1);
indices.push_back(index2);
}
else
{
u16 index1Prev = vertices.size() - 4; // index of the vertex1 in the previous iteration (BR of this quad)
u16 index2Prev = vertices.size() - 3; // index of the vertex2 in the previous iteration (BL of this quad)
ENSURE(index1Prev >= 0 && index1Prev < vertices.size());
ENSURE(index2Prev >= 0 && index2Prev < vertices.size());
// Add two corner points from last iteration and join with one of our own corners to create triangle 1
// (don't need to do this if i == 1 because i == 0 are the first two ones, they don't need to be copied)
if (i > 1)
{
indices.push_back(index1Prev);
indices.push_back(index2Prev);
}
indices.push_back(index1); // complete triangle 1
// Alternate V coordinate for debugging
// create triangle 2, specifying the adjacent side's vertices in the opposite order from triangle 1
indices.push_back(index1);
indices.push_back(index2Prev);
indices.push_back(index2);
}
// alternate V coordinate for debugging
v = 1 - v;
// Cycle the p's and compute the new p2
// cycle the p's and compute the new p2
p0 = p1;
p1 = p2;
p1floating = p2floating;
p2 = CVector3D(m_Line->m_Coords[((i+2) % n)*2], 0, m_Line->m_Coords[((i+2) % n)*2+1]);
// if in closed mode, wrap around the coordinate array for p2 -- otherwise, extend linearly
if (!closed && i == n-2)
// next iteration is the last point of the line, so create an artificial p2 that extends the p0 -> p1 direction
p2 = p1 + (p1 - p0);
else
p2 = CVector3D(m_Line->m_Coords[((i+2) % n)*2], 0, m_Line->m_Coords[((i+2) % n)*2+1]);
p2.Y = terrain->GetExactGroundLevel(p2.X, p2.Z);
if (p2.Y < w)
{
@ -413,17 +516,196 @@ void CTexturedLineRData::Update()
p2floating = false;
}
// Close the path
indices.push_back(0);
indices.push_back(1);
if (closed)
{
// close the path
indices.push_back(vertices.size()-2);
indices.push_back(vertices.size()-1);
indices.push_back(0);
indices.push_back(0);
indices.push_back(vertices.size()-1);
indices.push_back(1);
}
else
{
// Create start and end caps. On either end, this is done by taking the centroid between the last and second-to-last pair of
// vertices that was generated along the path (i.e. the vertex1's and vertex2's from above), taking a directional vector
// between them, and drawing the line cap in the plane given by the two butt-end corner points plus said vector.
std::vector<u16> capIndices;
std::vector<SVertex> capVertices;
// create end cap
CreateLineCap(
// the order of these vertices is important here, swapping them produces caps at the wrong side
vertices[vertices.size()-2].m_Position, // top-right vertex of last quad
vertices[vertices.size()-1].m_Position, // top-left vertex of last quad
// directional vector between centroids of last vertex pair and second-to-last vertex pair
(Centroid(vertices[vertices.size()-2], vertices[vertices.size()-1]) - Centroid(vertices[vertices.size()-4], vertices[vertices.size()-3])).Normalized(),
m_Line->m_EndCapType,
capVertices,
capIndices
);
for (unsigned i = 0; i < capIndices.size(); i++)
capIndices[i] += vertices.size();
vertices.insert(vertices.end(), capVertices.begin(), capVertices.end());
indices.insert(indices.end(), capIndices.begin(), capIndices.end());
capIndices.clear();
capVertices.clear();
// create start cap
CreateLineCap(
// the order of these vertices is important here, swapping them produces caps at the wrong side
vertices[1].m_Position,
vertices[0].m_Position,
// directional vector between centroids of first vertex pair and second vertex pair
(Centroid(vertices[1], vertices[0]) - Centroid(vertices[3], vertices[2])).Normalized(),
m_Line->m_StartCapType,
capVertices,
capIndices
);
for (unsigned i = 0; i < capIndices.size(); i++)
capIndices[i] += vertices.size();
vertices.insert(vertices.end(), capVertices.begin(), capVertices.end());
indices.insert(indices.end(), capIndices.begin(), capIndices.end());
}
ENSURE(indices.size() % 3 == 0); // GL_TRIANGLES indices, so must be multiple of 3
m_VB = g_VBMan.Allocate(sizeof(SVertex), vertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER);
m_VB->m_Owner->UpdateChunkVertices(m_VB, &vertices[0]);
if (m_VB)
{
// allocation might fail (e.g. due to too many vertices)
m_VB->m_Owner->UpdateChunkVertices(m_VB, &vertices[0]); // copy data into VBO
// Update the indices to include the base offset of the vertex data
for (size_t k = 0; k < indices.size(); ++k)
indices[k] += m_VB->m_Index;
for (size_t k = 0; k < indices.size(); ++k)
indices[k] += m_VB->m_Index;
m_VBIndices = g_VBMan.Allocate(sizeof(u16), indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER);
if (m_VBIndices)
m_VBIndices->m_Owner->UpdateChunkVertices(m_VBIndices, &indices[0]);
}
}
void CTexturedLineRData::CreateLineCap(const CVector3D& corner1, const CVector3D& corner2, const CVector3D& lineDirectionNormal,
SOverlayTexturedLine::LineCapType endCapType, std::vector<SVertex>& verticesOut,
std::vector<u16>& indicesOut)
{
if (endCapType == SOverlayTexturedLine::LINECAP_FLAT)
return; // no action needed, this is the default
// When not in closed mode, we've created artificial points for the start- and endpoints that extend the line in the
// direction of the first and the last segment, respectively. Thus, we know both the start and endpoints have perpendicular
// butt endings, i.e. the end corner vertices on either side of the line extend perpendicularly from the segment direction.
// That is to say, when viewed from the top, we will have something like
// .
// this: and not like this: /|
// ____. / |
// | / .
// | /
// ____. /
//
int roundCapPoints = 8; // amount of points to sample along the semicircle for rounded caps (including corner points)
float radius = m_Line->m_Thickness;
CVector3D centerPoint = (corner1 + corner2) * 0.5f;
SVertex centerVertex(centerPoint, 0.5f, 0.5f);
u16 indexOffset = verticesOut.size(); // index offset in verticesOut from where we start adding our vertices
switch (endCapType)
{
case SOverlayTexturedLine::LINECAP_SHARP:
{
roundCapPoints = 3; // creates only one point directly ahead
radius *= 1.5f; // make it a bit sharper (note that we don't use the radius for the butt-end corner points so it should be ok)
centerVertex.m_UVs[0] = 0.480f; // slight visual correction to make the texture match up better at the corner points
}
// fall-through
case SOverlayTexturedLine::LINECAP_ROUND:
{
// Draw a rounded line cap in the 3D plane of the line specified by the two corner points and the normal vector of the
// line's direction. The terrain normal at the centroid between the two corner points is perpendicular to this plane.
// The way this works is by taking a vector from the corner points' centroid to one of the corner points (which is then
// of radius length), and rotate it around the terrain normal vector in that centroid. This will rotate the vector in
// the line's plane, producing the desired rounded cap.
// To please OpenGL's winding order, this angle needs to be negated depending on whether we start rotating from
// the (center -> corner1) or (center -> corner2) vector. For the (center -> corner2) vector, we apparently need to use
// the negated angle.
float stepAngle = -(float)(M_PI/(roundCapPoints-1));
// Push the vertices in triangle fan order (easy to generate GL_TRIANGLES indices for afterwards)
// Note that we're manually adding the corner vertices instead of having them be generated by the rotating vector.
// This is because we want to support an overly large radius to make the sharp line ending look sharper.
verticesOut.push_back(centerVertex);
verticesOut.push_back(SVertex(corner2, 0.f, 0.f));
// Get the base vector that we will incrementally rotate in the cap plane to produce the radial sample points.
// Normally corner2 - centerPoint would suffice for this since it is of radius length, but we want to support custom
// radii to support tuning the 'sharpness' of sharp end caps (see above)
CVector3D rotationBaseVector = (corner2 - centerPoint).Normalized() * radius;
// Calculate the normal vector of the plane in which we're going to be drawing the line cap. This is the vector that
// is perpendicular to both baseVector and the 'lineDirectionNormal' vector indicating the direction of the line.
// Note that we shouldn't use terrain->CalcExactNormal() here because if the line is being rendered on top of water,
// then CalcExactNormal will return the normal vector of the terrain that's underwater (which can be quite funky).
CVector3D capPlaneNormal = lineDirectionNormal.Cross(rotationBaseVector).Normalized();
for (int i = 1; i < roundCapPoints - 1; ++i)
{
// Rotate the centerPoint -> corner vector by i*stepAngle radians around the cap plane normal at the center point.
CQuaternion quatRotation;
quatRotation.FromAxisAngle(capPlaneNormal, i * stepAngle);
CVector3D worldPos3D = centerPoint + quatRotation.Rotate(rotationBaseVector);
// Let v range from 0 to 1 as we move along the semi-circle, keep u fixed at 0 (i.e. curve the left vertical edge
// of the texture around the edge of the semicircle)
float u = 0.f;
float v = clamp((i/(float)(roundCapPoints-1)), 0.f, 1.f); // pos, u, v
verticesOut.push_back(SVertex(worldPos3D, u, v));
}
// connect back to the other butt-end corner point to complete the semicircle
verticesOut.push_back(SVertex(corner1, 0.f, 1.f));
// now push indices in GL_TRIANGLES order; vertices[indexOffset] is the center vertex, vertices[indexOffset + 1] is the
// first corner point, then a bunch of radial samples, and then at the end we have the other corner point again. So:
for (int i=1; i < roundCapPoints; ++i)
{
indicesOut.push_back(indexOffset); // center vertex
indicesOut.push_back(indexOffset + i);
indicesOut.push_back(indexOffset + i + 1);
}
}
break;
case SOverlayTexturedLine::LINECAP_SQUARE:
{
// Extend the (corner1 -> corner2) vector along the direction normal and draw a square line ending consisting of
// three triangles (sort of like a triangle fan)
// NOTE: The order in which the vertices are pushed out determines the visibility, as they
// are rendered only one-sided; the wrong order of vertices will make the cap visible only from the bottom.
verticesOut.push_back(centerVertex);
verticesOut.push_back(SVertex(corner2, 0.f, 0.f));
verticesOut.push_back(SVertex(corner2 + (lineDirectionNormal * (m_Line->m_Thickness)), 0.f, 0.33333f)); // extend butt corner point 2 along the normal vector
verticesOut.push_back(SVertex(corner1 + (lineDirectionNormal * (m_Line->m_Thickness)), 0.f, 0.66666f)); // extend butt corner point 1 along the normal vector
verticesOut.push_back(SVertex(corner1, 0.f, 1.0f)); // push butt corner point 1
for (int i=1; i < 4; ++i)
{
indicesOut.push_back(indexOffset); // center point
indicesOut.push_back(indexOffset + i);
indicesOut.push_back(indexOffset + i + 1);
}
}
break;
default:
break;
}
m_VBIndices = g_VBMan.Allocate(sizeof(u16), indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER);
m_VBIndices->m_Owner->UpdateChunkVertices(m_VBIndices, &indices[0]);
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -18,6 +18,8 @@
#ifndef INCLUDED_OVERLAYRENDERER
#define INCLUDED_OVERLAYRENDERER
#include "graphics/ShaderManager.h"
struct SOverlayLine;
struct SOverlayTexturedLine;
struct SOverlaySprite;
@ -83,6 +85,15 @@ public:
*/
void RenderForegroundOverlays(const CCamera& viewCamera);
private:
/**
* Helper method; renders those overlay lines currently registered in the internals (i.e. in m->texlines) for which the
* always visible flag equals @alwaysVisible. Used for batch rendering the overlay lines by their alwaysVisible status,
* because this requires a separate shader to be used.
*/
void RenderTexturedOverlayLines(CShaderProgramPtr shader, bool alwaysVisible);
private:
OverlayRendererInternals* m;
};

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -134,18 +134,6 @@ private:
};
/**
* Class RenderModifierRenderer: Interface to a model renderer that can render
* its models via a RenderModifier that sets up fragment stages.
*/
class RenderModifierRenderer : public ModelRenderer
{
public:
RenderModifierRenderer() { }
virtual ~RenderModifierRenderer();
};
/**
* Class PlainRenderModifier: RenderModifier that simply uses opaque textures
* modulated by primary color. It is used for normal, no-frills models.

View File

@ -503,11 +503,11 @@ ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const sh
m_nativeScope = JS_DefineObject(m_cx, m_glob, nativeScopeName, NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY
| JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "print", ::print, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "log", ::logmsg, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "warn", ::warn, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "error", ::error, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "deepcopy", ::deepcopy, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "print", ::print, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "log", ::logmsg, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "warn", ::warn, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "error", ::error, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
JS_DefineFunction(m_cx, m_glob, "deepcopy", ::deepcopy, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
Register("ProfileStart", ::ProfileStart, 1);
Register("ProfileStop", ::ProfileStop, 0);

View File

@ -118,6 +118,9 @@ COMPONENT(Position) // must be before VisualActor
INTERFACE(ProjectileManager)
COMPONENT(ProjectileManager)
INTERFACE(RallyPointRenderer)
COMPONENT(RallyPointRenderer)
INTERFACE(RangeManager)
COMPONENT(RangeManager)

File diff suppressed because it is too large Load Diff

View File

@ -103,6 +103,9 @@ public:
TerritoryOverlay* m_DebugOverlay;
bool m_EnableLineDebugOverlays; ///< Enable node debugging overlays for boundary lines?
std::vector<SOverlayLine> m_DebugBoundaryLineNodes;
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_Territories = NULL;
@ -110,7 +113,7 @@ public:
// m_DebugOverlay = new TerritoryOverlay(*this);
m_BoundaryLinesDirty = true;
m_TriggerEvent = true;
m_EnableLineDebugOverlays = false;
m_DirtyID = 1;
m_AnimTime = 0.0;
@ -742,6 +745,7 @@ void CCmpTerritoryManager::UpdateBoundaryLines()
PROFILE("update boundary lines");
m_BoundaryLines.clear();
m_DebugBoundaryLineNodes.clear();
if (!CRenderer::IsInitialised())
return;
@ -780,23 +784,40 @@ void CCmpTerritoryManager::UpdateBoundaryLines()
m_BoundaryLines.push_back(SBoundaryLine());
m_BoundaryLines.back().connected = boundaries[i].connected;
m_BoundaryLines.back().color = color;
m_BoundaryLines.back().overlay.m_Terrain = terrain;
m_BoundaryLines.back().overlay.m_TextureBase = textureBase;
m_BoundaryLines.back().overlay.m_TextureMask = textureMask;
m_BoundaryLines.back().overlay.m_Color = color;
m_BoundaryLines.back().overlay.m_Thickness = m_BorderThickness;
m_BoundaryLines.back().overlay.m_Closed = true;
SimRender::SmoothPointsAverage(boundaries[i].points, true);
SimRender::SmoothPointsAverage(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed);
SimRender::InterpolatePointsRNS(boundaries[i].points, true, m_BorderSeparation);
SimRender::InterpolatePointsRNS(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed, m_BorderSeparation);
std::vector<float>& points = m_BoundaryLines.back().overlay.m_Coords;
for (size_t j = 0; j < boundaries[i].points.size(); ++j)
{
points.push_back(boundaries[i].points[j].X);
points.push_back(boundaries[i].points[j].Y);
if (m_EnableLineDebugOverlays)
{
const int numHighlightNodes = 7; // highlight the X last nodes on either end to see where they meet (if closed)
SOverlayLine overlayNode;
if (j > boundaries[i].points.size() - 1 - numHighlightNodes)
overlayNode.m_Color = CColor(1.f, 0.f, 0.f, 1.f);
else if (j < numHighlightNodes)
overlayNode.m_Color = CColor(0.f, 1.f, 0.f, 1.f);
else
overlayNode.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f);
overlayNode.m_Thickness = 1;
SimRender::ConstructCircleOnGround(GetSimContext(), boundaries[i].points[j].X, boundaries[i].points[j].Y, 0.1f, overlayNode, true);
m_DebugBoundaryLineNodes.push_back(overlayNode);
}
}
}
}
@ -825,6 +846,10 @@ void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector)
{
for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
collector.Submit(&m_BoundaryLines[i].overlay);
for (size_t i = 0; i < m_DebugBoundaryLineNodes.size(); ++i)
collector.Submit(&m_DebugBoundaryLineNodes[i]);
}
player_id_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z)

View File

@ -361,6 +361,14 @@ public:
}
}
virtual void SetUnitEntitySelection(const CStr& selection)
{
if (m_Unit)
{
m_Unit->SetEntitySelection(selection);
}
}
virtual void SelectMovementAnimation(fixed runThreshold)
{
m_AnimRunThreshold = runThreshold;

View File

@ -53,6 +53,11 @@ public:
return m_Script.Call<CColor>("GetColour");
}
virtual std::wstring GetCiv()
{
return m_Script.Call<std::wstring>("GetCiv");
}
virtual CFixedVector3D GetStartingCameraPos()
{
return m_Script.Call<CFixedVector3D>("GetStartingCameraPos");

View File

@ -36,6 +36,7 @@ public:
virtual void SetColour(u8 r, u8 g, u8 b) = 0;
virtual CColor GetColour() = 0;
virtual std::wstring GetCiv() = 0;
virtual CFixedVector3D GetStartingCameraPos() = 0;
virtual CFixedVector3D GetStartingCameraRot() = 0;

View File

@ -0,0 +1,28 @@
/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "ICmpRallyPointRenderer.h"
#include "simulation2/system/InterfaceScripted.h"
class CFixedVector2D;
BEGIN_INTERFACE_WRAPPER(RallyPointRenderer)
DEFINE_INTERFACE_METHOD_1("SetDisplayed", void, ICmpRallyPointRenderer, SetDisplayed, bool)
DEFINE_INTERFACE_METHOD_1("SetPosition", void, ICmpRallyPointRenderer, SetPosition, CFixedVector2D)
END_INTERFACE_WRAPPER(RallyPointRenderer)

View File

@ -0,0 +1,42 @@
/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_ICMPRALLYPOINT
#define INCLUDED_ICMPRALLYPOINT
#include "maths/FixedVector2D.h"
#include "simulation2/helpers/Position.h"
#include "simulation2/system/Interface.h"
/**
* Rally Point.
* Holds the position of a unit's rally point, and renders it to screen.
*/
class ICmpRallyPointRenderer : public IComponent
{
public:
/// Sets whether the rally point marker and line should be displayed.
virtual void SetDisplayed(bool displayed) = 0;
/// Sets the position at which the rally point marker should be displayed.
virtual void SetPosition(CFixedVector2D position) = 0;
DECLARE_INTERFACE_TYPE(RallyPointRenderer)
};
#endif // INCLUDED_ICMPRALLYPOINT

View File

@ -20,6 +20,7 @@
#include "simulation2/system/Interface.h"
#include "ps/CStr.h"
#include "maths/BoundingBoxOriented.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/Fixed.h"
@ -90,6 +91,11 @@ public:
*/
virtual void SelectAnimation(std::string name, bool once, fixed speed, std::wstring soundgroup) = 0;
/**
* Sets the specified entity selection on the underlying unit.
*/
virtual void SetUnitEntitySelection(const CStr& selection) = 0;
/**
* Start playing the walk/run animations, scaled to the unit's movement speed.
* @param runThreshold movement speed at which to switch to the run animation

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -30,12 +30,38 @@ class CFixedVector2D;
namespace Geometry
{
/**
* Returns true if @p point is inside the square with rotated X axis unit vector @p u and rotated Z axis unit vector @p v,
* and half dimensions specified by @p halfSizes. Currently assumes the @p u and @p v vectors are perpendicular. Can also
* be used for rectangles.
*
* @param point point vector of the point that is to be tested relative to the origin (center) of the shape.
* @param u rotated X axis unit vector relative to the absolute XZ plane. Indicates the orientation of the rectangle. If not rotated,
* this value is the absolute X axis unit vector (1,0). If rotated by angle theta, this should be (cos theta, -sin theta), as
* the absolute Z axis points down in the unit circle.
* @param v rotated Z axis unit vector relative to the absolute XZ plane. Indicates the orientation of the rectangle. If not rotated,
* this value is the absolute Z axis unit vector (0,1). If rotated by angle theta, this should be (sin theta, cos theta), as
* the absolute Z axis points down in the unit circle.
* @param halfSizes Holds half the dimensions of the shape along the u and v vectors, respectively.
*/
bool PointIsInSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
CFixedVector2D GetHalfBoundingBox(CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
fixed DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
/**
* Returns the point that is closest to @p point on the edge of the square specified by orientation unit vectors @p u and @p v and half
* dimensions @p halfSize, relative to the center of the square. Currently assumes the @p u and @p v vectors are perpendicular.
* Can also be used for rectangles.
*
* @param point point vector of the point we want to get the nearest edge point for, relative to the origin (center) of the shape.
* @param u rotated X axis unit vector, relative to the absolute XZ plane. Indicates the orientation of the shape. If not rotated,
* this value is the absolute X axis unit vector (1,0). If rotated by angle theta, this should be (cos theta, -sin theta).
* @param v rotated Z axis unit vector, relative to the absolute XZ plane. Indicates the orientation of the shape. If not rotated,
* this value is the absolute Z axis unit vector (0,1). If rotated by angle theta, this should be (sin theta, cos theta).
* @param halfSizes Holds half the dimensions of the shape along the u and v vectors, respectively.
*/
CFixedVector2D NearestPointOnSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
bool TestRaySquare(CFixedVector2D a, CFixedVector2D b, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games.
/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -343,9 +343,10 @@ static CVector2D EvaluateSpline(float t, CVector2D a0, CVector2D a1, CVector2D a
return p + CVector2D(dp.Y*-offset, dp.X*offset);
}
void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset)
void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset, int segmentSamples)
{
PROFILE("InterpolatePointsRNS");
ENSURE(segmentSamples > 0);
std::vector<CVector2D> newPoints;
@ -357,20 +358,54 @@ void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed
// curve with fewer points
size_t n = points.size();
if (n < 1)
return; // can't do anything unless we have two points
size_t imax = closed ? n : n-1; // TODO: we probably need to do a bit more to handle non-closed paths
if (closed)
{
if (n < 1)
return; // we need at least a single point to not crash
}
else
{
if (n < 2)
return; // in non-closed mode, we need at least n=2 to not crash
}
newPoints.reserve(imax*4);
size_t imax = closed ? n : n-1;
newPoints.reserve(imax*segmentSamples);
// these are primarily used inside the loop, but for open paths we need them outside the loop once to compute the last point
CVector2D a0;
CVector2D a1;
CVector2D a2;
CVector2D a3;
for (size_t i = 0; i < imax; ++i)
{
// Get the relevant points for this spline segment
CVector2D p0 = points[(i-1+n)%n];
// Get the relevant points for this spline segment; each step interpolates the segment between p1 and p2; p0 and p3 are the points
// before p1 and after p2, respectively; they're needed to compute tangents and whatnot.
CVector2D p0; // normally points[(i-1+n)%n], but it's a bit more complicated due to open/closed paths -- see below
CVector2D p1 = points[i];
CVector2D p2 = points[(i+1)%n];
CVector2D p3 = points[(i+2)%n];
CVector2D p3; // normally points[(i+2)%n], but it's a bit more complicated due to open/closed paths -- see below
if (!closed && (i == 0))
// p0's point index is out of bounds, and we can't wrap around because we're in non-closed mode -- create an artificial point
// that extends p1 -> p0 (i.e. the first segment's direction)
p0 = points[0] + (points[0] - points[1]);
else
// standard wrap-around case
p0 = points[(i-1+n)%n]; // careful; don't use (i-1)%n here, as the result is machine-dependent for negative operands (e.g. if i==0, the result could be either -1 or n-1)
if (!closed && (i == n-2))
// p3's point index is out of bounds; create an artificial point that extends p_(n-2) -> p_(n-1) (i.e. the last segment's direction)
// (note that p2's index should not be out of bounds, because in non-closed mode imax is reduced by 1)
p3 = points[n-1] + (points[n-1] - points[n-2]);
else
// standard wrap-around case
p3 = points[(i+2)%n];
// Do the RNS computation (based on GPG4 "Nonuniform Splines")
float l1 = (p2 - p1).Length(); // length of spline segment (i)..(i+1)
@ -381,17 +416,108 @@ void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed
CVector2D v2 = (s1 + s2).Normalized() * l1; // spline velocity at i+1
// Compute standard cubic spline parameters
CVector2D a0 = p1*2 + p2*-2 + v1 + v2;
CVector2D a1 = p1*-3 + p2*3 + v1*-2 + v2*-1;
CVector2D a2 = v1;
CVector2D a3 = p1;
a0 = p1*2 + p2*-2 + v1 + v2;
a1 = p1*-3 + p2*3 + v1*-2 + v2*-1;
a2 = v1;
a3 = p1;
// Interpolate at regular points across the interval
for (int sample = 0; sample < segmentSamples; sample++)
newPoints.push_back(EvaluateSpline(sample/((float) segmentSamples), a0, a1, a2, a3, offset));
// Interpolate at various points
newPoints.push_back(EvaluateSpline(0.f, a0, a1, a2, a3, offset));
newPoints.push_back(EvaluateSpline(1.f/4.f, a0, a1, a2, a3, offset));
newPoints.push_back(EvaluateSpline(2.f/4.f, a0, a1, a2, a3, offset));
newPoints.push_back(EvaluateSpline(3.f/4.f, a0, a1, a2, a3, offset));
}
if (!closed)
// if the path is open, we should take care to include the last control point
// NOTE: we can't just do push_back(points[n-1]) here because that ignores the offset
newPoints.push_back(EvaluateSpline(1.f, a0, a1, a2, a3, offset));
points.swap(newPoints);
}
void SimRender::ConstructDashedLine(const std::vector<CVector2D>& keyPoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength)
{
// sanity checks
if (dashLength <= 0)
return;
if (blankLength <= 0)
return;
if (keyPoints.size() < 2)
return;
dashedLineOut.m_Points.clear();
dashedLineOut.m_StartIndices.clear();
// walk the line, counting the total length so far at each node point. When the length exceeds dashLength, cut the last segment at the
// required length and continue for blankLength along the line to start a new dash segment.
// TODO: we should probably extend this function to also allow for closed lines. I was thinking of slightly scaling the dash/blank length
// so that it fits the length of the curve, but that requires knowing the length of the curve upfront which is sort of expensive to compute
// (O(n) and lots of square roots).
bool buildingDash = true; // true if we're building a dash, false if a blank
float curDashLength = 0; // builds up the current dash/blank's length as we walk through the line nodes
CVector2D dashLastPoint = keyPoints[0]; // last point of the current dash/blank being built.
// register the first starting node of the first dash
dashedLineOut.m_Points.push_back(keyPoints[0]);
dashedLineOut.m_StartIndices.push_back(0);
// index of the next key point on the path. Must always point to a node that is further along the path than dashLastPoint, so we can
// properly take a direction vector along the path.
size_t i = 0;
while(i < keyPoints.size() - 1)
{
// get length of this segment
CVector2D segmentVector = keyPoints[i + 1] - dashLastPoint; // vector from our current point along the path to nextNode
float segmentLength = segmentVector.Length();
float targetLength = (buildingDash ? dashLength : blankLength);
if (curDashLength + segmentLength > targetLength)
{
// segment is longer than the dash length we still have to go, so we'll need to cut it; create a cut point along the segment
// line that is of just the required length to complete the dash, then make it the base point for the next dash/blank.
float cutLength = targetLength - curDashLength;
CVector2D cutPoint = dashLastPoint + (segmentVector.Normalized() * cutLength);
// start a new dash or blank in the next iteration
curDashLength = 0;
buildingDash = !buildingDash; // flip from dash to blank and vice-versa
dashLastPoint = cutPoint;
// don't increment i, we haven't fully traversed this segment yet so we still need to use the same point to take the
// direction vector with in the next iteration
// this cut point is either the end of the current dash or the beginning of a new dash; either way, we're gonna need it
// in the points array.
dashedLineOut.m_Points.push_back(cutPoint);
if (buildingDash)
{
// if we're gonna be building a new dash, then cutPoint is now the base point of that new dash, so let's register its
// index as a start index of a dash.
dashedLineOut.m_StartIndices.push_back(dashedLineOut.m_Points.size() - 1);
}
}
else
{
// the segment from lastDashPoint to keyPoints[i+1] doesn't suffice to complete the dash, so we need to add keyPoints[i+1]
// to this dash's points and continue from there
if (buildingDash)
// still building the dash, add it to the output (we don't need to store the blanks)
dashedLineOut.m_Points.push_back(keyPoints[i+1]);
curDashLength += segmentLength;
dashLastPoint = keyPoints[i+1];
i++;
}
}
}

View File

@ -23,6 +23,8 @@
* Helper functions related to rendering
*/
#include "maths/Vector2D.h"
class CSimContext;
class CVector2D;
class CVector3D;
@ -31,6 +33,26 @@ class CBoundingBoxAligned;
class CBoundingBoxOriented;
struct SOverlayLine;
struct SDashedLine
{
/// Packed array of consecutive dashes' points. Use m_StartIndices to navigate it.
std::vector<CVector2D> m_Points;
/**
* Start indices in m_Points of each dash. Dash n starts at point m_StartIndices[n] and ends at the point with index
* m_StartIndices[n+1] - 1, or at the end of the m_Points vector. Use the GetEndIndex(n) convenience method to abstract away the
* difference and get the (exclusive) end index of dash n.
*/
std::vector<size_t> m_StartIndices;
/// Returns the (exclusive) end point index (i.e. index within m_Points) of dash n.
size_t GetEndIndex(size_t i)
{
// for the last dash, there is no next starting index, so we need to use the end index of the m_Points array instead
return (i < m_StartIndices.size() - 1 ? m_StartIndices[i+1] : m_Points.size());
}
};
namespace SimRender
{
@ -95,8 +117,19 @@ void SmoothPointsAverage(std::vector<CVector2D>& points, bool closed);
* the direction of the curve.
* If @p closed then the points are treated as a closed path (the last is connected
* to the first).
* @param segmentSamples Amount of intermediate points to sample between every two control points.
*/
void InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset);
void InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset, int segmentSamples = 4);
/**
* Creates a dashed line from the line specified by @points so that each dash is of length
* @p dashLength, and each blank inbetween is of length @p blankLength. The dashed line returned as a list of smaller lines
* in @p dashedLineOut.
*
* @param dashLength Length of a single dash. Must be strictly positive.
* @param blankLength Length of a single blank between dashes. Must be strictly positive.
*/
void ConstructDashedLine(const std::vector<CVector2D>& linePoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength);
} // namespace