Ticket #524: rallypoints_06dec11.patch
File rallypoints_06dec11.patch, 126.7 KB (added by , 12 years ago) |
---|
-
.gitignore
diff --git a/.gitignore b/.gitignore index e82cae3..8ade2ca 100644
a b 4 4 docs/ 5 5 patches/ 6 6 binaries/data/mods/public/art/textures/misc/*.psd 7 binaries/system/ActorEditor.exe 7 8 binaries/system/*.pdb 8 9 binaries/system/*.ilk 9 10 binaries/system/*.exp -
binaries/data/mods/public/art/actors/props/special/common/waypoint_flag.xml
diff --git a/binaries/data/mods/public/art/actors/props/special/common/waypoint_flag.xml b/binaries/data/mods/public/art/actors/props/special/common/waypoint_flag.xml index fcc167a..4ac1a0c 100644
a b 8 8 </variant> 9 9 </group> 10 10 <group> 11 <variant name=" waypoint_hellenes">11 <variant name="hele"> 12 12 <texture>props/banner_greek.png</texture> 13 13 </variant> 14 <variant name="pers"> 15 <texture>props/banner_persian.png</texture> 16 </variant> 17 <variant name="celt"> 18 <texture>props/banner_celt.png</texture> 19 </variant> 20 <variant name="cart"> 21 <texture>props/banner_carthage.png</texture> 22 </variant> 23 <variant name="iber"> 24 <texture>props/banner_iberians.png</texture> 25 </variant> 26 <variant name="rome"> 27 <texture>props/banner_romans.png</texture> 28 </variant> 14 29 </group> 15 30 </actor> -
binaries/data/mods/public/gui/session/input.js
diff --git a/binaries/data/mods/public/art/textures/misc/rallypoint_line.png b/binaries/data/mods/public/art/textures/misc/rallypoint_line.png new file mode 100644 index 0000000..55c1100 Binary files /dev/null and b/binaries/data/mods/public/art/textures/misc/rallypoint_line.png differ diff --git a/binaries/data/mods/public/art/textures/misc/rallypoint_line_mask.png b/binaries/data/mods/public/art/textures/misc/rallypoint_line_mask.png new file mode 100644 index 0000000..b1824fc Binary files /dev/null and b/binaries/data/mods/public/art/textures/misc/rallypoint_line_mask.png differ diff --git a/binaries/data/mods/public/art/textures/ui/session/icons/single/focus-rally.png b/binaries/data/mods/public/art/textures/ui/session/icons/single/focus-rally.png new file mode 100644 index 0000000..31a341a Binary files /dev/null and b/binaries/data/mods/public/art/textures/ui/session/icons/single/focus-rally.png differ diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index 9a7068f..7ece473 100644
a b function getActionInfo(action, target) 135 135 return entState && entState.rallyPoint; 136 136 }); 137 137 138 139 138 if (!target) 140 139 { 141 140 if (action == "set-rallypoint" && haveRallyPoints) … … function performCommand(entity, commandName) 1115 1114 case "unload-all": 1116 1115 unloadAll(entity); 1117 1116 break; 1117 case "focus-rally": 1118 // if the selected building has a rally point set, move the camera to it; otherwise, move to the building itself 1119 // (since that's where units will spawn without a rally point) 1120 var focusTarget = null; 1121 if (entState.rallyPoint && entState.rallyPoint.position) 1122 { 1123 focusTarget = entState.rallyPoint.position; 1124 } 1125 else 1126 { 1127 if (entState.position) 1128 focusTarget = entState.position; 1129 } 1130 1131 if (focusTarget !== null) 1132 Engine.CameraMoveTo(focusTarget.x, focusTarget.z); 1133 1134 break; 1118 1135 default: 1119 1136 break; 1120 1137 } -
binaries/data/mods/public/gui/session/unit_commands.js
diff --git a/binaries/data/mods/public/gui/session/unit_commands.js b/binaries/data/mods/public/gui/session/unit_commands.js index 18f10f6..52c7771 100644
a b function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 270 270 break; 271 271 272 272 case COMMAND: 273 if (item == "unload-all") 273 // here, "item" is an object with properties .name (command name), .tooltip and .icon (relative to session/icons/single) 274 if (item.name == "unload-all") 274 275 { 275 276 var count = unitEntState.garrisonHolder.entities.length; 276 277 getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 0 ? count : ""); … … function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 280 281 getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = ""; 281 282 } 282 283 283 tooltip = toTitleCase(item);284 tooltip = (item.tooltip ? item.tooltip : toTitleCase(item.name)); 284 285 break; 285 286 286 287 default: … … function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 334 335 { 335 336 //icon.cell_id = i; 336 337 //icon.cell_id = getCommandCellId(item); 337 icon.sprite = "stretched:session/icons/single/" + getCommandImage(item);338 icon.sprite = "stretched:session/icons/single/" + item.icon; 338 339 339 340 } 340 341 else if (template.icon) … … function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s 477 478 var commands = getEntityCommandsList(entState); 478 479 if (commands.length) 479 480 setupUnitPanel("Command", usedPanels, entState, commands, 480 function (item) { performCommand(entState.id, item ); } );481 function (item) { performCommand(entState.id, item.name); } ); 481 482 482 483 if (entState.garrisonHolder) 483 484 { -
binaries/data/mods/public/gui/session/utility_functions.js
diff --git a/binaries/data/mods/public/gui/session/utility_functions.js b/binaries/data/mods/public/gui/session/utility_functions.js index 1cb89da..f723baa 100644
a b function getFormationCellId(formationName) 189 189 } 190 190 } 191 191 192 function getCommandImage(commandName)193 {194 switch (commandName)195 {196 case "delete":197 return "kill_small.png";198 case "unload-all":199 return "garrison-out.png";200 case "garrison":201 return "garrison.png";202 case "repair":203 return "repair.png";204 default:205 return "";206 }207 }208 209 192 function getEntityFormationsList(entState) 210 193 { 211 194 var civ = g_Players[entState.player].civ; … … function getEntityCommandsList(entState) 234 217 { 235 218 var commands = []; 236 219 if (entState.garrisonHolder) 237 commands.push("unload-all"); 220 { 221 commands.push({ 222 "name": "unload-all", 223 "tooltip": "Unload All", 224 "icon": "garrison-out.png" 225 }); 226 } 227 228 commands.push({ 229 "name": "delete", 230 "tooltip": "Delete", 231 "icon": "kill_small.png" 232 }); 233 238 234 if (isUnit(entState)) 239 commands.push("garrison"); 235 { 236 commands.push({ 237 "name": "garrison", 238 "tooltip": "Garrison", 239 "icon": "garrison.png" 240 }); 241 } 242 240 243 if (entState.buildEntities) 241 commands.push("repair"); 242 commands.push("delete"); 244 { 245 commands.push({ 246 "name": "repair", 247 "tooltip": "Repair", 248 "icon": "repair.png" 249 }); 250 } 251 252 if (entState.rallyPoint) 253 { 254 commands.push({ 255 "name": "focus-rally", 256 "tooltip": "Focus on Rally Point", 257 "icon": "focus-rally.png" 258 }); 259 } 260 243 261 return commands; 244 262 } 245 263 -
binaries/data/mods/public/shaders/overlayline.fp
diff --git a/binaries/data/mods/public/shaders/overlayline.fp b/binaries/data/mods/public/shaders/overlayline.fp index 368ce42..fd78472 100644
a b PARAM objectColor = program.local[0]; 3 3 TEMP base; 4 4 TEMP mask; 5 5 TEMP color; 6 TEMP los; 6 7 7 8 8 // Combine base texture and color, using mask texture 9 9 TEX base, fragment.texcoord[0], texture[0], 2D; 10 10 TEX mask, fragment.texcoord[0], texture[1], 2D; 11 11 LRP color.rgb, mask, objectColor, base; 12 12 13 // Multiply by LOS texture 14 TEX los, fragment.texcoord[1], texture[2], 2D; 15 MUL result.color.rgb, color, los.a; 13 #ifdef IGNORE_LOS 14 MOV result.color.rgb, color; 15 #else 16 // Multiply RGB by LOS texture (alpha channel) 17 TEMP los; 18 TEX los, fragment.texcoord[1], texture[2], 2D; 19 MUL result.color.rgb, color, los.a; 20 #endif 16 21 17 // Use alpha from base texture 22 // Use alpha from base texture, combined with the object color alpha. 23 // The latter is usually 1, so this basically comes down to base.a 18 24 MUL result.color.a, objectColor.a, base.a; 19 25 26 20 27 END -
binaries/data/mods/public/simulation/components/GuiInterface.js
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 8565f82..7e8440f 100644
a b GuiInterface.prototype.Init = function() 26 26 /* 27 27 * All of the functions defined below are called via Engine.GuiInterfaceCall(name, arg) 28 28 * from GUI scripts, and executed here with arguments (player, arg). 29 * 30 * CAUTION: The input to the functions in this module is not network-synchronised, so it 31 * mustn't affect the simulation state (i.e. the data that is serialised and can affect 32 * the behaviour of the rest of the simulation) else it'll cause out-of-sync errors. 29 33 */ 30 34 31 35 /** … … GuiInterface.prototype.GetEntityState = function(player, ent) 223 227 var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); 224 228 if (cmpRallyPoint) 225 229 { 226 ret.rallyPoint = { };230 ret.rallyPoint = {'position': cmpRallyPoint.GetPosition()}; // undefined or {x,z} object 227 231 } 228 232 229 233 var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder); … … GuiInterface.prototype.SetStatusBars = function(player, cmd) 418 422 }; 419 423 420 424 /** 421 * Displays the rally point of a building 425 * Displays the rally point of a given list of entities (carried in cmd.entities). 426 * 427 * The 'cmd' object may carry its own x/z coordinate pair indicating the location where the rally point should 428 * be rendered, in order to support instantaneously rendering a rally point marker at a specified location 429 * instead of incurring a delay while PostNetworkCommand processes the set-rallypoint command (see input.js). 430 * If cmd doesn't carry a custom location, then the position to render the marker at will be read from the 431 * RallyPoint component. 422 432 */ 423 433 GuiInterface.prototype.DisplayRallyPoint = function(player, cmd) 424 434 { 425 // If there are rally points already displayed, destroythem426 for each (var ent in this. rallyPoints)435 // If there are some rally points already displayed, first hide them 436 for each (var ent in this.entsRallyPointsDisplayed) 427 437 { 428 // Hide it first (the destruction won't be instantaneous) 429 var cmpPosition = Engine.QueryInterface(ent, IID_Position); 430 cmpPosition.MoveOutOfWorld(); 431 432 Engine.DestroyEntity(ent); 438 var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer); 439 if (cmpRallyPointRenderer) 440 cmpRallyPointRenderer.SetDisplayed(false); 433 441 } 434 442 435 this.rallyPoints = []; 436 437 var positions = []; 438 // DisplayRallyPoints is called passing a list of entities for which 439 // rally points must be displayed 443 // Show the rally points for the passed entities 440 444 for each (var ent in cmd.entities) 441 445 { 446 var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer); 447 if (!cmpRallyPointRenderer) 448 continue; 449 450 // entity must have a rally point component to display a rally point marker 451 // (regardless of whether cmd specifies a custom location) 442 452 var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); 443 453 if (!cmpRallyPoint) 444 454 continue; … … GuiInterface.prototype.DisplayRallyPoint = function(player, cmd) 452 462 // override the real rally point position; otherwise use the real position 453 463 var pos; 454 464 if (cmd.x && cmd.z) 455 pos = {"x": cmd.x, "z": cmd.z};465 pos = cmd; 456 466 else 457 pos = cmpRallyPoint.GetPosition(); 467 pos = cmpRallyPoint.GetPosition(); // may return undefined 458 468 459 469 if (pos) 460 { 461 // TODO: it'd probably be nice if we could draw some kind of line 462 // between the building and pos, to make the marker easy to find even 463 // if it's a long way from the building 470 cmpRallyPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z 464 471 465 positions.push(pos); 466 } 472 cmpRallyPointRenderer.SetDisplayed(true); 467 473 } 468 474 469 // Add rally point entity for each building 470 for each (var pos in positions) 471 { 472 var rallyPoint = Engine.AddLocalEntity("actor|props/special/common/waypoint_flag.xml"); 473 var cmpPosition = Engine.QueryInterface(rallyPoint, IID_Position); 474 cmpPosition.JumpTo(pos.x, pos.z); 475 this.rallyPoints.push(rallyPoint); 476 } 475 // Remember which entities have their rally points displayed so we can hide them again 476 this.entsRallyPointsDisplayed = cmd.entities; 477 477 }; 478 478 479 479 /** -
binaries/data/mods/public/simulation/components/RallyPoint.js
diff --git a/binaries/data/mods/public/simulation/components/RallyPoint.js b/binaries/data/mods/public/simulation/components/RallyPoint.js index 3c3816a..80f8ffe 100644
a b RallyPoint.prototype.SetPosition = function(x, z) 13 13 this.pos = { 14 14 "x": x, 15 15 "z": z 16 } 16 }; 17 17 }; 18 18 19 19 RallyPoint.prototype.Unset = function() -
new file inaries/data/mods/public/simulation/templates/special/rallypoint.xml
diff --git a/binaries/data/mods/public/simulation/templates/special/rallypoint.xml b/binaries/data/mods/public/simulation/templates/special/rallypoint.xml new file mode 100644 index 0000000..c73b5d8
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <Entity parent="special/actor"> 3 <VisualActor> 4 <Actor>props/special/common/waypoint_flag.xml</Actor> 5 </VisualActor> 6 <Vision> 7 <AlwaysVisible>true</AlwaysVisible> 8 </Vision> 9 </Entity> -
binaries/data/mods/public/simulation/templates/structures/cart_super_dock.xml
diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_super_dock.xml b/binaries/data/mods/public/simulation/templates/structures/cart_super_dock.xml index 71568df..c41969f 100644
a b 34 34 <Position> 35 35 <Floating>true</Floating> 36 36 </Position> 37 <RallyPointRenderer> 38 <LinePassabilityClass>ship</LinePassabilityClass> 39 </RallyPointRenderer> 37 40 <Sound> 38 41 <SoundGroups> 39 42 <select>interface/select/building/sel_dock.xml</select> -
binaries/data/mods/public/simulation/templates/template_structure.xml
diff --git a/binaries/data/mods/public/simulation/templates/template_structure.xml b/binaries/data/mods/public/simulation/templates/template_structure.xml index a346fd9..a4733d4 100644
a b 53 53 <DisableBlockPathfinding>false</DisableBlockPathfinding> 54 54 </Obstruction> 55 55 <OverlayRenderer/> 56 <RallyPoint/> 56 <RallyPoint /> 57 <RallyPointRenderer> 58 <MarkerTemplate>special/rallypoint</MarkerTemplate> 59 <LineTexture>art/textures/misc/rallypoint_line.png</LineTexture> 60 <LineTextureMask>art/textures/misc/rallypoint_line_mask.png</LineTextureMask> 61 <LineThickness>0.2</LineThickness> 62 <LineColour r="35" g="86" b="188" /> 63 <LineDashColour r="158" g="11" b="15" /> 64 <LineStartCap>square</LineStartCap> 65 <LineEndCap>round</LineEndCap> 66 <LineCostClass>default</LineCostClass> 67 <LinePassabilityClass>default</LinePassabilityClass> 68 </RallyPointRenderer> 57 69 <Sound> 58 70 <SoundGroups> 59 71 <select>interface/select/building/sel_universal.xml</select> -
binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml
diff --git a/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml b/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml index 74ab503..abda9bf 100644
a b 36 36 <Position> 37 37 <Floating>true</Floating> 38 38 </Position> 39 <RallyPointRenderer> 40 <LinePassabilityClass>ship</LinePassabilityClass> 41 </RallyPointRenderer> 39 42 <ResourceDropsite> 40 43 <Types>food wood stone metal</Types> 41 44 </ResourceDropsite> -
source/graphics/GameView.cpp
diff --git a/binaries/system/ActorEditor.exe b/binaries/system/ActorEditor.exe deleted file mode 100644 index 15cdfc5..0000000 Binary files a/binaries/system/ActorEditor.exe and /dev/null differ diff --git a/source/graphics/GameView.cpp b/source/graphics/GameView.cpp index 6e0c2f5..f17d2b0 100644
a b void CGameView::Update(float DeltaTime) 899 899 m->ViewCamera.UpdateFrustum(); 900 900 } 901 901 902 void CGameView::MoveCameraTarget(const CVector3D& target , bool minimap)902 void CGameView::MoveCameraTarget(const CVector3D& target) 903 903 { 904 904 // Maintain the same orientation and level of zoom, if we can 905 905 // (do this by working out the point the camera is looking at, saving … … void CGameView::MoveCameraTarget(const CVector3D& target, bool minimap) 912 912 CVector3D pivot = targetCam.GetFocus(); 913 913 CVector3D delta = target - pivot; 914 914 915 //If minimap movement, maintain previous zoom level by not changing Y position916 // - this prevents strange behavior when moving across changes in terrain height917 if (!minimap)918 m->PosY.SetValueSmoothly(delta.Y + m->PosY.GetValue());919 920 915 m->PosX.SetValueSmoothly(delta.X + m->PosX.GetValue()); 921 916 m->PosZ.SetValueSmoothly(delta.Z + m->PosZ.GetValue()); 922 917 -
source/graphics/GameView.h
diff --git a/source/graphics/GameView.h b/source/graphics/GameView.h index 0811cda..f7bb96c 100644
a b public: 79 79 80 80 InReaction HandleEvent(const SDL_Event_* ev); 81 81 82 void MoveCameraTarget(const CVector3D& target , bool minimap = false);82 void MoveCameraTarget(const CVector3D& target); 83 83 void ResetCameraTarget(const CVector3D& target); 84 84 void ResetCameraAngleZoom(); 85 85 void CameraFollow(entity_id_t entity, bool firstPerson); -
new file source/graphics/Overlay.cpp
diff --git a/source/graphics/Overlay.cpp b/source/graphics/Overlay.cpp new file mode 100644 index 0000000..7e52063
- + 1 /* Copyright (C) 2011 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "precompiled.h" 19 20 #include "ps/CStr.h" 21 #include "Overlay.h" 22 23 SOverlayTexturedLine::LineCapType SOverlayTexturedLine::StrToLineCapType(const std::wstring& str) 24 { 25 if (str == L"round") 26 return LINECAP_ROUND; 27 else if (str == L"sharp") 28 return LINECAP_SHARP; 29 else if (str == L"square") 30 return LINECAP_SQUARE; 31 else if (str == L"flat") 32 return LINECAP_FLAT; 33 else { 34 debug_warn(L"[Overlay] Unrecognized line cap type identifier"); 35 return LINECAP_FLAT; 36 } 37 } -
source/graphics/Overlay.h
diff --git a/source/graphics/Overlay.h b/source/graphics/Overlay.h index da47d19..0e0dc29 100644
a b struct SOverlayLine 49 49 */ 50 50 struct SOverlayTexturedLine 51 51 { 52 SOverlayTexturedLine() : m_Terrain(NULL), m_Thickness(1.0f) { } 52 53 enum LineCapType 54 { 55 LINECAP_FLAT, ///< no line ending; abrupt stop of the line (aka. butt ending) 56 57 /** 58 * Semi-circular line ending. The texture is mapped by curving the left vertical edge around the semi-circle's rim. That is, 59 * 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 60 * from 0 to 1 as the rim is traversed. 61 */ 62 LINECAP_ROUND, 63 LINECAP_SHARP, ///< sharp point ending 64 LINECAP_SQUARE, ///< square end that extends half the line width beyond the line end 65 }; 66 67 SOverlayTexturedLine() 68 : m_Terrain(NULL), m_Thickness(1.0f), m_Closed(false), m_AlwaysVisible(false), m_StartCapType(LINECAP_FLAT), m_EndCapType(LINECAP_FLAT) 69 {} 53 70 54 71 CTerrain* m_Terrain; 55 72 CTexturePtr m_TextureBase; 56 73 CTexturePtr m_TextureMask; 57 CColor m_Color; 58 std::vector<float> m_Coords; // (x, z) vertex coordinate pairs; y is computed automatically; shape is automatically closed 59 float m_Thickness; // world-space units 74 CColor m_Color; ///< Color to apply to the line texture 75 std::vector<float> m_Coords; ///< (x, z) vertex coordinate pairs; y is computed automatically 76 float m_Thickness; ///< Half-width of the line, in world-space units 77 78 bool m_Closed; ///< Should this line be treated as a closed loop? (if set, the end cap settings are ignored) 79 bool m_AlwaysVisible; ///< Should this line be rendered even under the SoD? 80 LineCapType m_StartCapType; ///< LineCapType to be used at the start of the line 81 LineCapType m_EndCapType; ///< LineCapType to be used at the end of the line 82 83 shared_ptr<CRenderData> m_RenderData; ///< Cached renderer data (shared_ptr so that copies/deletes are automatic) 84 85 /** 86 * Converts a string line cap type into its corresponding LineCap enum value, and returns the resulting value. 87 * If the input string is unrecognized, a warning is issued and a default value is returned. 88 */ 89 static LineCapType StrToLineCapType(const std::wstring& str); 60 90 61 shared_ptr<CRenderData> m_RenderData; // cached renderer data (shared_ptr so that copies/deletes are automatic)62 91 }; 63 92 64 93 /** -
source/graphics/ShaderManager.cpp
diff --git a/source/graphics/ShaderManager.cpp b/source/graphics/ShaderManager.cpp index ba270f0..599bac9 100644
a b bool CShaderManager::NewProgram(const char* name, const std::map<CStr, CStr>& ba 80 80 81 81 if (strncmp(name, "fixed:", 6) == 0) 82 82 { 83 program = CShaderProgramPtr(CShaderProgram::ConstructFFP(name+6 ));83 program = CShaderProgramPtr(CShaderProgram::ConstructFFP(name+6, baseDefines)); 84 84 if (!program) 85 85 return false; 86 86 program->Reload(); -
source/graphics/ShaderProgram.h
diff --git a/source/graphics/ShaderProgram.h b/source/graphics/ShaderProgram.h index 773096f..4d4516c 100644
a b public: 71 71 /** 72 72 * Construct an instance of a pre-defined fixed-function pipeline setup. 73 73 */ 74 static CShaderProgram* ConstructFFP(const std::string& id );74 static CShaderProgram* ConstructFFP(const std::string& id, const std::map<CStr, CStr>& defines); 75 75 76 76 typedef const char* attrib_id_t; 77 77 typedef const char* texture_id_t; -
source/graphics/ShaderProgramFFP.cpp
diff --git a/source/graphics/ShaderProgramFFP.cpp b/source/graphics/ShaderProgramFFP.cpp index b7d0ae3..f2caf26 100644
a b class CShaderProgramFFP_OverlayLine : public CShaderProgramFFP 100 100 ID_objectColor 101 101 }; 102 102 103 bool m_IgnoreLos; 104 103 105 public: 104 CShaderProgramFFP_OverlayLine( ) :106 CShaderProgramFFP_OverlayLine(const std::map<CStr, CStr>& defines) : 105 107 CShaderProgramFFP(STREAM_POS | STREAM_UV0 | STREAM_UV1) 106 108 { 107 109 m_UniformIndexes["losTransform"] = ID_losTransform; … … public: 111 113 m_UniformIndexes["baseTex"] = 0; 112 114 m_UniformIndexes["maskTex"] = 1; 113 115 m_UniformIndexes["losTex"] = 2; 116 117 m_IgnoreLos = (defines.find(CStr("IGNORE_LOS")) != defines.end()); 118 } 119 120 bool IsIgnoreLos() 121 { 122 return m_IgnoreLos; 114 123 } 115 124 116 125 virtual void Uniform(Binding id, float v0, float v1, float v2, float v3) … … public: 145 154 // RGB channels: 146 155 // Unit 0: Load base texture 147 156 // Unit 1: Load mask texture; interpolate with objectColor & base 148 // Unit 2: Load LOS texture; multiply157 // Unit 2: (Load LOS texture; multiply) if not #IGNORE_LOS, pass through otherwise 149 158 // Alpha channel: 150 159 // Unit 0: Load base texture 151 160 // Unit 1: Multiply by objectColor … … public: 163 172 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); 164 173 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); 165 174 175 // ----------------------------------------------------------------------------- 176 166 177 pglActiveTextureARB(GL_TEXTURE1); 167 178 glEnable(GL_TEXTURE_2D); 168 169 179 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); 170 180 // Uniform() sets GL_TEXTURE_ENV_COLOR 171 181 182 // load mask texture; interpolate with objectColor and base; GL_INTERPOLATE takes 3 arguments: 183 // a0 * a2 + a1 * (1 - a2) 172 184 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_INTERPOLATE); 173 185 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_CONSTANT); 174 186 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); … … public: 183 195 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_PREVIOUS); 184 196 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA); 185 197 198 // ----------------------------------------------------------------------------- 199 186 200 pglActiveTextureARB(GL_TEXTURE2); 187 201 glEnable(GL_TEXTURE_2D); 188 189 202 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); 190 203 191 glEnable(GL_TEXTURE_GEN_S); 192 glEnable(GL_TEXTURE_GEN_T); 193 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); 194 glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); 195 // Uniform() sets GL_OBJECT_PLANE values 196 197 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE); 198 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS); 199 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); 200 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE); 201 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_ALPHA); 204 bool ignoreLos = IsIgnoreLos(); 205 if (ignoreLos) 206 { 207 // RGB pass through 208 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); 209 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS); 210 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); 211 } 212 else 213 { 214 // multiply RGB with LoS texture alpha channel 215 glEnable(GL_TEXTURE_GEN_S); 216 glEnable(GL_TEXTURE_GEN_T); 217 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); 218 glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); 219 // Uniform() sets GL_OBJECT_PLANE values 220 221 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE); 222 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS); 223 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); 224 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE); 225 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_ALPHA); 226 } 202 227 228 // alpha pass through 203 229 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); 204 230 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS); 205 231 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); 232 206 233 } 207 234 208 235 virtual void Unbind() … … public: 221 248 } 222 249 }; 223 250 224 /*static*/ CShaderProgram* CShaderProgram::ConstructFFP(const std::string& id )251 /*static*/ CShaderProgram* CShaderProgram::ConstructFFP(const std::string& id, const std::map<CStr, CStr>& defines) 225 252 { 226 253 if (id == "overlayline") 227 return new CShaderProgramFFP_OverlayLine( );254 return new CShaderProgramFFP_OverlayLine(defines); 228 255 229 256 LOGERROR(L"CShaderProgram::ConstructFFP: Invalid id '%hs'", id.c_str()); 230 257 return NULL; -
source/gui/MiniMap.cpp
diff --git a/source/gui/MiniMap.cpp b/source/gui/MiniMap.cpp index 05e234e..97f05cc 100644
a b void CMiniMap::SetCameraPos() 160 160 CVector3D target; 161 161 GetMouseWorldCoordinates(target.X, target.Z); 162 162 target.Y = terrain->GetExactGroundLevel(target.X, target.Z); 163 g_Game->GetView()->MoveCameraTarget(target , true);163 g_Game->GetView()->MoveCameraTarget(target); 164 164 } 165 165 166 166 float CMiniMap::GetAngle() -
source/gui/scripting/ScriptFunctions.cpp
diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index 0fc6ddb..5e1afcb 100644
a b 34 34 #include "ps/CConsole.h" 35 35 #include "ps/Errors.h" 36 36 #include "ps/Game.h" 37 #include "ps/World.h" 37 38 #include "ps/Hotkey.h" 38 39 #include "ps/Overlay.h" 39 40 #include "ps/ProfileViewer.h" … … void CameraFollowFPS(void* UNUSED(cbdata), entity_id_t entityid) 400 401 g_Game->GetView()->CameraFollow(entityid, true); 401 402 } 402 403 404 /// Move camera to a 2D location 405 void CameraMoveTo(void* UNUSED(cbdata), entity_pos_t x, entity_pos_t z) 406 { 407 // called from JS; must not fail 408 if(!(g_Game && g_Game->GetWorld() && g_Game->GetView() && g_Game->GetWorld()->GetTerrain())) 409 return; 410 411 CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); 412 413 CVector3D target; 414 target.X = x.ToFloat(); 415 target.Z = z.ToFloat(); 416 target.Y = terrain->GetExactGroundLevel(target.X, target.Z); 417 418 g_Game->GetView()->MoveCameraTarget(target); 419 } 420 403 421 entity_id_t GetFollowedEntity(void* UNUSED(cbdata)) 404 422 { 405 423 if (g_Game && g_Game->GetView()) … … void QuickLoad(void* UNUSED(cbdata)) 519 537 { 520 538 g_Game->GetTurnManager()->QuickLoad(); 521 539 } 540 522 541 void SetBoundingBoxDebugOverlay(void* UNUSED(cbdata), bool enabled) 523 542 { 524 543 ICmpSelectable::ms_EnableDebugOverlays = enabled; … … void GuiScriptingInit(ScriptInterface& scriptInterface) 574 593 scriptInterface.RegisterFunction<CScriptVal, &GetMapSettings>("GetMapSettings"); 575 594 scriptInterface.RegisterFunction<void, entity_id_t, &CameraFollow>("CameraFollow"); 576 595 scriptInterface.RegisterFunction<void, entity_id_t, &CameraFollowFPS>("CameraFollowFPS"); 596 scriptInterface.RegisterFunction<void, entity_pos_t, entity_pos_t, &CameraMoveTo>("CameraMoveTo"); 577 597 scriptInterface.RegisterFunction<entity_id_t, &GetFollowedEntity>("GetFollowedEntity"); 578 598 scriptInterface.RegisterFunction<bool, std::string, &HotkeyIsPressed_>("HotkeyIsPressed"); 579 599 scriptInterface.RegisterFunction<void, std::wstring, &DisplayErrorDialog>("DisplayErrorDialog"); -
source/maths/Vector2D.h
diff --git a/source/maths/Vector2D.h b/source/maths/Vector2D.h index c61d31b..38a26a4 100644
a b public: 127 127 return CVector2D(X / mag, Y / mag); 128 128 } 129 129 130 /** 131 * Returns a version of this vector rotated counterclockwise by @p angle radians. 132 */ 133 CVector2D Rotated(float angle) 134 { 135 float c = cosf(angle); 136 float s = sinf(angle); 137 return CVector2D( 138 c*X - s*Y, 139 s*X + c*Y 140 ); 141 } 142 143 /** 144 * Rotates this vector counterclockwise by @p angle radians. 145 */ 146 void Rotate(float angle) 147 { 148 float c = cosf(angle); 149 float s = sinf(angle); 150 float newX = c*X - s*Y; 151 float newY = s*X + c*Y; 152 X = newX; 153 Y = newY; 154 } 155 130 156 public: 131 157 float X, Y; 132 158 }; -
source/renderer/OverlayRenderer.cpp
diff --git a/source/renderer/OverlayRenderer.cpp b/source/renderer/OverlayRenderer.cpp index 4e49103..34a818c 100644
a b 1 /* Copyright (C) 201 0Wildfire Games.1 /* Copyright (C) 2011 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 19 19 20 20 #include "OverlayRenderer.h" 21 21 22 #include "maths/MathUtil.h" 23 #include "maths/Quaternion.h" 24 #include "maths/Vector2D.h" 22 25 #include "graphics/LOSTexture.h" 23 26 #include "graphics/Overlay.h" 24 #include "graphics/ShaderManager.h"25 27 #include "graphics/Terrain.h" 26 28 #include "graphics/TextureManager.h" 27 29 #include "lib/ogl.h" … … class CTexturedLineRData : public CRenderData 44 46 { 45 47 public: 46 48 CTexturedLineRData(SOverlayTexturedLine* line) : 47 m_Line(line), m_VB(NULL), m_VBIndices(NULL) 48 { 49 } 49 m_Line(line), m_VB(NULL), m_VBIndices(NULL), m_Raise(.2f) 50 { } 50 51 51 52 ~CTexturedLineRData() 52 53 { … … public: 58 59 59 60 struct SVertex 60 61 { 61 SVertex(CVector3D pos, short u, short v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; }62 SVertex(CVector3D pos, float u, float v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; } 62 63 CVector3D m_Position; 63 GLshort m_UVs[2]; 64 GLfloat m_UVs[2]; 65 float _padding[3]; // 5 floats up till now, so pad with another 3 floats to get a power of 2 64 66 }; 65 cassert(sizeof(SVertex) == 16);67 cassert(sizeof(SVertex) == 32); 66 68 67 69 void Update(); 68 70 69 SOverlayTexturedLine* m_Line; 71 /** 72 * Creates a line cap of the specified type @p endCapType at the end of the segment going in direction @p normal, and appends 73 * the vertices to @p verticesOut in GL_TRIANGLES order. 74 * 75 * @param corner1 One of the two butt-end corner points of the line to which the cap should be attached. 76 * @param corner2 One of the two butt-end corner points of the line to which the cap should be attached. 77 * @param normal Normal vector indicating the direction of the segment to which the cap should be attached. 78 * @param endCapType The type of end cap to produce. 79 * @param verticesOut Output vector of vertices for passing to the renderer. 80 * @param indicesOut Output vector of vertex indices for passing to the renderer. 81 */ 82 void CreateLineCap(const CVector3D& corner1, const CVector3D& corner2, const CVector3D& normal, 83 SOverlayTexturedLine::LineCapType endCapType, std::vector<SVertex>& verticesOut, std::vector<u16>& indicesOut); 84 85 /// Small utility function; grabs the centroid of the positions of two vertices 86 inline CVector3D Centroid(const SVertex& v1, const SVertex& v2) 87 { 88 return (v1.m_Position + v2.m_Position) * 0.5; 89 } 70 90 91 SOverlayTexturedLine* m_Line; 71 92 CVertexBuffer::VBChunk* m_VB; 72 93 CVertexBuffer::VBChunk* m_VBIndices; 94 95 float m_Raise; // small vertical offset of line from terrain to prevent visual glitches 73 96 }; 74 97 75 98 OverlayRenderer::OverlayRenderer() … … void OverlayRenderer::RenderOverlaysAfterWater() 180 203 else 181 204 shaderName = "fixed:overlayline"; 182 205 183 CShaderManager& shaderManager = g_Renderer.GetShaderManager(); 184 CShaderProgramPtr shaderTexLine(shaderManager.LoadProgram(shaderName, std::map<CStr, CStr>())); 185 186 shaderTexLine->Bind(); 187 188 int streamflags = shaderTexLine->GetStreamFlags(); 189 190 if (streamflags & STREAM_POS) 191 glEnableClientState(GL_VERTEX_ARRAY); 192 193 if (streamflags & STREAM_UV0) 194 { 195 pglClientActiveTextureARB(GL_TEXTURE0); 196 glEnableClientState(GL_TEXTURE_COORD_ARRAY); 197 } 198 199 if (streamflags & STREAM_UV1) 200 { 201 pglClientActiveTextureARB(GL_TEXTURE1); 202 glEnableClientState(GL_TEXTURE_COORD_ARRAY); 203 } 206 std::map<CStr, CStr> defAlwaysVisible; 207 defAlwaysVisible.insert(std::make_pair(CStr("IGNORE_LOS"), CStr("1"))); 204 208 205 209 CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture(); 206 shaderTexLine->BindTexture("losTex", los.GetTexture());207 shaderTexLine->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);208 210 209 for (size_t i = 0; i < m->texlines.size(); ++i) 210 { 211 SOverlayTexturedLine* line = m->texlines[i]; 212 if (!line->m_RenderData) 213 continue; 211 CShaderManager& shaderManager = g_Renderer.GetShaderManager(); 212 CShaderProgramPtr shaderTexLineNormal(shaderManager.LoadProgram(shaderName, std::map<CStr, CStr>())); 213 CShaderProgramPtr shaderTexLineAlwaysVisible(shaderManager.LoadProgram(shaderName, defAlwaysVisible)); 214 214 215 shaderTexLine->BindTexture("baseTex", line->m_TextureBase->GetHandle()); 216 shaderTexLine->BindTexture("maskTex", line->m_TextureMask->GetHandle()); 217 shaderTexLine->Uniform("objectColor", line->m_Color); 215 // ---------------------------------------------------------------------------------------- 218 216 219 CTexturedLineRData* rdata = static_cast<CTexturedLineRData*>(line->m_RenderData.get()); 217 shaderTexLineNormal->Bind(); 218 shaderTexLineNormal->BindTexture("losTex", los.GetTexture()); 219 shaderTexLineNormal->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); 220 220 221 GLsizei stride = sizeof(CTexturedLineRData::SVertex);222 CTexturedLineRData::SVertex* base = reinterpret_cast<CTexturedLineRData::SVertex*>(rdata->m_VB->m_Owner->Bind());221 // batch render only the non-always-visible overlay lines using the normal shader 222 RenderTexturedOverlayLines(shaderTexLineNormal, false); 223 223 224 if (streamflags & STREAM_POS) 225 glVertexPointer(3, GL_FLOAT, stride, &base->m_Position[0]); 224 shaderTexLineNormal->Unbind(); 226 225 227 if (streamflags & STREAM_UV0) 228 { 229 pglClientActiveTextureARB(GL_TEXTURE0); 230 glTexCoordPointer(2, GL_SHORT, stride, &base->m_UVs[0]); 231 } 226 // ---------------------------------------------------------------------------------------- 232 227 233 if (streamflags & STREAM_UV1) 234 { 235 pglClientActiveTextureARB(GL_TEXTURE1); 236 glTexCoordPointer(2, GL_SHORT, stride, &base->m_UVs[0]); 237 } 238 239 u8* indexBase = rdata->m_VBIndices->m_Owner->Bind(); 240 glDrawElements(GL_QUAD_STRIP, rdata->m_VBIndices->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*rdata->m_VBIndices->m_Index); 228 shaderTexLineAlwaysVisible->Bind(); 229 // TODO: losTex and losTransform are unused in the always visible shader, but I'm not sure if it's worthwhile messing 230 // with it just to remove these calls 231 shaderTexLineAlwaysVisible->BindTexture("losTex", los.GetTexture()); 232 shaderTexLineAlwaysVisible->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); 241 233 242 g_Renderer.GetStats().m_OverlayTris += rdata->m_VBIndices->m_Count - 2;243 }234 // batch render only the always-visible overlay lines using the LoS-ignored shader 235 RenderTexturedOverlayLines(shaderTexLineAlwaysVisible, true); 244 236 245 shaderTexLine ->Unbind();237 shaderTexLineAlwaysVisible->Unbind(); 246 238 247 239 // TODO: the shader should probably be responsible for unbinding its textures 248 240 g_Renderer.BindTexture(1, 0); … … void OverlayRenderer::RenderOverlaysAfterWater() 260 252 } 261 253 } 262 254 255 void OverlayRenderer::RenderTexturedOverlayLines(CShaderProgramPtr shaderTexLine, bool alwaysVisible) 256 { 257 int streamflags = shaderTexLine->GetStreamFlags(); 258 259 if (streamflags & STREAM_POS) 260 glEnableClientState(GL_VERTEX_ARRAY); 261 262 if (streamflags & STREAM_UV0) 263 { 264 pglClientActiveTextureARB(GL_TEXTURE0); 265 glEnableClientState(GL_TEXTURE_COORD_ARRAY); 266 } 267 268 if (streamflags & STREAM_UV1) 269 { 270 pglClientActiveTextureARB(GL_TEXTURE1); 271 glEnableClientState(GL_TEXTURE_COORD_ARRAY); 272 } 273 274 for (size_t i = 0; i < m->texlines.size(); ++i) 275 { 276 SOverlayTexturedLine* line = m->texlines[i]; 277 278 // render only those lines matching the requested alwaysVisible status 279 if (!line->m_RenderData || line->m_AlwaysVisible != alwaysVisible) 280 continue; 281 282 shaderTexLine->BindTexture("baseTex", line->m_TextureBase->GetHandle()); 283 shaderTexLine->BindTexture("maskTex", line->m_TextureMask->GetHandle()); 284 shaderTexLine->Uniform("objectColor", line->m_Color); 285 286 CTexturedLineRData* rdata = static_cast<CTexturedLineRData*>(line->m_RenderData.get()); 287 if (!rdata->m_VB || !rdata->m_VBIndices) 288 continue; // might have failed to allocate 289 290 // -- render main line quad strip ---------------------- 291 292 GLsizei stride = sizeof(CTexturedLineRData::SVertex); 293 CTexturedLineRData::SVertex* vertexBase = reinterpret_cast<CTexturedLineRData::SVertex*>(rdata->m_VB->m_Owner->Bind()); 294 295 if (streamflags & STREAM_POS) 296 glVertexPointer(3, GL_FLOAT, stride, &vertexBase->m_Position[0]); 297 298 if (streamflags & STREAM_UV0) 299 { 300 pglClientActiveTextureARB(GL_TEXTURE0); 301 glTexCoordPointer(2, GL_FLOAT, stride, &vertexBase->m_UVs[0]); 302 } 303 304 if (streamflags & STREAM_UV1) 305 { 306 pglClientActiveTextureARB(GL_TEXTURE1); 307 glTexCoordPointer(2, GL_FLOAT, stride, &vertexBase->m_UVs[0]); 308 } 309 310 u8* indexBase = rdata->m_VBIndices->m_Owner->Bind(); 311 glDrawElements(GL_TRIANGLES, rdata->m_VBIndices->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*rdata->m_VBIndices->m_Index); 312 g_Renderer.GetStats().m_OverlayTris += rdata->m_VBIndices->m_Count/3; 313 314 } 315 316 } 317 263 318 void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera) 264 319 { 265 320 PROFILE3_GPU("overlays (fg)"); … … void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera) 293 348 }; 294 349 295 350 glVertexPointer(3, GL_FLOAT, sizeof(float)*3, &pos[0].X); 296 297 351 glDrawArrays(GL_QUADS, 0, (GLsizei)4); 298 352 } 299 353 … … void CTexturedLineRData::Update() 312 366 g_VBMan.Release(m_VB); 313 367 m_VB = NULL; 314 368 } 315 316 369 if (m_VBIndices) 317 370 { 318 371 g_VBMan.Release(m_VBIndices); 319 372 m_VBIndices = NULL; 320 373 } 321 374 375 CTerrain* terrain = m_Line->m_Terrain; 322 376 CmpPtr<ICmpWaterManager> cmpWaterManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); 323 377 378 float v = 0.f; 324 379 std::vector<SVertex> vertices; 325 380 std::vector<u16> indices; 326 381 327 short v = 0; 328 329 size_t n = m_Line->m_Coords.size() / 2; 330 ENSURE(n >= 1); 331 332 CTerrain* terrain = m_Line->m_Terrain; 382 size_t n = m_Line->m_Coords.size() / 2; // number of line points 383 bool closed = m_Line->m_Closed; 333 384 334 // TODO: this assumes paths are closed loops; probably should extend this to 335 // handle non-closed paths too 385 ENSURE(n >= 2); // minimum needed to avoid errors (also minimum value to make sense, can't draw a line between 1 point) 336 386 337 387 // In each iteration, p1 is the position of vertex i, p0 is i-1, p2 is i+1. 338 388 // To avoid slightly expensive terrain computations we cycle these around and 339 389 // recompute p2 at the end of each iteration. 340 CVector3D p0 = CVector3D(m_Line->m_Coords[(n-1)*2], 0, m_Line->m_Coords[(n-1)*2+1]); 341 CVector3D p1 = CVector3D(m_Line->m_Coords[0], 0, m_Line->m_Coords[1]); 342 CVector3D p2 = CVector3D(m_Line->m_Coords[(1 % n)*2], 0, m_Line->m_Coords[(1 % n)*2+1]); 390 391 CVector3D p0; 392 CVector3D p1(m_Line->m_Coords[0], 0, m_Line->m_Coords[1]); 393 CVector3D p2(m_Line->m_Coords[(1 % n)*2], 0, m_Line->m_Coords[(1 % n)*2+1]); 394 395 if (closed) 396 // grab the ending point so as to close the loop 397 p0 = CVector3D(m_Line->m_Coords[(n-1)*2], 0, m_Line->m_Coords[(n-1)*2+1]); 398 else 399 // we don't want to loop around and use the direction towards the other end of the line, so create an artificial p0 that 400 // extends the p2 -> p1 direction, and use that point instead 401 p0 = p1 + (p1 - p2); 402 343 403 bool p1floating = false; 344 404 bool p2floating = false; 345 405 … … void CTexturedLineRData::Update() 386 446 if (fabs(l) > 0.000001f) // avoid unlikely divide-by-zero 387 447 b *= m_Line->m_Thickness / l; 388 448 389 // Raise off the terrain a little bit 390 const float raised = 0.2f; 391 392 vertices.push_back(SVertex(p1 + b + norm*raised, 0, v)); 393 indices.push_back(vertices.size() - 1); 449 // Push vertices and indices in GL_TRIANGLES order 450 // 451 // NOTE: in order for OpenGL to successfully render these, the winding order needs to be correct. Basically, it means 452 // that every pair of triangles sharing a side must specify the vertices of that side in the opposite order from the 453 // other triangle. 454 // (see http://www.opengl.org/resources/code/samples/sig99/advanced99/notes/node16.html for an illustration) 455 // 456 // What the code below does is push the indices for a quad composed of two triangles in each iteration. The two triangles 457 // of each quad are indexed using the winding orders (BR, BL, TR) and (TR, BL, TR) (where BR is bottom-right of this 458 // iteration's quad, TR top-right etc). 459 SVertex vertex1(p1 + b + norm*m_Raise, 0.f, v); 460 SVertex vertex2(p1 - b + norm*m_Raise, 1.f, v); 461 vertices.push_back(vertex1); 462 vertices.push_back(vertex2); 463 464 u16 index1 = vertices.size() - 2; // index of vertex1 in this iteration (TR of this quad) 465 u16 index2 = vertices.size() - 1; // index of the vertex2 in this iteration (TL of this quad) 466 467 if (i == 0) 468 { 469 // initial two vertices to continue building triangles from (n must be >= 2 for this to work) 470 indices.push_back(index1); 471 indices.push_back(index2); 472 } 473 else 474 { 475 u16 index1Prev = vertices.size() - 4; // index of the vertex1 in the previous iteration (BR of this quad) 476 u16 index2Prev = vertices.size() - 3; // index of the vertex2 in the previous iteration (BL of this quad) 477 ENSURE(index1Prev >= 0 && index1Prev < vertices.size()); 478 ENSURE(index2Prev >= 0 && index2Prev < vertices.size()); 479 // Add two corner points from last iteration and join with one of our own corners to create triangle 1 480 // (don't need to do this if i == 1 because i == 0 are the first two ones, they don't need to be copied) 481 if (i > 1) 482 { 483 indices.push_back(index1Prev); 484 indices.push_back(index2Prev); 485 } 486 indices.push_back(index1); // complete triangle 1 394 487 395 vertices.push_back(SVertex(p1 - b + norm*raised, 1, v)); 396 indices.push_back(vertices.size() - 1); 488 // create triangle 2, specifying the adjacent side's vertices in the opposite order from triangle 1 489 indices.push_back(index1); 490 indices.push_back(index2Prev); 491 indices.push_back(index2); 492 } 397 493 398 // Alternate V coordinate for debugging494 // alternate V coordinate for debugging 399 495 v = 1 - v; 400 496 401 // Cycle the p's and compute the new p2497 // cycle the p's and compute the new p2 402 498 p0 = p1; 403 499 p1 = p2; 404 500 p1floating = p2floating; 405 p2 = CVector3D(m_Line->m_Coords[((i+2) % n)*2], 0, m_Line->m_Coords[((i+2) % n)*2+1]); 501 502 // if in closed mode, wrap around the coordinate array for p2 -- otherwise, extend linearly 503 if (!closed && i == n-2) 504 // next iteration is the last point of the line, so create an artificial p2 that extends the p0 -> p1 direction 505 p2 = p1 + (p1 - p0); 506 else 507 p2 = CVector3D(m_Line->m_Coords[((i+2) % n)*2], 0, m_Line->m_Coords[((i+2) % n)*2+1]); 508 406 509 p2.Y = terrain->GetExactGroundLevel(p2.X, p2.Z); 407 510 if (p2.Y < w) 408 511 { … … void CTexturedLineRData::Update() 413 516 p2floating = false; 414 517 } 415 518 416 // Close the path 417 indices.push_back(0); 418 indices.push_back(1); 519 if (!closed) 520 { 521 // Create start and end caps. On either end, this is done by taking the centroid between the last and second-to-last pair of 522 // vertices that was generated along the path (i.e. the vertex1's and vertex2's from above), taking a directional vector 523 // between them, and drawing the line cap in the plane given by the two butt-end corner points plus said vector. 524 std::vector<u16> capIndices; 525 std::vector<SVertex> capVertices; 526 527 // create end cap 528 CreateLineCap( 529 // the order of these vertices is important here, swapping them produces caps at the wrong side 530 vertices[vertices.size()-2].m_Position, // top-right vertex of last quad 531 vertices[vertices.size()-1].m_Position, // top-left vertex of last quad 532 // directional vector between centroids of last vertex pair and second-to-last vertex pair 533 (Centroid(vertices[vertices.size()-2], vertices[vertices.size()-1]) - Centroid(vertices[vertices.size()-4], vertices[vertices.size()-3])).Normalized(), 534 m_Line->m_EndCapType, 535 capVertices, 536 capIndices 537 ); 538 539 for (unsigned i = 0; i < capIndices.size(); i++) 540 capIndices[i] += vertices.size(); 541 vertices.insert(vertices.end(), capVertices.begin(), capVertices.end()); 542 indices.insert(indices.end(), capIndices.begin(), capIndices.end()); 543 544 capIndices.clear(); 545 capVertices.clear(); 546 547 // create start cap 548 CreateLineCap( 549 // the order of these vertices is important here, swapping them produces caps at the wrong side 550 vertices[1].m_Position, 551 vertices[0].m_Position, 552 // directional vector between centroids of first vertex pair and second vertex pair 553 (Centroid(vertices[1], vertices[0]) - Centroid(vertices[3], vertices[2])).Normalized(), 554 m_Line->m_StartCapType, 555 capVertices, 556 capIndices 557 ); 558 559 for (unsigned i = 0; i < capIndices.size(); i++) 560 capIndices[i] += vertices.size(); 561 vertices.insert(vertices.end(), capVertices.begin(), capVertices.end()); 562 indices.insert(indices.end(), capIndices.begin(), capIndices.end()); 563 } 564 565 ENSURE(indices.size() % 3 == 0); // GL_TRIANGLES indices, so must be multiple of 3 419 566 420 567 m_VB = g_VBMan.Allocate(sizeof(SVertex), vertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER); 421 m_VB->m_Owner->UpdateChunkVertices(m_VB, &vertices[0]); 568 if (m_VB) 569 { 570 // allocation might fail (e.g. due to too many vertices) 571 m_VB->m_Owner->UpdateChunkVertices(m_VB, &vertices[0]); // copy data into VBO 572 573 for (size_t k = 0; k < indices.size(); ++k) 574 indices[k] += m_VB->m_Index; 575 576 m_VBIndices = g_VBMan.Allocate(sizeof(u16), indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER); 577 if (m_VBIndices) 578 m_VBIndices->m_Owner->UpdateChunkVertices(m_VBIndices, &indices[0]); 579 } 422 580 423 // Update the indices to include the base offset of the vertex data 424 for (size_t k = 0; k < indices.size(); ++k) 425 indices[k] += m_VB->m_Index; 581 } 582 583 void CTexturedLineRData::CreateLineCap(const CVector3D& corner1, const CVector3D& corner2, const CVector3D& lineDirectionNormal, 584 SOverlayTexturedLine::LineCapType endCapType, std::vector<SVertex>& verticesOut, 585 std::vector<u16>& indicesOut) 586 { 587 if (endCapType == SOverlayTexturedLine::LINECAP_FLAT) 588 return; // no action needed, this is the default 589 590 // When not in closed mode, we've created artificial points for the start- and endpoints that extend the line in the 591 // direction of the first and the last segment, respectively. Thus, we know both the start and endpoints have perpendicular 592 // butt endings, i.e. the end corner vertices on either side of the line extend perpendicularly from the segment direction. 593 // That is to say, when viewed from the top, we will have something like 594 // . 595 // this: and not like this: /| 596 // ____. / | 597 // | / . 598 // | / 599 // ____. / 600 // 601 602 int roundCapPoints = 8; // amount of points to sample along the semicircle for rounded caps (including corner points) 603 float radius = m_Line->m_Thickness; 604 605 CVector3D centerPoint = (corner1 + corner2) * 0.5f; 606 SVertex centerVertex(centerPoint, 0.5f, 0.5f); 607 u16 indexOffset = verticesOut.size(); // index offset in verticesOut from where we start adding our vertices 608 609 switch (endCapType) 610 { 611 case SOverlayTexturedLine::LINECAP_SHARP: 612 { 613 roundCapPoints = 3; // creates only one point directly ahead 614 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) 615 centerVertex.m_UVs[0] = 0.480f; // slight visual correction to make the texture match up better at the corner points 616 } 617 // fall-through 618 case SOverlayTexturedLine::LINECAP_ROUND: 619 { 620 // Draw a rounded line cap in the 3D plane of the line specified by the two corner points and the normal vector of the 621 // line's direction. The terrain normal at the centroid between the two corner points is perpendicular to this plane. 622 // The way this works is by taking a vector from the corner points' centroid to one of the corner points (which is then 623 // of radius length), and rotate it around the terrain normal vector in that centroid. This will rotate the vector in 624 // the line's plane, producing the desired rounded cap. 625 626 // To please OpenGL's winding order, this angle needs to be negated depending on whether we start rotating from 627 // the (center -> corner1) or (center -> corner2) vector. For the (center -> corner2) vector, we apparently need to use 628 // the negated angle. 629 float stepAngle = -(float)(M_PI/(roundCapPoints-1)); 630 631 // Push the vertices in triangle fan order (easy to generate GL_TRIANGLES indices for afterwards) 632 // Note that we're manually adding the corner vertices instead of having them be generated by the rotating vector. 633 // This is because we want to support an overly large radius to make the sharp line ending look sharper. 634 verticesOut.push_back(centerVertex); 635 verticesOut.push_back(SVertex(corner2, 0.f, 0.f)); 636 637 // Get the base vector that we will incrementally rotate in the cap plane to produce the radial sample points. 638 // Normally corner2 - centerPoint would suffice for this since it is of radius length, but we want to support custom 639 // radii to support tuning the 'sharpness' of sharp end caps (see above) 640 CVector3D rotationBaseVector = (corner2 - centerPoint).Normalized() * radius; 641 // Calculate the normal vector of the plane in which we're going to be drawing the line cap. This is the vector that 642 // is perpendicular to both baseVector and the 'lineDirectionNormal' vector indicating the direction of the line. 643 // Note that we shouldn't use terrain->CalcExactNormal() here because if the line is being rendered on top of water, 644 // then CalcExactNormal will return the normal vector of the terrain that's underwater (which can be quite funky). 645 CVector3D capPlaneNormal = lineDirectionNormal.Cross(rotationBaseVector).Normalized(); 646 647 for (int i = 1; i < roundCapPoints - 1; ++i) 648 { 649 // Rotate the centerPoint -> corner vector by i*stepAngle radians around the cap plane normal at the center point. 650 CQuaternion quatRotation; 651 quatRotation.FromAxisAngle(capPlaneNormal, i * stepAngle); 652 CVector3D worldPos3D = centerPoint + quatRotation.Rotate(rotationBaseVector); 653 654 // 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 655 // of the texture around the edge of the semicircle) 656 float u = 0.f; 657 float v = clamp((i/(float)(roundCapPoints-1)), 0.f, 1.f); // pos, u, v 658 verticesOut.push_back(SVertex(worldPos3D, u, v)); 659 } 660 661 // connect back to the other butt-end corner point to complete the semicircle 662 verticesOut.push_back(SVertex(corner1, 0.f, 1.f)); 663 664 // now push indices in GL_TRIANGLES order; vertices[indexOffset] is the center vertex, vertices[indexOffset + 1] is the 665 // first corner point, then a bunch of radial samples, and then at the end we have the other corner point again. So: 666 for (int i=1; i < roundCapPoints; ++i) 667 { 668 indicesOut.push_back(indexOffset); // center vertex 669 indicesOut.push_back(indexOffset + i); 670 indicesOut.push_back(indexOffset + i + 1); 671 } 672 } 673 break; 674 675 case SOverlayTexturedLine::LINECAP_SQUARE: 676 { 677 // Extend the (corner1 -> corner2) vector along the direction normal and draw a square line ending consisting of 678 // three triangles (sort of like a triangle fan) 679 // NOTE: The order in which the vertices are pushed out determines the visibility, as they 680 // are rendered only one-sided; the wrong order of vertices will make the cap visible only from the bottom. 681 verticesOut.push_back(centerVertex); 682 verticesOut.push_back(SVertex(corner2, 0.f, 0.f)); 683 verticesOut.push_back(SVertex(corner2 + (lineDirectionNormal * (m_Line->m_Thickness)), 0.f, 0.33333f)); // extend butt corner point 2 along the normal vector 684 verticesOut.push_back(SVertex(corner1 + (lineDirectionNormal * (m_Line->m_Thickness)), 0.f, 0.66666f)); // extend butt corner point 1 along the normal vector 685 verticesOut.push_back(SVertex(corner1, 0.f, 1.0f)); // push butt corner point 1 686 687 for (int i=1; i < 4; ++i) 688 { 689 indicesOut.push_back(indexOffset); // center point 690 indicesOut.push_back(indexOffset + i); 691 indicesOut.push_back(indexOffset + i + 1); 692 } 693 } 694 break; 695 696 default: 697 break; 698 } 426 699 427 m_VBIndices = g_VBMan.Allocate(sizeof(u16), indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER);428 m_VBIndices->m_Owner->UpdateChunkVertices(m_VBIndices, &indices[0]);429 700 } -
source/renderer/OverlayRenderer.h
diff --git a/source/renderer/OverlayRenderer.h b/source/renderer/OverlayRenderer.h index a48ae0a..66de988 100644
a b 1 /* Copyright (C) 201 0Wildfire Games.1 /* Copyright (C) 2011 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 18 18 #ifndef INCLUDED_OVERLAYRENDERER 19 19 #define INCLUDED_OVERLAYRENDERER 20 20 21 #include "graphics/ShaderManager.h" 22 21 23 struct SOverlayLine; 22 24 struct SOverlayTexturedLine; 23 25 struct SOverlaySprite; … … public: 84 86 void RenderForegroundOverlays(const CCamera& viewCamera); 85 87 86 88 private: 89 90 /** 91 * Helper method; renders those overlay lines currently registered in the internals (i.e. in m->texlines) for which the 92 * always visible flag equals @alwaysVisible. Used for batch rendering the overlay lines by their alwaysVisible status, 93 * because this requires a separate shader to be used. 94 */ 95 void RenderTexturedOverlayLines(CShaderProgramPtr shader, bool alwaysVisible); 96 97 private: 87 98 OverlayRendererInternals* m; 88 99 }; 89 100 -
source/renderer/RenderModifiers.h
diff --git a/source/renderer/RenderModifiers.h b/source/renderer/RenderModifiers.h index 01bc887..a7291c2 100644
a b 1 /* Copyright (C) 20 09Wildfire Games.1 /* Copyright (C) 2011 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … private: 135 135 136 136 137 137 /** 138 * Class RenderModifierRenderer: Interface to a model renderer that can render139 * its models via a RenderModifier that sets up fragment stages.140 */141 class RenderModifierRenderer : public ModelRenderer142 {143 public:144 RenderModifierRenderer() { }145 virtual ~RenderModifierRenderer();146 };147 148 149 /**150 138 * Class PlainRenderModifier: RenderModifier that simply uses opaque textures 151 139 * modulated by primary color. It is used for normal, no-frills models. 152 140 */ -
source/scriptinterface/ScriptInterface.cpp
diff --git a/source/scriptinterface/ScriptInterface.cpp b/source/scriptinterface/ScriptInterface.cpp index 46910bd..4d1d1f4 100644
a b ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const sh 503 503 m_nativeScope = JS_DefineObject(m_cx, m_glob, nativeScopeName, NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY 504 504 | JSPROP_PERMANENT); 505 505 506 JS_DefineFunction(m_cx, m_glob, "print", ::print, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);507 JS_DefineFunction(m_cx, m_glob, "log", ::logmsg, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);508 JS_DefineFunction(m_cx, m_glob, "warn", ::warn, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);509 JS_DefineFunction(m_cx, m_glob, "error", ::error, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);510 JS_DefineFunction(m_cx, m_glob, "deepcopy", ::deepcopy, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);506 JS_DefineFunction(m_cx, m_glob, "print", ::print, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); 507 JS_DefineFunction(m_cx, m_glob, "log", ::logmsg, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); 508 JS_DefineFunction(m_cx, m_glob, "warn", ::warn, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); 509 JS_DefineFunction(m_cx, m_glob, "error", ::error, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); 510 JS_DefineFunction(m_cx, m_glob, "deepcopy", ::deepcopy, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); 511 511 512 512 Register("ProfileStart", ::ProfileStart, 1); 513 513 Register("ProfileStop", ::ProfileStop, 0); -
source/simulation2/TypeList.h
diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h index 60d1f54..0157b0f 100644
a b COMPONENT(Position) // must be before VisualActor 118 118 INTERFACE(ProjectileManager) 119 119 COMPONENT(ProjectileManager) 120 120 121 INTERFACE(RallyPointRenderer) 122 COMPONENT(RallyPointRenderer) 123 121 124 INTERFACE(RangeManager) 122 125 COMPONENT(RangeManager) 123 126 -
new file source/simulation2/components/CCmpRallyPointRenderer.cpp
diff --git a/source/simulation2/components/CCmpRallyPointRenderer.cpp b/source/simulation2/components/CCmpRallyPointRenderer.cpp new file mode 100644 index 0000000..d8cd196
- + 1 /* Copyright (C) 2011 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "precompiled.h" 19 #include "ICmpRallyPointRenderer.h" 20 21 #include "simulation2/MessageTypes.h" 22 #include "simulation2/components/ICmpFootprint.h" 23 #include "simulation2/components/ICmpObstructionManager.h" 24 #include "simulation2/components/ICmpOwnership.h" 25 #include "simulation2/components/ICmpPathfinder.h" 26 #include "simulation2/components/ICmpPlayer.h" 27 #include "simulation2/components/ICmpPlayerManager.h" 28 #include "simulation2/components/ICmpPosition.h" 29 #include "simulation2/components/ICmpRangeManager.h" 30 #include "simulation2/components/ICmpTerrain.h" 31 #include "simulation2/components/ICmpVisual.h" 32 #include "simulation2/components/ICmpWaterManager.h" 33 #include "simulation2/helpers/Render.h" 34 #include "simulation2/helpers/Geometry.h" 35 #include "simulation2/system/Component.h" 36 37 #include "ps/CLogger.h" 38 #include "graphics/Overlay.h" 39 #include "graphics/TextureManager.h" 40 #include "renderer/Renderer.h" 41 42 struct SVisibilitySegment 43 { 44 bool m_Visible; 45 size_t m_StartIndex; 46 size_t m_EndIndex; // inclusive 47 48 SVisibilitySegment(bool visible, size_t startIndex, size_t endIndex) 49 : m_Visible(visible), m_StartIndex(startIndex), m_EndIndex(endIndex) 50 {} 51 52 bool operator==(const SVisibilitySegment& other) const 53 { 54 return (m_Visible == other.m_Visible && m_StartIndex == other.m_StartIndex && m_EndIndex == other.m_EndIndex); 55 } 56 57 bool operator!=(const SVisibilitySegment& other) const 58 { 59 return !(*this == other); 60 } 61 62 bool IsSinglePoint() 63 { 64 return (m_StartIndex == m_EndIndex); 65 } 66 }; 67 68 class CCmpRallyPointRenderer : public ICmpRallyPointRenderer 69 { 70 // import some types for less verbosity 71 typedef ICmpPathfinder::Path Path; 72 typedef ICmpPathfinder::Goal Goal; 73 typedef ICmpPathfinder::Waypoint Waypoint; 74 typedef ICmpRangeManager::CLosQuerier CLosQuerier; 75 typedef SOverlayTexturedLine::LineCapType LineCapType; 76 77 public: 78 static void ClassInit(CComponentManager& componentManager) 79 { 80 componentManager.SubscribeToMessageType(MT_RenderSubmit); 81 componentManager.SubscribeToMessageType(MT_OwnershipChanged); 82 componentManager.SubscribeToMessageType(MT_TurnStart); 83 // TODO: should probably also listen to movement messages (unlikely to happen in-game, but might occur inside atlas) 84 } 85 86 DEFAULT_COMPONENT_ALLOCATOR(RallyPointRenderer) 87 88 protected: 89 90 /// Display position of the rally point. Note that this is merely the display position; it is not necessarily the same as the 91 /// actual position used in the simulation at any given time. In particular, we need this separate copy to support 92 /// instantaneously rendering the rally point marker/line when the user sets one in-game (instead of waiting until the 93 /// network-synchronization code sets it on the RallyPoint component, which might take up to half a second). 94 CFixedVector2D m_RallyPoint; 95 /// Full path to the rally point as returned by the pathfinder, with some post-processing applied to reduce zig/zagging. 96 std::vector<CVector2D> m_Path; 97 /// Visibility segments of the rally point path; splits the path into SoD/non-SoD segments. 98 std::deque<SVisibilitySegment> m_VisibilitySegments; 99 100 bool m_Displayed; ///< Should we render the rally point and its path line? (set from JS when e.g. the unit is selected/deselected) 101 bool m_SmoothPath; ///< Smooth the path before rendering? 102 103 entity_id_t m_MarkerEntityId; ///< Entity ID of the rally point marker. Allocated when first displayed. 104 std::wstring m_MarkerTemplate; ///< Template name of the rally point marker. 105 106 /// Marker connector line settings (loaded from XML) 107 float m_LineThickness; 108 CColor m_LineColor; 109 CColor m_LineDashColor; 110 LineCapType m_LineStartCapType; 111 LineCapType m_LineEndCapType; 112 std::wstring m_LineTexturePath; 113 std::wstring m_LineTextureMaskPath; 114 std::string m_LinePassabilityClass; ///< Pathfinder passability class to use for computing the (long-range) marker line path. 115 std::string m_LineCostClass; ///< Pathfinder cost class to use for computing the (long-range) marker line path. 116 117 CTexturePtr m_Texture; 118 CTexturePtr m_TextureMask; 119 120 /// Textured overlay lines to be used for rendering the marker line. There can be multiple because we may need to render 121 /// dashes for segments that are inside the SoD. 122 std::vector<SOverlayTexturedLine> m_TexturedOverlayLines; 123 124 /// Draw little overlay circles to indicate where the exact path points are? 125 bool m_EnableDebugNodeOverlay; 126 std::vector<SOverlayLine> m_DebugNodeOverlays; 127 128 public: 129 130 static std::string GetSchema() 131 { 132 return 133 "<a:help>Displays a rally point marker where created units will gather when spawned</a:help>" 134 "<a:example>" 135 "<MarkerTemplate>special/rallypoint</MarkerTemplate>" 136 "<LineThickness>0.75</LineThickness>" 137 "<LineStartCap>round</LineStartCap>" 138 "<LineEndCap>square</LineEndCap>" 139 "<LineColour r='20' g='128' b='240'></LineColour>" 140 "<LineDashColour r='158' g='11' b='15'></LineDashColour>" 141 "<LineCostClass>default</LineCostClass>" 142 "<LinePassabilityClass>default</LinePassabilityClass>" 143 "</a:example>" 144 "<element name='MarkerTemplate' a:help='Template name for the rally point marker entity (typically a waypoint flag actor)'>" 145 "<text/>" 146 "</element>" 147 "<element name='LineTexture' a:help='Texture file to use for the rally point line'>" 148 "<text />" 149 "</element>" 150 "<element name='LineTextureMask' a:help='Texture mask to indicate where overlay colors are to be applied (see LineColour and LineDashColour)'>" 151 "<text />" 152 "</element>" 153 "<element name='LineThickness' a:help='Thickness of the marker line connecting the entity to the rally point marker'>" 154 "<data type='decimal'/>" 155 "</element>" 156 "<element name='LineColour'>" 157 "<attribute name='r'>" 158 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" 159 "</attribute>" 160 "<attribute name='g'>" 161 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" 162 "</attribute>" 163 "<attribute name='b'>" 164 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" 165 "</attribute>" 166 "</element>" 167 "<element name='LineDashColour'>" 168 "<attribute name='r'>" 169 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" 170 "</attribute>" 171 "<attribute name='g'>" 172 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" 173 "</attribute>" 174 "<attribute name='b'>" 175 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" 176 "</attribute>" 177 "</element>" 178 "<element name='LineStartCap'>" 179 "<choice>" 180 "<value a:help='Abrupt line ending; line endings are not closed'>flat</value>" 181 "<value a:help='Semi-circular line end cap'>round</value>" 182 "<value a:help='Sharp, pointy line end cap'>sharp</value>" 183 "<value a:help='Square line end cap'>square</value>" 184 "</choice>" 185 "</element>" 186 "<element name='LineEndCap'>" 187 "<choice>" 188 "<value a:help='Abrupt line ending; line endings are not closed'>flat</value>" 189 "<value a:help='Semi-circular line end cap'>round</value>" 190 "<value a:help='Sharp, pointy line end cap'>sharp</value>" 191 "<value a:help='Square line end cap'>square</value>" 192 "</choice>" 193 "</element>" 194 "<element name='LinePassabilityClass' a:help='The pathfinder passability class to use for computing the rally point marker line path'>" 195 "<text />" 196 "</element>" 197 "<element name='LineCostClass' a:help='The pathfinder cost class to use for computing the rally point marker line path'>" 198 "<text />" 199 "</element>"; 200 } 201 202 virtual void Init(const CParamNode& paramNode); 203 204 virtual void Deinit() 205 { 206 } 207 208 virtual void Serialize(ISerializer& UNUSED(serialize)) 209 { 210 // do NOT serialize anything; this is a rendering-only component, it does not and should not affect simulation state 211 } 212 213 virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize)) 214 { 215 Init(paramNode); 216 } 217 218 virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) 219 { 220 switch (msg.GetType()) 221 { 222 case MT_RenderSubmit: 223 { 224 if (m_Displayed && IsSet()) 225 { 226 const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg); 227 RenderSubmit(msgData.collector); 228 } 229 } 230 break; 231 case MT_OwnershipChanged: 232 { 233 CreateMarker(); // change marker actor to new owner's civilization 234 UpdateMarkerPosition(); // the new marker doesn't have a position yet, so let's make sure to update it 235 } 236 break; 237 case MT_TurnStart: 238 { 239 UpdateOverlayLines(); // check for changes to the SoD and update the overlay lines accordingly 240 } 241 break; 242 } 243 } 244 245 virtual void SetPosition(CFixedVector2D pos) 246 { 247 if (m_RallyPoint != pos) 248 { 249 m_RallyPoint = pos; 250 UpdateMarkerPosition(); 251 RecomputeRallyPointPath(); 252 } 253 } 254 255 virtual void SetDisplayed(bool displayed) 256 { 257 if (m_Displayed != displayed) 258 { 259 m_Displayed = displayed; 260 261 // move the marker out of oblivion and back into the real world, or vice-versa 262 UpdateMarkerPosition(); 263 264 // Check for changes to the SoD and update the overlay lines accordingly. We need to do this here because this method 265 // only takes effect when the display flag is active; we need to pick up changes to the SoD that might have occurred 266 // while this rally point was not being displayed. 267 UpdateOverlayLines(); 268 } 269 } 270 271 private: 272 273 /** 274 * Returns true iff a display rally point is set; i.e., if we have a point to render our marker/line at. 275 */ 276 bool IsSet() 277 { 278 return !m_RallyPoint.IsZero(); 279 } 280 281 /** 282 * (Re)creates the rally point marker entity. Called upon initialization and whenever the ownership of this entity changes, as 283 * the marker actor depends on the owner's civilization. If a marker entity already exists, it will be destroyed first. 284 */ 285 void CreateMarker(); 286 287 /** 288 * Repositions the rally point marker; moves it outside of the world (ie. hides it), or positions it at the rally point. 289 * Should be called whenever either the position of the rally point changes (including whether it is set or not), or the display 290 * flag changes. 291 */ 292 void UpdateMarkerPosition(); 293 294 /** 295 * Recomputes the full path from this entity to the rally point, and does all the necessary post-processing to make it prettier. 296 * Should be called whenever the rally point position changes. 297 */ 298 void RecomputeRallyPointPath(); 299 300 /** 301 * Checks for changes to the SoD to the previously saved state, and reconstructs the visibility segments and overlay lines to 302 * match if necessary. Does nothing if the rally point line is not currently set to be displayed, or if the rally point is 303 * not set. 304 */ 305 void UpdateOverlayLines(); 306 307 /** 308 * Sets up the overlay lines for rendering according to the current full path and visibility segments. Does all the necessary 309 * splitting of the line into solid and dashed pieces (for the SoD). Should be called whenever the SoD has changed. If no full 310 * path is currently set, this method does nothing. 311 */ 312 void ConstructOverlayLines(); 313 314 /** 315 * Removes points from @p coords that are obstructed by the originating building's footprint, and links up the last point 316 * nicely to the edge of the building's footprint. Only needed if the pathfinder can possibly return obstructed tile waypoints, 317 * i.e. when pathfinding is started from an obstructed tile. 318 */ 319 void FixFootprintWaypoints(std::vector<CVector2D>& coords, CmpPtr<ICmpPosition> cmpPosition, CmpPtr<ICmpFootprint> cmpFootprint); 320 321 /** 322 * Removes points from @p coords that are inside the shroud of darkness, i.e. where the player shouldn't be able to get any 323 * information about the positions of various buildings and whatnot from the rally point path. 324 */ 325 void FixInvisibleWaypoints(std::vector<CVector2D>& coords); 326 327 /** 328 * Returns a list of indices of waypoints in the current path (m_FullPath) where the LOS visibility changes, ordered from 329 * building to rally point. Used to construct the overlay line segments and track changes to the shroud of darkness. 330 */ 331 void GetVisibilitySegments(std::deque<SVisibilitySegment>& out); 332 333 /** 334 * Simplifies the path by removing waypoints that lie between two points that are visible from one another. This is primarily 335 * intended to reduce some unnecessary curviness of the path; the pathfinder returns a mathematically (near-)optimal path, which 336 * will happily curve and bend to reduce costs. Visually, it doesn't make sense for a rally point path to curve and bend when it 337 * could just as well have gone in a straight line; that's why we have this, to make it look more natural. 338 * 339 * @p coords array of path coordinates to simplify 340 * @p maxSegmentLinks if non-zero, indicates the maximum amount of consecutive node-to-node links that can be joined into a 341 * single link. If this value is set to e.g. 1, then no reductions will be performed. A value of 3 means that 342 * at most 3 consecutive node links will be joined into a single link. 343 * @p floating whether to consider nodes who are under the water level as floating on top of the water 344 */ 345 void ReduceSegmentsByVisibility(std::vector<CVector2D>& coords, unsigned maxSegmentLinks = 0, bool floating = true); 346 347 /** 348 * Helper function to GetVisibilitySegments, factored out for testing. Merges single-point segments with its neighbouring 349 * segments. You should not have to call this method directly. 350 */ 351 static void MergeVisibilitySegments(std::deque<SVisibilitySegment>& segments); 352 353 void RenderSubmit(SceneCollector& collector); 354 }; 355 356 REGISTER_COMPONENT_TYPE(RallyPointRenderer) 357 358 void CCmpRallyPointRenderer::Init(const CParamNode& paramNode) 359 { 360 m_Displayed = false; 361 m_SmoothPath = true; 362 m_MarkerEntityId = INVALID_ENTITY; 363 m_EnableDebugNodeOverlay = false; 364 365 // --------------------------------------------------------------------------------------------- 366 // load some XML configuration data (schema guarantees that all these nodes are valid) 367 368 m_MarkerTemplate = paramNode.GetChild("MarkerTemplate").ToString(); 369 370 const CParamNode& lineColor = paramNode.GetChild("LineColour"); 371 m_LineColor = CColor( 372 lineColor.GetChild("@r").ToInt()/255.f, 373 lineColor.GetChild("@g").ToInt()/255.f, 374 lineColor.GetChild("@b").ToInt()/255.f, 375 1.f 376 ); 377 378 const CParamNode& lineDashColor = paramNode.GetChild("LineDashColour"); 379 m_LineDashColor = CColor( 380 lineDashColor.GetChild("@r").ToInt()/255.f, 381 lineDashColor.GetChild("@g").ToInt()/255.f, 382 lineDashColor.GetChild("@b").ToInt()/255.f, 383 1.f 384 ); 385 386 m_LineThickness = paramNode.GetChild("LineThickness").ToFixed().ToFloat(); 387 m_LineTexturePath = paramNode.GetChild("LineTexture").ToString(); 388 m_LineTextureMaskPath = paramNode.GetChild("LineTextureMask").ToString(); 389 m_LineStartCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineStartCap").ToString()); 390 m_LineEndCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineEndCap").ToString()); 391 m_LineCostClass = paramNode.GetChild("LineCostClass").ToUTF8(); 392 m_LinePassabilityClass = paramNode.GetChild("LinePassabilityClass").ToUTF8(); 393 394 // --------------------------------------------------------------------------------------------- 395 // load some textures 396 397 if (g_Renderer.IsInitialised()) 398 { 399 CTextureProperties texturePropsBase(m_LineTexturePath); 400 texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); 401 texturePropsBase.SetMaxAnisotropy(4.f); 402 m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase); 403 404 CTextureProperties texturePropsMask(m_LineTextureMaskPath); 405 texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); 406 texturePropsMask.SetMaxAnisotropy(4.f); 407 m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask); 408 } 409 410 // --------------------------------------------------------------------------------------------- 411 412 CreateMarker(); // TODO: evaluate how much load this puts on the entity IDs, if it's too high then we can do this on-demand) 413 } 414 415 void CCmpRallyPointRenderer::CreateMarker() 416 { 417 CComponentManager& componentMgr = GetSimContext().GetComponentManager(); 418 419 // if a marker entity already exists, kill it first 420 if (m_MarkerEntityId != INVALID_ENTITY) 421 { 422 CmpPtr<ICmpPosition> markerCmpPosition(GetSimContext(), m_MarkerEntityId); 423 if (!markerCmpPosition.null()) 424 markerCmpPosition->MoveOutOfWorld(); 425 426 componentMgr.DestroyComponentsSoon(m_MarkerEntityId); // queue entity for destruction 427 m_MarkerEntityId = INVALID_ENTITY; // make sure any code below doesn't try to access the soon-to-be-destroyed entity anymore 428 } 429 430 // allocate a new entity for the marker 431 if (!m_MarkerTemplate.empty()) 432 { 433 m_MarkerEntityId = componentMgr.AllocateNewLocalEntity(); 434 if (m_MarkerEntityId != INVALID_ENTITY) 435 m_MarkerEntityId = componentMgr.AddEntity(m_MarkerTemplate, m_MarkerEntityId); 436 } 437 438 // set rally point flag selection based on player civilization 439 CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), GetEntityId()); 440 if (!cmpOwnership.null()) 441 { 442 player_id_t ownerId = cmpOwnership->GetOwner(); 443 if (ownerId != INVALID_PLAYER) 444 { 445 CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY); 446 ENSURE(!cmpPlayerManager.null()); 447 448 CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(ownerId)); 449 if (!cmpPlayer.null()) 450 { 451 CmpPtr<ICmpVisual> cmpVisualActor(GetSimContext(), m_MarkerEntityId); 452 if (!cmpVisualActor.null()) 453 { 454 cmpVisualActor->SetUnitEntitySelection(CStrW(cmpPlayer->GetCiv()).ToUTF8()); 455 } 456 } 457 } 458 } 459 } 460 461 void CCmpRallyPointRenderer::UpdateMarkerPosition() 462 { 463 if (m_MarkerEntityId == INVALID_ENTITY) 464 return; // there is no valid marker entity, no use trying to position it 465 466 CmpPtr<ICmpPosition> markerCmpPosition(GetSimContext(), m_MarkerEntityId); 467 if (!markerCmpPosition.null()) 468 { 469 if (m_Displayed && IsSet()) 470 { 471 markerCmpPosition->JumpTo(m_RallyPoint.X, m_RallyPoint.Y); 472 } 473 else 474 { 475 markerCmpPosition->MoveOutOfWorld(); // hide it 476 } 477 } 478 } 479 480 void CCmpRallyPointRenderer::RecomputeRallyPointPath() 481 { 482 m_Path.clear(); 483 m_VisibilitySegments.clear(); 484 485 if (!IsSet()) 486 return; // no use computing a path if the rally point isn't set 487 488 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId()); 489 if (cmpPosition.null() || !cmpPosition->IsInWorld()) 490 return; // no point going on if this entity doesn't have a position or is outside of the world 491 492 CmpPtr<ICmpFootprint> cmpFootprint(GetSimContext(), GetEntityId()); 493 CmpPtr<ICmpPathfinder> cmpPathFinder(GetSimContext(), SYSTEM_ENTITY); 494 495 // ------------------------------------------------------------------------------------------------- 496 497 entity_pos_t pathStartX = cmpPosition->GetPosition2D().X; 498 entity_pos_t pathStartY = cmpPosition->GetPosition2D().Y; 499 500 // Find a long path to the goal point -- this uses the tile-based pathfinder, which will return a 501 // list of waypoints (i.e. a Path) from the building to the goal, where each waypoint is centered 502 // at a tile. We'll have to do some post-processing on the path to get it smooth. 503 Path path; 504 std::vector<Waypoint>& waypoints = path.m_Waypoints; 505 506 Goal goal = { Goal::POINT, m_RallyPoint.X, m_RallyPoint.Y }; 507 cmpPathFinder->ComputePath( 508 pathStartX, 509 pathStartY, 510 goal, 511 cmpPathFinder->GetPassabilityClass(m_LinePassabilityClass), 512 cmpPathFinder->GetCostClass(m_LineCostClass), 513 path 514 ); 515 516 if (path.m_Waypoints.size() < 2) 517 return; // not likely to happen, but can't hurt to check 518 519 // From here on, we choose to represent the waypoints as CVector2D floats to avoid to have to convert back and forth 520 // between fixed-point Waypoint/CFixedVector2D and various other float-based formats used by interpolation and whatnot. 521 // Since we'll only be further using these points for rendering purposes, using floats should be fine. 522 523 // Make sure to add the actual goal point as the last point (the long pathfinder only finds paths to the tile closest to the 524 // goal, so we need to complete the last bit from the closest tile to the rally point itself) 525 // NOTE: the points are returned in reverse order (from the goal to the start point), so we actually need to insert it at the 526 // front of the coordinate list. Hence, we'll do this first before appending the rest of the fixed waypoints as CVector2Ds. 527 528 Waypoint& lastWaypoint = waypoints.back(); 529 if (lastWaypoint.x != goal.x || lastWaypoint.z != goal.z) 530 m_Path.push_back(CVector2D(goal.x.ToFloat(), goal.z.ToFloat())); 531 532 // add the rest of the waypoints 533 for (size_t i = 0; i < waypoints.size(); ++i) 534 m_Path.push_back(CVector2D(waypoints[i].x.ToFloat(), waypoints[i].z.ToFloat())); 535 536 // ------------------------------------------------------------------------------------------- 537 // post-processing 538 539 // Linearize the path; 540 // Pass through the waypoints, averaging each waypoint with its next one except the last one. Because the path 541 // goes from the marker to this entity and we want to keep the point at the marker's exact position, loop backwards through the 542 // waypoints so that the marker waypoint is maintained. 543 // TODO: see if we can do this at the same time as the waypoint -> coord conversion above 544 for(size_t i = m_Path.size() - 1; i > 0; --i) 545 m_Path[i] = (m_Path[i] + m_Path[i-1]) / 2.0f; 546 547 // if there's a footprint, remove any points returned by the pathfinder that may be on obstructed footprint tiles 548 if (!cmpFootprint.null()) 549 FixFootprintWaypoints(m_Path, cmpPosition, cmpFootprint); 550 551 // Eliminate some consecutive waypoints that are visible from eachother. Reduce across a maximum distance of approx. 6 tiles 552 // (prevents segments that are too long to properly stick to the terrain) 553 ReduceSegmentsByVisibility(m_Path, 6); 554 555 //// <DEBUG> /////////////////////////////////////////////// 556 if (m_EnableDebugNodeOverlay) 557 m_DebugNodeOverlays.clear(); 558 559 if (m_EnableDebugNodeOverlay && m_SmoothPath) 560 { 561 // Create separate control point overlays so we can differentiate when using smoothing (offset them a little higher from the 562 // terrain so we can still see them after the interpolated points are added) 563 for (size_t j = 0; j < m_Path.size(); ++j) 564 { 565 SOverlayLine overlayLine; 566 overlayLine.m_Color = CColor(1.0f, 0.0f, 0.0f, 1.0f); 567 overlayLine.m_Thickness = 2; 568 SimRender::ConstructSquareOnGround(GetSimContext(), m_Path[j].X, m_Path[j].Y, 0.2f, 0.2f, 1.0f, overlayLine, true); 569 m_DebugNodeOverlays.push_back(overlayLine); 570 } 571 } 572 //// </DEBUG> ////////////////////////////////////////////// 573 574 if (m_SmoothPath) 575 // The number of points to interpolate goes hand in hand with the maximum amount of node links allowed to be joined together 576 // by the visibility reduction. The more node links that can be joined together, the more interpolated points you need to 577 // generate to be able to deal with local terrain height changes. 578 SimRender::InterpolatePointsRNS(m_Path, false, 0, 8); // no offset, keep line at its exact path 579 580 // ------------------------------------------------------------------------------------------- 581 582 // find which point is the last visible point before going into the SoD, so we have a point to compare to on the next turn 583 GetVisibilitySegments(m_VisibilitySegments); 584 585 // build overlay lines for the new path 586 ConstructOverlayLines(); 587 } 588 589 void CCmpRallyPointRenderer::ConstructOverlayLines() 590 { 591 // We need to create a new SOverlayTexturedLine every time we want to change the coordinates after having passed it to the 592 // renderer, because it does some fancy vertex buffering thing and caches them internally instead of recomputing them on every 593 // pass (which is only sensible). 594 m_TexturedOverlayLines.clear(); 595 596 if (m_Path.size() < 2) 597 return; 598 599 CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); 600 LineCapType dashesLineCapType = SOverlayTexturedLine::LINECAP_ROUND; // line caps to use for the dashed segments (and any other segment's edges that border it) 601 602 for (std::deque<SVisibilitySegment>::const_iterator it = m_VisibilitySegments.begin(); it != m_VisibilitySegments.end(); ++it) 603 { 604 const SVisibilitySegment& segment = (*it); 605 606 if (segment.m_Visible) 607 { 608 // does this segment border on the building or rally point flag on either side? 609 bool bordersBuilding = (segment.m_EndIndex == m_Path.size() - 1); 610 bool bordersFlag = (segment.m_StartIndex == 0); 611 612 // construct solid textured overlay line along a subset of the full path points from startPointIdx to endPointIdx 613 SOverlayTexturedLine overlayLine; 614 overlayLine.m_Thickness = m_LineThickness; 615 overlayLine.m_Terrain = cmpTerrain->GetCTerrain(); 616 overlayLine.m_TextureBase = m_Texture; 617 overlayLine.m_TextureMask = m_TextureMask; 618 overlayLine.m_Color = m_LineColor; 619 overlayLine.m_Closed = false; 620 // we should take care to only use m_LineXCap for the actual end points at the building and the rally point; any intermediate 621 // end points (i.e., that border a dashed segment) should have the dashed cap 622 // the path line is actually in reverse order as well, so let's swap out the start and end caps 623 overlayLine.m_StartCapType = (bordersFlag ? m_LineEndCapType : dashesLineCapType); 624 overlayLine.m_EndCapType = (bordersBuilding ? m_LineStartCapType : dashesLineCapType); 625 overlayLine.m_AlwaysVisible = true; 626 627 // push overlay line coordinates 628 ENSURE(segment.m_EndIndex > segment.m_StartIndex); 629 for (size_t j = segment.m_StartIndex; j <= segment.m_EndIndex; ++j) // end index is inclusive here 630 { 631 overlayLine.m_Coords.push_back(m_Path[j].X); 632 overlayLine.m_Coords.push_back(m_Path[j].Y); 633 } 634 635 m_TexturedOverlayLines.push_back(overlayLine); 636 } 637 else 638 { 639 // construct dashed line from startPointIdx to endPointIdx, add textured overlay lines for it to the render list 640 std::vector<CVector2D> straightLine; 641 straightLine.push_back(m_Path[segment.m_StartIndex]); 642 straightLine.push_back(m_Path[segment.m_EndIndex]); 643 644 // We always want to have the dashed line end at either point with a full dash (i.e. not a cleared space), so that the dashed 645 // area is visually obvious. That implies that we want at least So, let's do some calculations to see what size we should make 646 // the dashes and clears. 647 648 float maxDashSize = 3.f; 649 float maxClearSize = 3.f; 650 651 float dashSize = maxDashSize; 652 float clearSize = maxClearSize; 653 float pairDashRatio = (dashSize / (dashSize + clearSize)); // ratio of the dash's length to a (dash + clear) pair's length 654 655 float distance = (m_Path[segment.m_StartIndex] - m_Path[segment.m_EndIndex]).Length(); // straight-line distance between the points 656 657 // see how many pairs (dash + clear) can fit into the distance unmodified. Then check the remaining distance; if it's not exactly 658 // a dash size's worth (and it likely won't be), then adjust the dash/clear sizes slightly so that it is. 659 int numFitUnmodified = floor(distance/(dashSize + clearSize)); 660 float remainderDistance = distance - (numFitUnmodified * (dashSize + clearSize)); 661 662 // Now we want to make remainderDistance equal exactly one dash size (i.e. maxDashSize) by scaling dashSize and clearSize slightly. 663 // We have (remainderDistance - maxDashSize) of space to distribute over numFitUnmodified instances of (dashSize + clearSize) to make 664 // it fit, so each (dashSize + clearSize) pair needs to adjust its length by (remainderDistance - maxDashSize)/numFitUnmodified 665 // (which will be positive or negative accordingly). This number can then be distributed further proportionally among the dash's 666 // length and the clear's length. 667 668 // we always want to have at least one dash/clear pair (i.e., "|===| |===|"); also, we need to avoid division by zero below. 669 numFitUnmodified = std::max(1, numFitUnmodified); 670 671 float pairwiseLengthDifference = (remainderDistance - maxDashSize)/numFitUnmodified; // can be either positive or negative 672 dashSize += pairDashRatio * pairwiseLengthDifference; 673 clearSize += (1 - pairDashRatio) * pairwiseLengthDifference; 674 675 // ------------------------------------------------------------------------------------------------ 676 677 SDashedLine dashedLine; 678 SimRender::ConstructDashedLine(straightLine, dashedLine, dashSize, clearSize); 679 680 // build overlay lines for dashes 681 size_t numDashes = dashedLine.m_StartIndices.size(); 682 for (size_t i=0; i < numDashes; i++) 683 { 684 SOverlayTexturedLine dashOverlay; 685 686 dashOverlay.m_Thickness = m_LineThickness; 687 dashOverlay.m_Terrain = cmpTerrain->GetCTerrain(); 688 dashOverlay.m_TextureBase = m_Texture; 689 dashOverlay.m_TextureMask = m_TextureMask; 690 dashOverlay.m_Color = m_LineDashColor; 691 dashOverlay.m_Closed = false; 692 dashOverlay.m_StartCapType = dashesLineCapType; 693 dashOverlay.m_EndCapType = dashesLineCapType; 694 dashOverlay.m_AlwaysVisible = true; 695 // TODO: maybe adjust the elevation of the dashes to be a little lower, so that it slides underneath the actual path 696 697 size_t dashStartIndex = dashedLine.m_StartIndices[i]; 698 size_t dashEndIndex = dashedLine.GetEndIndex(i); 699 ENSURE(dashEndIndex > dashStartIndex); 700 701 for (size_t n = dashStartIndex; n < dashEndIndex; n++) 702 { 703 dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].X); 704 dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].Y); 705 } 706 707 m_TexturedOverlayLines.push_back(dashOverlay); 708 } 709 710 } 711 } 712 713 //// <DEBUG> ////////////////////////////////////////////// 714 if (m_EnableDebugNodeOverlay) 715 { 716 for (size_t j = 0; j < m_Path.size(); ++j) 717 { 718 SOverlayLine overlayLine; 719 overlayLine.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f); 720 overlayLine.m_Thickness = 1; 721 SimRender::ConstructCircleOnGround(GetSimContext(), m_Path[j].X, m_Path[j].Y, 0.075f, overlayLine, true); 722 m_DebugNodeOverlays.push_back(overlayLine); 723 } 724 } 725 //// </DEBUG> ////////////////////////////////////////////// 726 } 727 728 void CCmpRallyPointRenderer::UpdateOverlayLines() 729 { 730 // We should only do this if the rally point is currently being displayed and set inside the world, otherwise it's a massive 731 // waste of time to calculate all this stuff (this method is called every turn) 732 if (!m_Displayed || !IsSet()) 733 return; 734 735 // see if there have been any changes to the SoD by grabbing the visibility edge points and comparing them to the previous ones 736 std::deque<SVisibilitySegment> newVisibilitySegments; 737 GetVisibilitySegments(newVisibilitySegments); 738 739 // compare the two indices vectors; as soon as an element is different (and provided the full path hasn't changed), then the SoD 740 // has changed and we should recreate the overlay lines 741 if (m_VisibilitySegments != newVisibilitySegments) 742 { 743 // the visibility segments have changed, so we want to reconstruct the overlay lines to match. Note that the path itself doesn't 744 // change, only the overlay lines we construct from them. 745 m_VisibilitySegments = newVisibilitySegments; // save the new visibility segments to compare against next time 746 ConstructOverlayLines(); 747 } 748 749 } 750 751 void CCmpRallyPointRenderer::FixFootprintWaypoints(std::vector<CVector2D>& coords, CmpPtr<ICmpPosition> cmpPosition, CmpPtr<ICmpFootprint> cmpFootprint) 752 { 753 ENSURE(!cmpPosition.null()); 754 ENSURE(!cmpFootprint.null()); 755 756 // ----------------------------------------------------------------------------------------------------- 757 // TODO: nasty fixed/float conversions everywhere 758 759 // grab the shape and dimensions of the footprint 760 entity_pos_t footprintSize0, footprintSize1, footprintHeight; 761 ICmpFootprint::EShape footprintShape; 762 cmpFootprint->GetShape(footprintShape, footprintSize0, footprintSize1, footprintHeight); 763 764 // grab the center of the footprint 765 CFixedVector2D center = cmpPosition->GetPosition2D(); 766 767 // ----------------------------------------------------------------------------------------------------- 768 769 switch (footprintShape) 770 { 771 case ICmpFootprint::SQUARE: 772 { 773 // in this case, footprintSize0 and 1 respectively indicate the (unrotated) size along the X and Z axes 774 775 // the building's footprint could be rotated any which way, so let's get the rotation around the Y axis 776 // and the rotated unit vectors in the X/Z plane of the shape's footprint 777 // (the Footprint itself holds only the outline, the Position holds the orientation) 778 779 fixed s, c; // sine and cosine of the Y axis rotation angle (aka the yaw) 780 fixed a = cmpPosition->GetRotation().Y; 781 sincos_approx(a, s, c); 782 CFixedVector2D u(c, -s); // unit vector along the rotated X axis 783 CFixedVector2D v(s, c); // unit vector along the rotated Z axis 784 CFixedVector2D halfSize(footprintSize0/2, footprintSize1/2); 785 786 // starting from the start position, check if any points are within the footprint of the building 787 // (this is possible if the pathfinder was started from a point located within the footprint) 788 for(int i = (int)(coords.size() - 1); i >= 0; i--) 789 { 790 const CVector2D& wp = coords[i]; 791 if (Geometry::PointIsInSquare(CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center, u, v, halfSize)) 792 { 793 coords.erase(coords.begin() + i); 794 } 795 else 796 { 797 break; // point no longer inside footprint, from this point on neither will any of the following be 798 } 799 } 800 801 // add a point right on the edge of the footprint (nearest to the last waypoint) so that it links up nicely with the rest of the path 802 CFixedVector2D lastWaypoint(fixed::FromFloat(coords.back().X), fixed::FromFloat(coords.back().Y)); 803 CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(lastWaypoint - center, u, v, halfSize); // relative to the shape origin (center) 804 CVector2D footprintEdge((center.X + footprintEdgePoint.X).ToFloat(), (center.Y + footprintEdgePoint.Y).ToFloat()); 805 coords.push_back(footprintEdge); 806 807 } 808 break; 809 case ICmpFootprint::CIRCLE: 810 { 811 // in this case, both footprintSize0 and 1 indicate the circle's radius 812 813 for(int i = (int)(coords.size() - 1); i >= 0; i--) 814 { 815 const CVector2D& wp = coords[i]; 816 fixed pointDistance = (CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center).Length(); 817 if (pointDistance <= footprintSize0) 818 { 819 coords.erase(coords.begin() + i); 820 } 821 else 822 { 823 break; // point no longer inside footprint, from this point on neither will any of the following be 824 } 825 } 826 827 // add a point right on the edge of the footprint so that it links up nicely with the rest of the path 828 CFixedVector2D radiusEdgePoint(fixed::FromFloat(coords.back().X), fixed::FromFloat(coords.back().Y)); 829 radiusEdgePoint.Normalize(footprintSize1); 830 CVector2D footprintEdge((center.X + radiusEdgePoint.X).ToFloat(), (center.Y + radiusEdgePoint.Y).ToFloat()); 831 coords.push_back(footprintEdge); 832 } 833 break; 834 } 835 } 836 837 void CCmpRallyPointRenderer::FixInvisibleWaypoints(std::vector<CVector2D>& coords) 838 { 839 CmpPtr<ICmpRangeManager> cmpRangeMgr(GetSimContext(), SYSTEM_ENTITY); 840 841 player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer(); 842 CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer)); 843 844 //for (std::vector<Waypoint>::iterator it = waypoints.begin(); it != waypoints.end();) 845 for(std::vector<CVector2D>::iterator it = coords.begin(); it != coords.end();) 846 { 847 int i = (fixed::FromFloat(it->X) / (int)CELL_SIZE).ToInt_RoundToNearest(); 848 int j = (fixed::FromFloat(it->Y) / (int)CELL_SIZE).ToInt_RoundToNearest(); 849 850 bool explored = losQuerier.IsExplored(i, j); 851 if (!explored) 852 { 853 it = coords.erase(it); 854 } 855 else 856 { 857 it++; 858 } 859 } 860 } 861 862 void CCmpRallyPointRenderer::ReduceSegmentsByVisibility(std::vector<CVector2D>& coords, unsigned maxSegmentLinks, bool floating) 863 { 864 CmpPtr<ICmpPathfinder> cmpPathFinder(GetSimContext(), SYSTEM_ENTITY); 865 CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); 866 CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); 867 ENSURE(!cmpPathFinder.null() && !cmpTerrain.null() && !cmpWaterManager.null()); 868 869 if (coords.size() < 3) 870 return; 871 872 // The basic idea is this: starting from a base node, keep checking each individual point along the path to see if there's a visible 873 // line between it and the base point. If so, keep going, otherwise, make the last visible point the new base node and start the same 874 // process from there on until the entire line is checked. The output is the array of base nodes. 875 876 std::vector<CVector2D> newCoords; 877 StationaryObstructionFilter obstructionFilter; 878 entity_pos_t lineRadius = fixed::FromFloat(m_LineThickness); 879 ICmpPathfinder::pass_class_t passabilityClass = cmpPathFinder->GetPassabilityClass(m_LinePassabilityClass); 880 881 newCoords.push_back(coords[0]); // save the first base node 882 883 size_t baseNodeIdx = 0; 884 size_t curNodeIdx = 1; 885 886 float baseNodeY; 887 entity_pos_t baseNodeX; 888 entity_pos_t baseNodeZ; 889 890 // set initial base node coords 891 baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X); 892 baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y); 893 baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y); 894 if (floating) 895 baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y)); 896 897 while (curNodeIdx < coords.size()) 898 { 899 ENSURE(curNodeIdx > baseNodeIdx); // this needs to be true at all times, otherwise we're checking visibility between a point and itself 900 901 entity_pos_t curNodeX = fixed::FromFloat(coords[curNodeIdx].X); 902 entity_pos_t curNodeZ = fixed::FromFloat(coords[curNodeIdx].Y); 903 float curNodeY = cmpTerrain->GetExactGroundLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y); 904 if (floating) 905 curNodeY = std::max(curNodeY, cmpWaterManager->GetExactWaterLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y)); 906 907 // find out whether curNode is visible from baseNode (careful; this is in 2D only; terrain height differences are ignored!) 908 bool curNodeVisible = cmpPathFinder->CheckMovement(obstructionFilter, baseNodeX, baseNodeZ, curNodeX, curNodeZ, lineRadius, passabilityClass); 909 910 // since height differences are ignored by CheckMovement, let's call two points visible from one another only if they're at 911 // roughly the same terrain elevation 912 curNodeVisible = curNodeVisible && (fabsf(curNodeY - baseNodeY) < 3.f); // TODO: this could probably use some tuning 913 if (maxSegmentLinks > 0) 914 // max. amount of node-to-node links to be eliminated (unsigned subtraction is valid because curNodeIdx is always > baseNodeIdx) 915 curNodeVisible = curNodeVisible && ((curNodeIdx - baseNodeIdx) <= maxSegmentLinks); 916 917 if (!curNodeVisible) 918 { 919 // current node is not visible from the base node, so the previous one was the last visible point from baseNode and should 920 // hence become the new base node for further iterations. 921 922 // if curNodeIdx is adjacent to the current baseNode (which is possible due to steep height differences, e.g. hills), then 923 // we should take care not to stay stuck at the current base node 924 if (curNodeIdx > baseNodeIdx + 1) 925 { 926 baseNodeIdx = curNodeIdx - 1; 927 } 928 else 929 { 930 // curNodeIdx == baseNodeIdx + 1 931 baseNodeIdx = curNodeIdx; 932 curNodeIdx++; // move the next candidate node one forward so that we don't test a point against itself in the next iteration 933 } 934 935 newCoords.push_back(coords[baseNodeIdx]); // add new base node to output list 936 937 // update base node coordinates 938 baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X); 939 baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y); 940 baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y); 941 if (floating) 942 baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y)); 943 } 944 945 curNodeIdx++; 946 } 947 948 // we always need to add the last point back to the array; if e.g. all the points up to the last one are all visible from the current 949 // base node, then the loop above just ends and no endpoint is ever added to the list. 950 ENSURE(curNodeIdx == coords.size()); 951 newCoords.push_back(coords[coords.size() - 1]); 952 953 coords.swap(newCoords); 954 } 955 956 void CCmpRallyPointRenderer::GetVisibilitySegments(std::deque<SVisibilitySegment>& out) 957 { 958 out.clear(); 959 960 if (m_Path.size() < 2) 961 return; 962 963 CmpPtr<ICmpRangeManager> cmpRangeMgr(GetSimContext(), SYSTEM_ENTITY); 964 965 player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer(); 966 CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer)); 967 968 // go through the path node list, comparing each node's visibility with the previous one. If it changes, end the current segment and start 969 // a new one at the next point. 970 971 bool lastVisible = losQuerier.IsExplored( 972 (fixed::FromFloat(m_Path[0].X) / (int) CELL_SIZE).ToInt_RoundToNearest(), 973 (fixed::FromFloat(m_Path[0].Y) / (int) CELL_SIZE).ToInt_RoundToNearest() 974 ); 975 size_t curSegmentStartIndex = 0; // starting node index of the current segment 976 977 for (size_t k = 1; k < m_Path.size(); ++k) 978 { 979 // grab tile indices for this coord 980 int i = (fixed::FromFloat(m_Path[k].X) / (int)CELL_SIZE).ToInt_RoundToNearest(); 981 int j = (fixed::FromFloat(m_Path[k].Y) / (int)CELL_SIZE).ToInt_RoundToNearest(); 982 983 bool nodeVisible = losQuerier.IsExplored(i, j); 984 if (nodeVisible != lastVisible) 985 { 986 // visibility changed; write out the segment that was just completed and get ready for the new one 987 out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, k - 1)); 988 989 //curSegmentStartIndex = k; // new segment starts here 990 curSegmentStartIndex = k - 1; 991 lastVisible = nodeVisible; 992 } 993 994 } 995 996 // terminate the last segment 997 out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, m_Path.size() - 1)); 998 999 MergeVisibilitySegments(out); 1000 } 1001 1002 void CCmpRallyPointRenderer::MergeVisibilitySegments(std::deque<SVisibilitySegment>& segments) 1003 { 1004 // Scan for single-point segments; if they are inbetween two other segments, delete them and merge the surrounding segments. 1005 // If they're at either end of the path, include them in their bordering segment (but only if those bordering segments aren't 1006 // themselves single-point segments, because then we would want those to get absorbed by its surrounding ones first). 1007 1008 // first scan for absorptions of single-point surrounded segments (i.e. excluding edge segments) 1009 size_t numSegments = segments.size(); 1010 1011 // WARNING: FOR LOOP TRICKERY AHEAD! 1012 for (size_t i = 1; i < numSegments - 1;) 1013 { 1014 SVisibilitySegment& segment = segments[i]; 1015 if (segment.IsSinglePoint()) 1016 { 1017 // since the segments' visibility alternates, the surrounding ones should have the same visibility 1018 ENSURE(segments[i-1].m_Visible == segments[i+1].m_Visible); 1019 1020 segments[i-1].m_EndIndex = segments[i+1].m_EndIndex; // make previous segment span all the way across to the next 1021 segments.erase(segments.begin() + i); // erase this segment ... 1022 segments.erase(segments.begin() + i); // and the next (we removed [i], so [i+1] is now at position [i]) 1023 numSegments -= 2; // we removed 2 segments, so update the loop condition 1024 // in the next iteration, i should still point to the segment right after the one that got expanded, which is now 1025 // at position i; so don't increment i here 1026 } 1027 else 1028 { 1029 ++i; 1030 } 1031 } 1032 1033 ENSURE(numSegments == segments.size()); 1034 1035 // check to see if the first segment needs to be merged with its neighbour 1036 if (segments.size() >= 2 && segments[0].IsSinglePoint()) 1037 { 1038 int firstSegmentStartIndex = segments.front().m_StartIndex; 1039 ENSURE(firstSegmentStartIndex == 0); 1040 ENSURE(!segments[1].IsSinglePoint()); // at this point, the second segment should never be a single-point segment 1041 1042 segments.erase(segments.begin()); 1043 segments.front().m_StartIndex = firstSegmentStartIndex; 1044 1045 } 1046 1047 // check to see if the last segment needs to be merged with its neighbour 1048 if (segments.size() >= 2 && segments[segments.size()-1].IsSinglePoint()) 1049 { 1050 int lastSegmentEndIndex = segments.back().m_EndIndex; 1051 ENSURE(!segments[segments.size()-2].IsSinglePoint()); // at this point, the second-to-last segment should never be a single-point segment 1052 1053 segments.erase(segments.end()); 1054 segments.back().m_EndIndex = lastSegmentEndIndex; 1055 } 1056 1057 // -------------------------------------------------------------------------------------------------------- 1058 // at this point, every segment should have at least 2 points 1059 for (size_t i = 0; i < segments.size(); ++i) 1060 { 1061 ENSURE(!segments[i].IsSinglePoint()); 1062 ENSURE(segments[i].m_EndIndex > segments[i].m_StartIndex); 1063 } 1064 } 1065 1066 void CCmpRallyPointRenderer::RenderSubmit(SceneCollector& collector) 1067 { 1068 // we only get here if the rally point is set and should be displayed 1069 for (size_t i = 0; i < m_TexturedOverlayLines.size(); ++i) 1070 { 1071 if (!m_TexturedOverlayLines[i].m_Coords.empty()) 1072 collector.Submit(&m_TexturedOverlayLines[i]); 1073 } 1074 1075 if (m_EnableDebugNodeOverlay && !m_DebugNodeOverlays.empty()) 1076 { 1077 for (size_t i = 0; i < m_DebugNodeOverlays.size(); i++) 1078 collector.Submit(&m_DebugNodeOverlays[i]); 1079 } 1080 } -
source/simulation2/components/CCmpTerritoryManager.cpp
diff --git a/source/simulation2/components/CCmpTerritoryManager.cpp b/source/simulation2/components/CCmpTerritoryManager.cpp index 2a87eba..28893db 100644
a b public: 103 103 104 104 TerritoryOverlay* m_DebugOverlay; 105 105 106 bool m_EnableLineDebugOverlays; ///< Enable node debugging overlays for boundary lines? 107 std::vector<SOverlayLine> m_DebugBoundaryLineNodes; 108 106 109 virtual void Init(const CParamNode& UNUSED(paramNode)) 107 110 { 108 111 m_Territories = NULL; … … public: 110 113 // m_DebugOverlay = new TerritoryOverlay(*this); 111 114 m_BoundaryLinesDirty = true; 112 115 m_TriggerEvent = true; 113 116 m_EnableLineDebugOverlays = false; 114 117 m_DirtyID = 1; 115 118 116 119 m_AnimTime = 0.0; … … void CCmpTerritoryManager::UpdateBoundaryLines() 742 745 PROFILE("update boundary lines"); 743 746 744 747 m_BoundaryLines.clear(); 748 m_DebugBoundaryLineNodes.clear(); 745 749 746 750 if (!CRenderer::IsInitialised()) 747 751 return; … … void CCmpTerritoryManager::UpdateBoundaryLines() 780 784 m_BoundaryLines.push_back(SBoundaryLine()); 781 785 m_BoundaryLines.back().connected = boundaries[i].connected; 782 786 m_BoundaryLines.back().color = color; 783 784 787 m_BoundaryLines.back().overlay.m_Terrain = terrain; 785 788 m_BoundaryLines.back().overlay.m_TextureBase = textureBase; 786 789 m_BoundaryLines.back().overlay.m_TextureMask = textureMask; 787 790 m_BoundaryLines.back().overlay.m_Color = color; 788 791 m_BoundaryLines.back().overlay.m_Thickness = m_BorderThickness; 792 m_BoundaryLines.back().overlay.m_Closed = true; 789 793 790 SimRender::SmoothPointsAverage(boundaries[i].points, true);794 SimRender::SmoothPointsAverage(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed); 791 795 792 SimRender::InterpolatePointsRNS(boundaries[i].points, true, m_BorderSeparation);796 SimRender::InterpolatePointsRNS(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed, m_BorderSeparation); 793 797 794 798 std::vector<float>& points = m_BoundaryLines.back().overlay.m_Coords; 795 799 for (size_t j = 0; j < boundaries[i].points.size(); ++j) 796 800 { 797 801 points.push_back(boundaries[i].points[j].X); 798 802 points.push_back(boundaries[i].points[j].Y); 803 804 if (m_EnableLineDebugOverlays) 805 { 806 const int numHighlightNodes = 7; // highlight the X last nodes on either end to see where they meet (if closed) 807 SOverlayLine overlayNode; 808 if (j > boundaries[i].points.size() - 1 - numHighlightNodes) 809 overlayNode.m_Color = CColor(1.f, 0.f, 0.f, 1.f); 810 else if (j < numHighlightNodes) 811 overlayNode.m_Color = CColor(0.f, 1.f, 0.f, 1.f); 812 else 813 overlayNode.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f); 814 815 overlayNode.m_Thickness = 1; 816 SimRender::ConstructCircleOnGround(GetSimContext(), boundaries[i].points[j].X, boundaries[i].points[j].Y, 0.1f, overlayNode, true); 817 m_DebugBoundaryLineNodes.push_back(overlayNode); 818 } 799 819 } 820 800 821 } 801 822 } 802 823 … … void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector) 825 846 { 826 847 for (size_t i = 0; i < m_BoundaryLines.size(); ++i) 827 848 collector.Submit(&m_BoundaryLines[i].overlay); 849 850 for (size_t i = 0; i < m_DebugBoundaryLineNodes.size(); ++i) 851 collector.Submit(&m_DebugBoundaryLineNodes[i]); 852 828 853 } 829 854 830 855 player_id_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z) -
source/simulation2/components/CCmpVisualActor.cpp
diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp index 30b8b52..b677a33 100644
a b public: 356 356 } 357 357 } 358 358 359 virtual void SetUnitEntitySelection(const CStr& selection) 360 { 361 if (m_Unit) 362 { 363 m_Unit->SetEntitySelection(selection); 364 } 365 } 366 359 367 virtual void SelectMovementAnimation(fixed runThreshold) 360 368 { 361 369 m_AnimRunThreshold = runThreshold; -
source/simulation2/components/ICmpPlayer.cpp
diff --git a/source/simulation2/components/ICmpPlayer.cpp b/source/simulation2/components/ICmpPlayer.cpp index ad2a56e..49f16a4 100644
a b public: 53 53 return m_Script.Call<CColor>("GetColour"); 54 54 } 55 55 56 virtual std::wstring GetCiv() 57 { 58 return m_Script.Call<std::wstring>("GetCiv"); 59 } 60 56 61 virtual CFixedVector3D GetStartingCameraPos() 57 62 { 58 63 return m_Script.Call<CFixedVector3D>("GetStartingCameraPos"); -
source/simulation2/components/ICmpPlayer.h
diff --git a/source/simulation2/components/ICmpPlayer.h b/source/simulation2/components/ICmpPlayer.h index 531689b..4418639 100644
a b public: 36 36 virtual void SetColour(u8 r, u8 g, u8 b) = 0; 37 37 38 38 virtual CColor GetColour() = 0; 39 virtual std::wstring GetCiv() = 0; 39 40 virtual CFixedVector3D GetStartingCameraPos() = 0; 40 41 virtual CFixedVector3D GetStartingCameraRot() = 0; 41 42 -
new file source/simulation2/components/ICmpRallyPointRenderer.cpp
diff --git a/source/simulation2/components/ICmpRallyPointRenderer.cpp b/source/simulation2/components/ICmpRallyPointRenderer.cpp new file mode 100644 index 0000000..8a3a64f
- + 1 /* Copyright (C) 2011 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "precompiled.h" 19 20 #include "ICmpRallyPointRenderer.h" 21 #include "simulation2/system/InterfaceScripted.h" 22 23 class CFixedVector2D; 24 25 BEGIN_INTERFACE_WRAPPER(RallyPointRenderer) 26 DEFINE_INTERFACE_METHOD_1("SetDisplayed", void, ICmpRallyPointRenderer, SetDisplayed, bool) 27 DEFINE_INTERFACE_METHOD_1("SetPosition", void, ICmpRallyPointRenderer, SetPosition, CFixedVector2D) 28 END_INTERFACE_WRAPPER(RallyPointRenderer) -
new file source/simulation2/components/ICmpRallyPointRenderer.h
diff --git a/source/simulation2/components/ICmpRallyPointRenderer.h b/source/simulation2/components/ICmpRallyPointRenderer.h new file mode 100644 index 0000000..71f315a
- + 1 /* Copyright (C) 2011 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #ifndef INCLUDED_ICMPRALLYPOINT 19 #define INCLUDED_ICMPRALLYPOINT 20 21 #include "maths/FixedVector2D.h" 22 #include "simulation2/helpers/Position.h" 23 #include "simulation2/system/Interface.h" 24 25 /** 26 * Rally Point. 27 * Holds the position of a unit's rally point, and renders it to screen. 28 */ 29 class ICmpRallyPointRenderer : public IComponent 30 { 31 public: 32 33 /// Sets whether the rally point marker and line should be displayed. 34 virtual void SetDisplayed(bool displayed) = 0; 35 36 /// Sets the position at which the rally point marker should be displayed. 37 virtual void SetPosition(CFixedVector2D position) = 0; 38 39 DECLARE_INTERFACE_TYPE(RallyPointRenderer) 40 }; 41 42 #endif // INCLUDED_ICMPRALLYPOINT -
source/simulation2/components/ICmpVisual.h
diff --git a/source/simulation2/components/ICmpVisual.h b/source/simulation2/components/ICmpVisual.h index 86a5183..e02b4b5 100644
a b 20 20 21 21 #include "simulation2/system/Interface.h" 22 22 23 #include "ps/CStr.h" 23 24 #include "maths/BoundingBoxOriented.h" 24 25 #include "maths/BoundingBoxAligned.h" 25 26 #include "maths/Fixed.h" … … public: 82 83 virtual void SelectAnimation(std::string name, bool once, fixed speed, std::wstring soundgroup) = 0; 83 84 84 85 /** 86 * Sets the specified entity selection on the underlying unit. 87 */ 88 virtual void SetUnitEntitySelection(const CStr& selection) = 0; 89 90 /** 85 91 * Start playing the walk/run animations, scaled to the unit's movement speed. 86 92 * @param runThreshold movement speed at which to switch to the run animation 87 93 */ -
source/simulation2/helpers/Geometry.h
diff --git a/source/simulation2/helpers/Geometry.h b/source/simulation2/helpers/Geometry.h index 0fc046c..4e5ede1 100644
a b 1 /* Copyright (C) 201 0Wildfire Games.1 /* Copyright (C) 2011 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … class CFixedVector2D; 30 30 namespace Geometry 31 31 { 32 32 33 /** 34 * 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, 35 * and half dimensions specified by @p halfSizes. Currently assumes the @p u and @p v vectors are perpendicular. Can also 36 * be used for rectangles. 37 * 38 * @param point point vector of the point that is to be tested relative to the origin (center) of the shape. 39 * @param u rotated X axis unit vector relative to the absolute XZ plane. Indicates the orientation of the rectangle. If not rotated, 40 * this value is the absolute X axis unit vector (1,0). If rotated by angle theta, this should be (cos theta, -sin theta), as 41 * the absolute Z axis points down in the unit circle. 42 * @param v rotated Z axis unit vector relative to the absolute XZ plane. Indicates the orientation of the rectangle. If not rotated, 43 * this value is the absolute Z axis unit vector (0,1). If rotated by angle theta, this should be (sin theta, cos theta), as 44 * the absolute Z axis points down in the unit circle. 45 * @param halfSizes Holds half the dimensions of the shape along the u and v vectors, respectively. 46 */ 33 47 bool PointIsInSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize); 34 48 35 49 CFixedVector2D GetHalfBoundingBox(CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize); 36 50 37 51 fixed DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize); 38 52 53 /** 54 * 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 55 * dimensions @p halfSize, relative to the center of the square. Currently assumes the @p u and @p v vectors are perpendicular. 56 * Can also be used for rectangles. 57 * 58 * @param point point vector of the point we want to get the nearest edge point for, relative to the origin (center) of the shape. 59 * @param u rotated X axis unit vector, relative to the absolute XZ plane. Indicates the orientation of the shape. If not rotated, 60 * this value is the absolute X axis unit vector (1,0). If rotated by angle theta, this should be (cos theta, -sin theta). 61 * @param v rotated Z axis unit vector, relative to the absolute XZ plane. Indicates the orientation of the shape. If not rotated, 62 * this value is the absolute Z axis unit vector (0,1). If rotated by angle theta, this should be (sin theta, cos theta). 63 * @param halfSizes Holds half the dimensions of the shape along the u and v vectors, respectively. 64 */ 39 65 CFixedVector2D NearestPointOnSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize); 40 66 41 67 bool TestRaySquare(CFixedVector2D a, CFixedVector2D b, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize); -
source/simulation2/helpers/Render.cpp
diff --git a/source/simulation2/helpers/Render.cpp b/source/simulation2/helpers/Render.cpp index 7c83778..dd8a906 100644
a b 1 /* Copyright (C) 201 0Wildfire Games.1 /* Copyright (C) 2011 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … static CVector2D EvaluateSpline(float t, CVector2D a0, CVector2D a1, CVector2D a 319 319 return p + CVector2D(dp.Y*-offset, dp.X*offset); 320 320 } 321 321 322 void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset )322 void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset, int segmentSamples) 323 323 { 324 324 PROFILE("InterpolatePointsRNS"); 325 ENSURE(segmentSamples > 0); 325 326 326 327 std::vector<CVector2D> newPoints; 327 328 … … void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed 333 334 // curve with fewer points 334 335 335 336 size_t n = points.size(); 336 if (n < 1)337 return; // can't do anything unless we have two points338 337 339 size_t imax = closed ? n : n-1; // TODO: we probably need to do a bit more to handle non-closed paths 338 if (closed) 339 { 340 if (n < 1) 341 return; // we need at least a single point to not crash 342 } 343 else 344 { 345 if (n < 2) 346 return; // in non-closed mode, we need at least n=2 to not crash 347 } 340 348 341 newPoints.reserve(imax*4); 349 size_t imax = closed ? n : n-1; 350 newPoints.reserve(imax*segmentSamples); 351 352 // these are primarily used inside the loop, but for open paths we need them outside the loop once to compute the last point 353 CVector2D a0; 354 CVector2D a1; 355 CVector2D a2; 356 CVector2D a3; 342 357 343 358 for (size_t i = 0; i < imax; ++i) 344 359 { 345 // Get the relevant points for this spline segment 346 CVector2D p0 = points[(i-1+n)%n]; 360 361 // Get the relevant points for this spline segment; each step interpolates the segment between p1 and p2; p0 and p3 are the points 362 // before p1 and after p2, respectively; they're needed to compute tangents and whatnot. 363 CVector2D p0; // normally points[(i-1+n)%n], but it's a bit more complicated due to open/closed paths -- see below 347 364 CVector2D p1 = points[i]; 348 365 CVector2D p2 = points[(i+1)%n]; 349 CVector2D p3 = points[(i+2)%n]; 366 CVector2D p3; // normally points[(i+2)%n], but it's a bit more complicated due to open/closed paths -- see below 367 368 if (!closed && (i == 0)) 369 // 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 370 // that extends p1 -> p0 (i.e. the first segment's direction) 371 p0 = points[0] + (points[0] - points[1]); 372 else 373 // standard wrap-around case 374 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) 375 376 377 if (!closed && (i == n-2)) 378 // 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) 379 // (note that p2's index should not be out of bounds, because in non-closed mode imax is reduced by 1) 380 p3 = points[n-1] + (points[n-1] - points[n-2]); 381 else 382 // standard wrap-around case 383 p3 = points[(i+2)%n]; 384 350 385 351 386 // Do the RNS computation (based on GPG4 "Nonuniform Splines") 352 387 float l1 = (p2 - p1).Length(); // length of spline segment (i)..(i+1) … … void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed 357 392 CVector2D v2 = (s1 + s2).Normalized() * l1; // spline velocity at i+1 358 393 359 394 // Compute standard cubic spline parameters 360 CVector2D a0 = p1*2 + p2*-2 + v1 + v2; 361 CVector2D a1 = p1*-3 + p2*3 + v1*-2 + v2*-1; 362 CVector2D a2 = v1; 363 CVector2D a3 = p1; 364 365 // Interpolate at various points 366 newPoints.push_back(EvaluateSpline(0.f, a0, a1, a2, a3, offset)); 367 newPoints.push_back(EvaluateSpline(1.f/4.f, a0, a1, a2, a3, offset)); 368 newPoints.push_back(EvaluateSpline(2.f/4.f, a0, a1, a2, a3, offset)); 369 newPoints.push_back(EvaluateSpline(3.f/4.f, a0, a1, a2, a3, offset)); 395 a0 = p1*2 + p2*-2 + v1 + v2; 396 a1 = p1*-3 + p2*3 + v1*-2 + v2*-1; 397 a2 = v1; 398 a3 = p1; 399 400 // Interpolate at regular points across the interval 401 for (int sample = 0; sample < segmentSamples; sample++) 402 newPoints.push_back(EvaluateSpline(sample/((float) segmentSamples), a0, a1, a2, a3, offset)); 403 370 404 } 371 405 406 if (!closed) 407 // if the path is open, we should take care to include the last control point 408 // NOTE: we can't just do push_back(points[n-1]) here because that ignores the offset 409 newPoints.push_back(EvaluateSpline(1.f, a0, a1, a2, a3, offset)); 410 372 411 points.swap(newPoints); 373 412 } 413 414 void SimRender::ConstructDashedLine(const std::vector<CVector2D>& keyPoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength) 415 { 416 // sanity checks 417 if (dashLength <= 0) 418 return; 419 420 if (blankLength <= 0) 421 return; 422 423 if (keyPoints.size() < 2) 424 return; 425 426 dashedLineOut.m_Points.clear(); 427 dashedLineOut.m_StartIndices.clear(); 428 429 // walk the line, counting the total length so far at each node point. When the length exceeds dashLength, cut the last segment at the 430 // required length and continue for blankLength along the line to start a new dash segment. 431 432 // TODO: we should probably extend this function to also allow for closed lines. I was thinking of slightly scaling the dash/blank length 433 // 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 434 // (O(n) and lots of square roots). 435 436 bool buildingDash = true; // true if we're building a dash, false if a blank 437 float curDashLength = 0; // builds up the current dash/blank's length as we walk through the line nodes 438 CVector2D dashLastPoint = keyPoints[0]; // last point of the current dash/blank being built. 439 440 // register the first starting node of the first dash 441 dashedLineOut.m_Points.push_back(keyPoints[0]); 442 dashedLineOut.m_StartIndices.push_back(0); 443 444 // 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 445 // properly take a direction vector along the path. 446 size_t i = 0; 447 448 while(i < keyPoints.size() - 1) 449 { 450 // get length of this segment 451 CVector2D segmentVector = keyPoints[i + 1] - dashLastPoint; // vector from our current point along the path to nextNode 452 float segmentLength = segmentVector.Length(); 453 454 float targetLength = (buildingDash ? dashLength : blankLength); 455 if (curDashLength + segmentLength > targetLength) 456 { 457 // 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 458 // line that is of just the required length to complete the dash, then make it the base point for the next dash/blank. 459 float cutLength = targetLength - curDashLength; 460 CVector2D cutPoint = dashLastPoint + (segmentVector.Normalized() * cutLength); 461 462 // start a new dash or blank in the next iteration 463 curDashLength = 0; 464 buildingDash = !buildingDash; // flip from dash to blank and vice-versa 465 dashLastPoint = cutPoint; 466 467 // don't increment i, we haven't fully traversed this segment yet so we still need to use the same point to take the 468 // direction vector with in the next iteration 469 470 // 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 471 // in the points array. 472 dashedLineOut.m_Points.push_back(cutPoint); 473 474 if (buildingDash) 475 { 476 // 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 477 // index as a start index of a dash. 478 dashedLineOut.m_StartIndices.push_back(dashedLineOut.m_Points.size() - 1); 479 } 480 481 } 482 else 483 { 484 // the segment from lastDashPoint to keyPoints[i+1] doesn't suffice to complete the dash, so we need to add keyPoints[i+1] 485 // to this dash's points and continue from there 486 487 if (buildingDash) 488 // still building the dash, add it to the output (we don't need to store the blanks) 489 dashedLineOut.m_Points.push_back(keyPoints[i+1]); 490 491 curDashLength += segmentLength; 492 dashLastPoint = keyPoints[i+1]; 493 i++; 494 495 } 496 497 } 498 499 } -
source/simulation2/helpers/Render.h
diff --git a/source/simulation2/helpers/Render.h b/source/simulation2/helpers/Render.h index bf66828..5b2f251 100644
a b 23 23 * Helper functions related to rendering 24 24 */ 25 25 26 #include "maths/Vector2D.h" 27 26 28 class CSimContext; 27 class CVector2D;28 29 class CVector3D; 29 30 class CBoundingBoxAligned; 30 31 class CBoundingBoxOriented; 31 32 struct SOverlayLine; 32 33 34 35 36 struct SDashedLine 37 { 38 std::vector<CVector2D> m_Points; ///< Packed array of consecutive dashes' points. Use m_StartIndices to navigate it. 39 40 /** 41 * Start indices in m_Points of each dash. Dash n starts at point m_StartIndices[n] and ends at the point with index 42 * m_StartIndices[n+1] - 1, or at the end of the m_Points vector. Use the GetEndIndex(n) convenience method to abstract away the 43 * difference and get the (exclusive) end index of dash n. 44 */ 45 std::vector<size_t> m_StartIndices; 46 47 /// Returns the (exclusive) end point index (i.e. index within m_Points) of dash n. 48 size_t GetEndIndex(size_t i) 49 { 50 // for the last dash, there is no next starting index, so we need to use the end index of the m_Points array instead 51 return (i < m_StartIndices.size() - 1 ? m_StartIndices[i+1] : m_Points.size()); 52 } 53 }; 54 33 55 namespace SimRender 34 56 { 35 57 … … void SmoothPointsAverage(std::vector<CVector2D>& points, bool closed); 88 110 * the direction of the curve. 89 111 * If @p closed then the points are treated as a closed path (the last is connected 90 112 * to the first). 113 * @param segmentSamples Amount of intermediate points to sample between every two control points. 114 */ 115 void InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset, int segmentSamples = 4); 116 117 /** 118 * Creates a dashed line from the line specified by @points so that each dash is of length 119 * @p dashLength, and each blank inbetween is of length @p blankLength. The dashed line returned as a list of smaller lines 120 * in @p dashedLineOut. 121 * 122 * @param dashLength Length of a single dash. Must be strictly positive. 123 * @param blankLength Length of a single blank between dashes. Must be strictly positive. 91 124 */ 92 void InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset);125 void ConstructDashedLine(const std::vector<CVector2D>& linePoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength); 93 126 94 127 } // namespace 95 128