Ticket #524: rallypoints_wip_11sep11.patch
File rallypoints_wip_11sep11.patch, 131.6 KB (added by , 13 years ago) |
---|
-
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..1ee5bdc 100644
a b 11 11 <variant name="waypoint_hellenes"> 12 12 <texture>props/banner_greek.png</texture> 13 13 </variant> 14 <variant name="waypoint_persians"> 15 <texture>props/banner_persian.png</texture> 16 </variant> 17 <variant name="waypoint_celts"> 18 <texture>props/banner_celt.png</texture> 19 </variant> 20 <variant name="waypoint_carthaginians"> 21 <texture>props/banner_carthage.png</texture> 22 </variant> 23 <variant name="waypoint_iberians"> 24 <texture>props/banner_iberians.png</texture> 25 </variant> 26 <variant name="waypoint_romans"> 27 <texture>props/banner_romans.png</texture> 28 </variant> 14 29 </group> 15 30 </actor> -
new file inaries/data/mods/public/art/textures/misc/rallypoint_line.png
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 0000000000000000000000000000000000000000..55c1100618b0fa701fe2ee22c16430987a80cc89 GIT binary patch literal 2849 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
-
new file 0
literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b1824fc318c54ca2d9b31a1e4f9acf1d3d5696db GIT binary patch literal 2826 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
-
new file 0
literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..31a341a52d386d3176e14fdd96ead1ac209f2ad3 GIT binary patch literal 3816 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
-
binaries/data/mods/public/gui/session/input.js
literal 0 HcmV?d00001 diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index b8c9395..b34eb18 100644
a b function performCommand(entity, commandName) 1108 1108 case "unload-all": 1109 1109 unloadAll(entity); 1110 1110 break; 1111 case "focus-rally": 1112 // make the camera move to the building's rally point 1113 1114 // grab the rally point position (if any) 1115 var entState = Engine.GuiInterfaceCall("GetEntityState", entity); 1116 if (entState.rallyPoint) 1117 { 1118 // the rally point isn't necessarily set 1119 if (entState.rallyPoint.position) 1120 { 1121 Engine.CameraMoveTo(entState.rallyPoint.position.x, entState.rallyPoint.position.y); 1122 } 1123 else 1124 { 1125 // no rally point set, jump to the building itself instead (since units will now spawn right next to the 1126 // building) 1127 if (entState.position) 1128 Engine.CameraMoveTo(entState.position.x, entState.position.z); 1129 } 1130 } 1131 1132 break; 1111 1133 default: 1112 1134 break; 1113 1135 } -
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 eaaf6d6..9817b00 100644
a b function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 236 236 break; 237 237 238 238 case COMMAND: 239 if (item == "unload-all") 239 // here, "item" is an object with properties .name (command name), .tooltip and .icon (relative to session/icons/single) 240 if (item.name == "unload-all") 240 241 { 241 242 var count = unitEntState.garrisonHolder.entities.length; 242 243 getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 0 ? count : ""); … … function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 246 247 getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = ""; 247 248 } 248 249 249 tooltip = toTitleCase(item);250 tooltip = (item.tooltip ? item.tooltip : toTitleCase(item.name)); // use item.tooltip if available, otherwise default to title-cased command name 250 251 break; 251 252 252 253 default: … … function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback) 300 301 { 301 302 //icon.cell_id = i; 302 303 //icon.cell_id = getCommandCellId(item); 303 icon.sprite = "stretched:session/icons/single/" + getCommandImage(item);304 icon.sprite = "stretched:session/icons/single/" + item.icon; 304 305 305 306 } 306 307 else if (template.icon) … … function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s 384 385 var commands = getEntityCommandsList(entState); 385 386 if (commands.length) 386 387 setupUnitPanel("Command", usedPanels, entState, commands, 387 function (item) { performCommand(entState.id, item ); } );388 function (item) { performCommand(entState.id, item.name); } ); 388 389 389 390 if (entState.garrisonHolder) 390 391 { -
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 313e141..3c67a1a 100644
a b function getFormationCellId(formationName) 188 188 } 189 189 } 190 190 191 function getCommandImage(commandName)192 {193 switch (commandName)194 {195 case "delete":196 return "kill_small.png";197 case "unload-all":198 return "garrison-out.png";199 case "garrison":200 return "garrison.png";201 case "repair":202 return "repair.png";203 default:204 return "";205 }206 }207 208 191 function getEntityFormationsList(entState) 209 192 { 210 193 var civ = g_Players[entState.player].civ; … … function getEntityCommandsList(entState) 233 216 { 234 217 var commands = []; 235 218 if (entState.garrisonHolder) 236 commands.push("unload-all"); 219 { 220 commands.push({ 221 "name": "unload-all", 222 "tooltip": "Unload All", 223 "icon": "garrison-out.png" 224 }); 225 } 226 227 commands.push({ 228 "name": "delete", 229 "tooltip": "Delete", 230 "icon": "kill_small.png" 231 }); 232 237 233 if (isUnit(entState)) 238 commands.push("garrison"); 234 { 235 commands.push({ 236 "name": "garrison", 237 "tooltip": "Garrison", 238 "icon": "garrison.png" 239 }); 240 } 241 239 242 if (entState.buildEntities) 240 commands.push("repair"); 241 commands.push("delete"); 243 { 244 commands.push({ 245 "name": "repair", 246 "tooltip": "Repair", 247 "icon": "repair.png" 248 }); 249 } 250 251 if(entState.rallyPoint) 252 { 253 commands.push({ 254 "name": "focus-rally", 255 "tooltip": "Focus on Rally Point", 256 "icon": "focus-rally.png" 257 }); 258 } 259 242 260 return commands; 243 261 } 244 262 -
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..e9c123d 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/GarrisonHolder.js
diff --git a/binaries/data/mods/public/simulation/components/GarrisonHolder.js b/binaries/data/mods/public/simulation/components/GarrisonHolder.js index b128529..bba6725 100644
a b GarrisonHolder.prototype.OrderWalkToRallyPoint = function(entities) 183 183 { 184 184 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 185 185 var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint); 186 if (cmpRallyPoint )186 if (cmpRallyPoint && cmpRallyPoint.IsSet()) 187 187 { 188 188 var rallyPos = cmpRallyPoint.GetPosition(); 189 189 if (rallyPos) … … GarrisonHolder.prototype.OrderWalkToRallyPoint = function(entities) 192 192 "type": "walk", 193 193 "entities": entities, 194 194 "x": rallyPos.x, 195 "z": rallyPos. z,195 "z": rallyPos.y, 196 196 "queued": false 197 197 }); 198 198 } -
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 abd7e1e..dc32d2b 100644
a b GuiInterface.prototype.GetEntityState = function(player, ent) 222 222 if (cmpRallyPoint) 223 223 { 224 224 ret.rallyPoint = { }; 225 if (cmpRallyPoint.IsSet()) // can't return a rally point position if none is set ... 226 ret.rallyPoint.position = cmpRallyPoint.GetPosition(); 225 227 } 226 228 227 229 var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder); … … GuiInterface.prototype.SetStatusBars = function(player, cmd) 379 381 */ 380 382 GuiInterface.prototype.DisplayRallyPoint = function(player, cmd) 381 383 { 382 // If there are rally points already displayed, destroythem383 for each (var ent in this. rallyPoints)384 // If there are some rally points already displayed, first hide them 385 for each (var ent in this.entsRallyPointsDisplayed) 384 386 { 385 // Hide it first (the destruction won't be instantaneous)386 var cmpPosition = Engine.QueryInterface(ent, IID_Position);387 cmpPosition.MoveOutOfWorld();387 var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); 388 if (!cmpRallyPoint) 389 continue; 388 390 389 Engine.DestroyEntity(ent);391 cmpRallyPoint.SetDisplayed(false); 390 392 } 391 393 392 this.rallyPoints = []; 393 394 var positions = []; 395 // DisplayRallyPoints is called passing a list of entities for which 396 // rally points must be displayed 394 // Show the rally points for the passed entities 397 395 for each (var ent in cmd.entities) 398 396 { 399 397 var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); … … GuiInterface.prototype.DisplayRallyPoint = function(player, cmd) 405 403 if (!cmpOwnership || cmpOwnership.GetOwner() != player) 406 404 continue; 407 405 408 // If the command was passed an explicit position, use that and 409 // override the real rally point position; otherwise use the real position 410 var pos; 406 // If the command was passed an explicit position, first set it 411 407 if (cmd.x && cmd.z) 412 pos = {"x": cmd.x, "z": cmd.z}; 413 else 414 pos = cmpRallyPoint.GetPosition(); 408 cmpRallyPoint.SetPosition({x: cmd.x, y:cmd.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z 415 409 416 if (pos) 417 { 418 // TODO: it'd probably be nice if we could draw some kind of line 419 // between the building and pos, to make the marker easy to find even 420 // if it's a long way from the building 410 cmpRallyPoint.SetDisplayed(true); 421 411 422 positions.push(pos);423 }424 412 } 425 413 426 // Add rally point entity for each building 427 for each (var pos in positions) 428 { 429 var rallyPoint = Engine.AddLocalEntity("actor|props/special/common/waypoint_flag.xml"); 430 var cmpPosition = Engine.QueryInterface(rallyPoint, IID_Position); 431 cmpPosition.JumpTo(pos.x, pos.z); 432 this.rallyPoints.push(rallyPoint); 433 } 414 // Remember which entities have their rally points displayed so we can hide them again 415 this.entsRallyPointsDisplayed = cmd.entities; 416 434 417 }; 435 418 436 419 /** -
deleted file 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 deleted file mode 100644 index 3c3816a..0000000
+ - 1 function RallyPoint() {}2 3 RallyPoint.prototype.Schema =4 "<a:component/><empty/>";5 6 RallyPoint.prototype.Init = function()7 {8 this.pos = undefined;9 };10 11 RallyPoint.prototype.SetPosition = function(x, z)12 {13 this.pos = {14 "x": x,15 "z": z16 }17 };18 19 RallyPoint.prototype.Unset = function()20 {21 this.pos = undefined;22 };23 24 RallyPoint.prototype.GetPosition = function()25 {26 return this.pos;27 };28 29 Engine.RegisterComponentType(IID_RallyPoint, "RallyPoint", RallyPoint); -
binaries/data/mods/public/simulation/components/TrainingQueue.js
diff --git a/binaries/data/mods/public/simulation/components/TrainingQueue.js b/binaries/data/mods/public/simulation/components/TrainingQueue.js index 87f74a9..3bbad11 100644
a b TrainingQueue.prototype.SpawnUnits = function(templateName, count, metadata) 281 281 if (spawnedEnts.length > 0) 282 282 { 283 283 // If a rally point is set, walk towards it (in formation) 284 if (cmpRallyPoint )284 if (cmpRallyPoint && cmpRallyPoint.IsSet()) 285 285 { 286 286 var rallyPos = cmpRallyPoint.GetPosition(); 287 287 if (rallyPos) … … TrainingQueue.prototype.SpawnUnits = function(templateName, count, metadata) 290 290 "type": "walk", 291 291 "entities": spawnedEnts, 292 292 "x": rallyPos.x, 293 "z": rallyPos. z,293 "z": rallyPos.y, 294 294 "queued": false 295 295 }); 296 296 } -
deleted file binaries/data/mods/public/simulation/components/interfaces/RallyPoint.js
diff --git a/binaries/data/mods/public/simulation/components/interfaces/RallyPoint.js b/binaries/data/mods/public/simulation/components/interfaces/RallyPoint.js deleted file mode 100644 index ac0f079..0000000
+ - 1 Engine.RegisterInterface("RallyPoint"); -
binaries/data/mods/public/simulation/helpers/Commands.js
diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js index 57dc2b9..a344461 100644
a b function ProcessCommand(player, cmd) 229 229 { 230 230 var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); 231 231 if (cmpRallyPoint) 232 cmpRallyPoint.SetPosition( cmd.x, cmd.z);232 cmpRallyPoint.SetPosition({x:cmd.x, y:cmd.z}); // SetPosition takes a CFixedVector2D which has X/Y components instead of X/Z 233 233 } 234 234 break; 235 235 -
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/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..8de5ade 100644
a b 53 53 <DisableBlockPathfinding>false</DisableBlockPathfinding> 54 54 </Obstruction> 55 55 <OverlayRenderer/> 56 <RallyPoint/> 56 <RallyPoint> 57 <MarkerTemplate>special/rallypoint</MarkerTemplate> 58 <LineThickness>0.2</LineThickness> 59 <LineColour r="35" g="86" b="188"></LineColour> 60 <LineDashColour r="158" g="11" b="15"></LineDashColour> 61 <LineStartCap>square</LineStartCap> 62 <LineEndCap>round</LineEndCap> 63 </RallyPoint> 57 64 <Sound> 58 65 <SoundGroups> 59 66 <select>interface/select/building/sel_universal.xml</select> -
new file source/graphics/Overlay.cpp
diff --git a/source/graphics/Overlay.cpp b/source/graphics/Overlay.cpp new file mode 100644 index 0000000..7db8ab6
- + 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 "Overlay.h" 21 22 bool SOverlayTexturedLine::StrToLineCap(const std::wstring& str, SOverlayTexturedLine::LineCap& out) 23 { 24 if (str == L"flat") 25 { 26 out = LINECAP_FLAT; 27 return true; 28 } 29 else if (str == L"round") 30 { 31 out = LINECAP_ROUND; 32 return true; 33 } 34 else if (str == L"sharp") 35 { 36 out = LINECAP_SHARP; 37 return true; 38 } 39 else if (str == L"square") 40 { 41 out = LINECAP_SQUARE; 42 return true; 43 } 44 else 45 return false; 46 } -
source/graphics/Overlay.h
diff --git a/source/graphics/Overlay.h b/source/graphics/Overlay.h index e07b13b..871f7a6 100644
a b struct SOverlayLine 44 44 */ 45 45 struct SOverlayTexturedLine 46 46 { 47 SOverlayTexturedLine() : m_Terrain(NULL), m_Thickness(1.0f) { } 47 48 enum LineCap 49 { 50 LINECAP_FLAT, ///< no line ending; abrupt stop of the line (aka. butt ending) 51 52 /** 53 * Semi-circular line ending. The texture is mapped by curving the left vertical edge around the semi-circle's rim. That is, 54 * 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 55 * from 0 to 1 as the rim is traversed. 56 */ 57 LINECAP_ROUND, 58 LINECAP_SHARP, ///< sharp point ending 59 LINECAP_SQUARE, ///< square end that extends half the line width beyond the line end 60 }; 61 62 SOverlayTexturedLine() 63 : m_Terrain(NULL), m_Thickness(1.0f), m_Closed(false), m_AlwaysVisible(false), m_StartCap(LINECAP_FLAT), m_EndCap(LINECAP_FLAT) 64 {} 48 65 49 66 CTerrain* m_Terrain; 50 67 CTexturePtr m_TextureBase; 51 68 CTexturePtr m_TextureMask; 52 CColor m_Color; 53 std::vector<float> m_Coords; // (x, z) vertex coordinate pairs; y is computed automatically; shape is automatically closed 54 float m_Thickness; // world-space units 69 CColor m_Color; // color to apply to the line texture 70 std::vector<float> m_Coords; // (x, z) vertex coordinate pairs; y is computed automatically 71 float m_Thickness; // half-width of the line, in world-space units 72 73 bool m_Closed; // should this line be treated as a closed loop? (if set, the end cap settings are ignored) 74 bool m_AlwaysVisible; // should this line be rendered even under the SoD? 75 LineCap m_StartCap; // LineEndCap to be used at the start of the line 76 LineCap m_EndCap; // LineEndCap to be used at the end of the line 55 77 56 78 shared_ptr<CRenderData> m_RenderData; // cached renderer data (shared_ptr so that copies/deletes are automatic) 79 80 /** 81 * Interprets @p str as a LineCap value, and writes the matching value to @p out, if any. Returns true if a value was written to @p out, 82 * false otherwise. 83 */ 84 static bool StrToLineCap(const std::wstring& str, LineCap& out); 85 57 86 }; 58 87 59 88 /** -
source/graphics/ShaderManager.cpp
diff --git a/source/graphics/ShaderManager.cpp b/source/graphics/ShaderManager.cpp index ad3080c..942cd91 100644
a b bool CShaderManager::NewProgram(const char* name, const std::map<CStr, CStr>& ba 76 76 { 77 77 if (strncmp(name, "fixed:", 6) == 0) 78 78 { 79 program = CShaderProgramPtr(CShaderProgram::ConstructFFP(name+6 ));79 program = CShaderProgramPtr(CShaderProgram::ConstructFFP(name+6, baseDefines)); 80 80 if (!program) 81 81 return false; 82 82 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..6a5cb66 100644
a b class CShaderProgramFFP_OverlayLine : public CShaderProgramFFP 100 100 ID_objectColor 101 101 }; 102 102 103 std::map<CStr, CStr> m_Defines; 104 105 bool IsIgnoreLos() 106 { 107 std::map<CStr, CStr>::const_iterator define = m_Defines.find(CStr("IGNORE_LOS")); 108 return (define != m_Defines.end() && define->second == CStr("1")); 109 } 110 103 111 public: 104 CShaderProgramFFP_OverlayLine() : 105 CShaderProgramFFP(STREAM_POS | STREAM_UV0 | STREAM_UV1) 112 CShaderProgramFFP_OverlayLine(const std::map<CStr, CStr>& defines) : 113 CShaderProgramFFP(STREAM_POS | STREAM_UV0 | STREAM_UV1), 114 m_Defines(defines) 106 115 { 107 116 m_UniformIndexes["losTransform"] = ID_losTransform; 108 117 m_UniformIndexes["objectColor"] = ID_objectColor; … … 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 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 191 215 glEnable(GL_TEXTURE_GEN_S); 192 216 glEnable(GL_TEXTURE_GEN_T); 193 217 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); … … public: 199 223 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); 200 224 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE); 201 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: 210 237 pglActiveTextureARB(GL_TEXTURE2); 211 238 glDisable(GL_TEXTURE_2D); 212 239 240 // technically these aren't enabled if ignore_los is true, but the previous behaviour before ignore_los was added 241 // was to always disable it, so not doing the if-check for ignore_los here shouldn't break anything 213 242 glDisable(GL_TEXTURE_GEN_S); 214 243 glDisable(GL_TEXTURE_GEN_T); 215 244 … … public: 221 250 } 222 251 }; 223 252 224 /*static*/ CShaderProgram* CShaderProgram::ConstructFFP(const std::string& id )253 /*static*/ CShaderProgram* CShaderProgram::ConstructFFP(const std::string& id, const std::map<CStr, CStr>& defines) 225 254 { 226 255 if (id == "overlayline") 227 return new CShaderProgramFFP_OverlayLine( );256 return new CShaderProgramFFP_OverlayLine(defines); 228 257 229 258 LOGERROR(L"CShaderProgram::ConstructFFP: Invalid id '%hs'", id.c_str()); 230 259 return NULL; -
source/gui/scripting/ScriptFunctions.cpp
diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index 0054246..6dc6d82 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) 346 347 g_Game->GetView()->CameraFollow(entityid, true); 347 348 } 348 349 350 /// Move camera to a 2D location 351 void CameraMoveTo(void* UNUSED(cbdata), entity_pos_t x, entity_pos_t z) 352 { 353 // this must not fail 354 if(!(g_Game && g_Game->GetWorld() && g_Game->GetView() && g_Game->GetWorld()->GetTerrain())) 355 return; 356 357 CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); 358 359 CVector3D target; 360 target.X = x.ToFloat(); 361 target.Z = z.ToFloat(); 362 target.Y = terrain->GetExactGroundLevel(target.X, target.Z); 363 364 // move the camera in minimap mode since this is an "instant" camera movement similar to what 365 // you would get for clicking the minimap (also prevents height glitches) 366 g_Game->GetView()->MoveCameraTarget(target, true); 367 } 368 349 369 entity_id_t GetFollowedEntity(void* UNUSED(cbdata)) 350 370 { 351 371 if (g_Game && g_Game->GetView()) … … void GuiScriptingInit(ScriptInterface& scriptInterface) 499 519 scriptInterface.RegisterFunction<CScriptVal, &GetMapSettings>("GetMapSettings"); 500 520 scriptInterface.RegisterFunction<void, entity_id_t, &CameraFollow>("CameraFollow"); 501 521 scriptInterface.RegisterFunction<void, entity_id_t, &CameraFollowFPS>("CameraFollowFPS"); 522 scriptInterface.RegisterFunction<void, entity_pos_t, entity_pos_t, &CameraMoveTo>("CameraMoveTo"); 502 523 scriptInterface.RegisterFunction<entity_id_t, &GetFollowedEntity>("GetFollowedEntity"); 503 524 scriptInterface.RegisterFunction<bool, std::string, &HotkeyIsPressed_>("HotkeyIsPressed"); 504 525 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 a8cd900..0a44e32 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" 22 23 #include "graphics/LOSTexture.h" 23 24 #include "graphics/Overlay.h" 24 #include "graphics/ShaderManager.h"25 25 #include "graphics/Terrain.h" 26 26 #include "graphics/TextureManager.h" 27 27 #include "lib/ogl.h" 28 #include "maths/Vector2D.h" 29 #include "maths/Quaternion.h" 28 30 #include "ps/Game.h" 29 31 #include "ps/Profile.h" 30 32 #include "renderer/Renderer.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) 49 m_Line(line), 50 m_VB(NULL), 51 m_VBIndices(NULL), 52 m_LineIndexCount(0), 53 m_StartCapIndexCount(0), 54 m_StartCapIndexOffset(0), 55 m_EndCapIndexCount(0), 56 m_EndCapIndexOffset(0), 57 m_Raise(.2f) 48 58 { 49 59 } 50 60 … … public: 58 68 59 69 struct SVertex 60 70 { 61 SVertex(CVector3D pos, short u, short v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; }71 SVertex(CVector3D pos, float u, float v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; } 62 72 CVector3D m_Position; 63 GLshort m_UVs[2]; 73 GLfloat m_UVs[2]; 74 float _padding[3]; // 5 floats up till now, so pad with another 3 floats to get a power of 2 64 75 }; 65 cassert(sizeof(SVertex) == 16);76 cassert(sizeof(SVertex) == 32); 66 77 67 78 void Update(); 68 79 80 /** 81 * Creates a line cap of the specified type @p endCapType at the end of the segment going in direction @p normal, and write the vertices and indices 82 * to @p verticesOut and @p indicesOut, respectively. The drawing mode to use for the resulting vertices/indices array (quad strip, 83 * triangle strip, ...) is written to @p drawModeOut. 84 * 85 * @param corner1 One of the two butt-end corner points of the line to which the cap should be attached. 86 * @param corner2 One of the two butt-end corner points of the line to which the cap should be attached. 87 * @param normal Normal vector indicating the direction of the segment to which the cap should be attached. 88 * @param endCapType The type of end cap to produce. 89 * @param verticesOut Output vector of vertices to draw using @p drawModeOut. These will be entered into a vertex buffer and handed to the 90 * renderer along with the indices array. 91 * @param indicesOut Output vector of vertex indices to draw using @p drawModeOut. These will be entered into a vertex index buffer and 92 * handed to the rendered along with the vertex array. 93 */ 94 void CreateLineCap(const CVector3D& corner1, const CVector3D& corner2, const CVector3D& normal, SOverlayTexturedLine::LineCap endCapType, 95 std::vector<SVertex>& verticesOut, std::vector<u16>& indicesOut); 96 97 /// Small utility function; grabs the centroid of the positions of two vertices 98 inline CVector3D Centroid(const SVertex& v1, const SVertex& v2) 99 { 100 return (v1.m_Position + v2.m_Position) * 0.5; 101 } 102 69 103 SOverlayTexturedLine* m_Line; 70 104 71 105 CVertexBuffer::VBChunk* m_VB; 72 106 CVertexBuffer::VBChunk* m_VBIndices; 107 unsigned int m_LineIndexCount; 108 unsigned int m_StartCapIndexOffset; // offset of start cap indices in the VB; valid iff > 0 109 unsigned int m_StartCapIndexCount; // number of start cap indices in the VB; valid iff > 0 110 unsigned int m_EndCapIndexOffset; // offset of end cap indices in the VB; valid iff > 0 111 unsigned int m_EndCapIndexCount; // number of end cap indices in the VB; valid iff > 0 112 113 float m_Raise; // small vertical offset of line from terrain to prevent visual glitches 114 73 115 }; 74 116 75 117 OverlayRenderer::OverlayRenderer() … … void OverlayRenderer::RenderOverlaysBeforeWater() 164 206 glDisable(GL_BLEND); 165 207 } 166 208 167 void OverlayRenderer::Render OverlaysAfterWater()209 void OverlayRenderer::RenderTexturedOverlayLines(const std::vector<size_t>& indices, CShaderProgramPtr shaderTexLine) 168 210 { 169 PROFILE("render overlays (after water)");170 171 if (!m->texlines.empty())172 {173 glEnable(GL_TEXTURE_2D);174 glEnable(GL_BLEND);175 glDepthMask(0);176 177 const char* shaderName;178 if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER)179 shaderName = "overlayline";180 else181 shaderName = "fixed:overlayline";182 183 CShaderManager& shaderManager = g_Renderer.GetShaderManager();184 CShaderProgramPtr shaderTexLine(shaderManager.LoadProgram(shaderName, std::map<CStr, CStr>()));185 186 shaderTexLine->Bind();187 188 211 int streamflags = shaderTexLine->GetStreamFlags(); 189 212 190 213 if (streamflags & STREAM_POS) … … void OverlayRenderer::RenderOverlaysAfterWater() 202 225 glEnableClientState(GL_TEXTURE_COORD_ARRAY); 203 226 } 204 227 205 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 209 for (size_t i = 0; i < m->texlines.size(); ++i) 228 //for (size_t i = 0; i < m->texlines.size(); ++i) 229 for (std::vector<size_t>::const_iterator idxIt = indices.begin(); idxIt != indices.end(); idxIt++) 210 230 { 211 SOverlayTexturedLine* line = m->texlines[i];231 SOverlayTexturedLine* line = m->texlines[(*idxIt)]; 212 232 if (!line->m_RenderData) 213 233 continue; 214 234 … … void OverlayRenderer::RenderOverlaysAfterWater() 218 238 219 239 CTexturedLineRData* rdata = static_cast<CTexturedLineRData*>(line->m_RenderData.get()); 220 240 241 // -- render main line quad strip ---------------------- 242 221 243 GLsizei stride = sizeof(CTexturedLineRData::SVertex); 222 CTexturedLineRData::SVertex* base = reinterpret_cast<CTexturedLineRData::SVertex*>(rdata->m_VB->m_Owner->Bind());244 CTexturedLineRData::SVertex* vertexBase = reinterpret_cast<CTexturedLineRData::SVertex*>(rdata->m_VB->m_Owner->Bind()); 223 245 224 246 if (streamflags & STREAM_POS) 225 glVertexPointer(3, GL_FLOAT, stride, &base->m_Position[0]);247 glVertexPointer(3, GL_FLOAT, stride, &vertexBase->m_Position[0]); 226 248 227 249 if (streamflags & STREAM_UV0) 228 250 { 229 251 pglClientActiveTextureARB(GL_TEXTURE0); 230 glTexCoordPointer(2, GL_SHORT, stride, &base->m_UVs[0]);252 glTexCoordPointer(2, GL_FLOAT, stride, &vertexBase->m_UVs[0]); 231 253 } 232 254 233 255 if (streamflags & STREAM_UV1) 234 256 { 235 257 pglClientActiveTextureARB(GL_TEXTURE1); 236 glTexCoordPointer(2, GL_SHORT, stride, &base->m_UVs[0]);258 glTexCoordPointer(2, GL_FLOAT, stride, &vertexBase->m_UVs[0]); 237 259 } 238 260 261 239 262 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); 263 glDrawElements(GL_QUAD_STRIP, rdata->m_LineIndexCount, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*rdata->m_VBIndices->m_Index); 264 265 g_Renderer.GetStats().m_OverlayTris += rdata->m_LineIndexCount - 2; 241 266 242 g_Renderer.GetStats().m_OverlayTris += rdata->m_VBIndices->m_Count - 2; 267 // -- render start cap (if any) ------------------------ 268 269 if (rdata->m_StartCapIndexCount > 0) 270 { 271 glDrawElements(GL_TRIANGLES, rdata->m_StartCapIndexCount, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*(rdata->m_VBIndices->m_Index + rdata->m_StartCapIndexOffset)); 272 g_Renderer.GetStats().m_OverlayTris += rdata->m_StartCapIndexCount/3; 243 273 } 244 274 245 shaderTexLine->Unbind(); 275 // -- render end cap (if any) -------------------------- 276 277 if (rdata->m_EndCapIndexCount > 0) 278 { 279 glDrawElements(GL_TRIANGLES, rdata->m_EndCapIndexCount, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*(rdata->m_VBIndices->m_Index + rdata->m_EndCapIndexOffset)); 280 g_Renderer.GetStats().m_OverlayTris += rdata->m_EndCapIndexCount/3; 281 } 282 283 } 284 285 } 286 287 void OverlayRenderer::RenderOverlaysAfterWater() 288 { 289 PROFILE("render overlays (after water)"); 290 291 if (!m->texlines.empty()) 292 { 293 glEnable(GL_TEXTURE_2D); 294 glEnable(GL_BLEND); 295 glDepthMask(0); 296 297 const char* shaderName; 298 if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER) 299 shaderName = "overlayline"; 300 else 301 shaderName = "fixed:overlayline"; 302 303 std::map<CStr, CStr> defAlwaysVisible; 304 defAlwaysVisible.insert(std::make_pair(CStr("IGNORE_LOS"), CStr("1"))); 305 306 CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture(); 307 308 CShaderManager& shaderManager = g_Renderer.GetShaderManager(); 309 CShaderProgramPtr shaderTexLineNormal(shaderManager.LoadProgram(shaderName, std::map<CStr, CStr>())); 310 CShaderProgramPtr shaderTexLineAlwaysVisible(shaderManager.LoadProgram(shaderName, defAlwaysVisible)); 311 312 // ---------------------------------------------------------------------------------------- 313 // group the overlay lines by whether they need to be always visible 314 std::vector<size_t> alwaysVisibleTexLineIndices; 315 std::vector<size_t> normalTexLineIndices; 316 317 for (size_t i = 0; i < m->texlines.size(); i++) 318 { 319 if (m->texlines[i]->m_AlwaysVisible) 320 alwaysVisibleTexLineIndices.push_back(i); 321 else 322 normalTexLineIndices.push_back(i); 323 } 324 325 // render go go 326 shaderTexLineNormal->Bind(); 327 shaderTexLineNormal->BindTexture("losTex", los.GetTexture()); 328 shaderTexLineNormal->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); 329 330 RenderTexturedOverlayLines(normalTexLineIndices, shaderTexLineNormal); 331 332 shaderTexLineNormal->Unbind(); 333 334 // ---------------------------------------------------------------------------------------- 335 336 shaderTexLineAlwaysVisible->Bind(); 337 // TODO: losTex and losTransform are unused in the always visible shader, but I'm not sure if it's worthwhile messing 338 // with it just to remove these calls 339 shaderTexLineAlwaysVisible->BindTexture("losTex", los.GetTexture()); 340 shaderTexLineAlwaysVisible->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); 341 342 RenderTexturedOverlayLines(alwaysVisibleTexLineIndices, shaderTexLineAlwaysVisible); 343 344 shaderTexLineAlwaysVisible->Unbind(); 246 345 247 346 // TODO: the shader should probably be responsible for unbinding its textures 248 347 g_Renderer.BindTexture(1, 0); … … void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera) 293 392 }; 294 393 295 394 glVertexPointer(3, GL_FLOAT, sizeof(float)*3, &pos[0].X); 296 297 395 glDrawArrays(GL_QUADS, 0, (GLsizei)4); 298 396 } 299 397 … … void CTexturedLineRData::Update() 324 422 std::vector<SVertex> vertices; 325 423 std::vector<u16> indices; 326 424 327 short v = 0;425 float v = 0.f; 328 426 329 size_t n = m_Line->m_Coords.size() / 2; 330 ENSURE(n >= 1);427 size_t n = m_Line->m_Coords.size() / 2; // number of line points 428 bool closed = m_Line->m_Closed; 331 429 332 CTerrain* terrain = m_Line->m_Terrain; 430 if (closed) 431 ENSURE(n >= 1); // granted, it's kind of hard to draw a closed loop through 1 point, but this is the minimum to avoid errors 432 else 433 ENSURE(n >= 2); 333 434 334 // TODO: this assumes paths are closed loops; probably should extend this to 335 // handle non-closed paths too 435 CTerrain* terrain = m_Line->m_Terrain; 336 436 337 437 // In each iteration, p1 is the position of vertex i, p0 is i-1, p2 is i+1. 338 438 // To avoid slightly expensive terrain computations we cycle these around and 339 439 // 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]); 440 441 CVector3D p0; 442 CVector3D p1(m_Line->m_Coords[0], 0, m_Line->m_Coords[1]); 443 CVector3D p2(m_Line->m_Coords[(1 % n)*2], 0, m_Line->m_Coords[(1 % n)*2+1]); 444 445 if (closed) 446 // grab the ending point so as to close the loop 447 p0 = CVector3D(m_Line->m_Coords[(n-1)*2], 0, m_Line->m_Coords[(n-1)*2+1]); 448 else 449 // we don't want to loop around and use the direction towards the other end of the line, so create an artificial p0 that 450 // extends the p2 -> p1 direction, and use that point instead 451 p0 = p1 + (p1 - p2); 452 343 453 bool p1floating = false; 344 454 bool p2floating = false; 345 455 … … void CTexturedLineRData::Update() 371 481 { 372 482 // For vertex i, compute bisector of lines (i-1)..(i) and (i)..(i+1) 373 483 // perpendicular to terrain normal 484 // Push vertices in GL_QUAD_STRIP order 374 485 375 486 // Normal is vertical if on water, else computed from terrain 376 487 CVector3D norm; … … void CTexturedLineRData::Update() 386 497 if (fabs(l) > 0.000001f) // avoid unlikely divide-by-zero 387 498 b *= m_Line->m_Thickness / l; 388 499 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)); 500 vertices.push_back(SVertex(p1 + b + norm*m_Raise, 0.f, v)); 393 501 indices.push_back(vertices.size() - 1); 394 502 395 vertices.push_back(SVertex(p1 - b + norm* raised, 1, v));503 vertices.push_back(SVertex(p1 - b + norm*m_Raise, 1.f, v)); 396 504 indices.push_back(vertices.size() - 1); 397 505 398 506 // Alternate V coordinate for debugging … … void CTexturedLineRData::Update() 402 510 p0 = p1; 403 511 p1 = p2; 404 512 p1floating = p2floating; 513 514 // if in closed mode, wrap around the coordinate array for p2 -- otherwise, extend linearly 515 if (!closed && i == n-2) 516 // next iteration is the last point of the line, so create an artificial p2 that extends the p0 -> p1 direction 517 p2 = p1 + (p1 - p0); 518 else 405 519 p2 = CVector3D(m_Line->m_Coords[((i+2) % n)*2], 0, m_Line->m_Coords[((i+2) % n)*2+1]); 520 406 521 p2.Y = terrain->GetExactGroundLevel(p2.X, p2.Z); 407 522 if (p2.Y < w) 408 523 { … … void CTexturedLineRData::Update() 413 528 p2floating = false; 414 529 } 415 530 531 532 if (closed) 533 { 416 534 // Close the path 417 535 indices.push_back(0); 418 536 indices.push_back(1); 537 } 538 539 m_LineIndexCount = indices.size(); 540 541 if (!closed) 542 { 543 // ------------------------------------------------------------------- 544 // create start cap 545 546 std::vector<u16> startCapIndices; 547 std::vector<SVertex> startCapVertices; 548 549 CreateLineCap( 550 vertices[1].m_Position, // the order of these vertices is actually important here, swapping them produces caps at the wrong side 551 vertices[0].m_Position, 552 (Centroid(vertices[1], vertices[0]) - Centroid(vertices[3], vertices[2])).Normalized(), 553 m_Line->m_StartCap, 554 startCapVertices, 555 startCapIndices 556 ); 557 558 if (!startCapVertices.empty() && !startCapIndices.empty()) 559 { 560 ENSURE(startCapIndices.size() % 3 == 0); // GL_TRIANGLES indices, so must be multiple of 3 561 562 m_StartCapIndexOffset = indices.size(); // start cap indices start where the line indices end 563 m_StartCapIndexCount = startCapIndices.size(); 564 565 // adjust the indices to take the existing line vertices into account (right now they're relative to their own vertices array) 566 for (size_t k = 0; k < m_StartCapIndexCount; ++k) 567 startCapIndices[k] += vertices.size(); 568 569 indices.insert(indices.end(), startCapIndices.begin(), startCapIndices.end()); // append start cap indices to VBO data 570 vertices.insert(vertices.end(), startCapVertices.begin(), startCapVertices.end()); // append start cap vertices to VBO data 571 } 572 573 // ------------------------------------------------------------------- 574 // create end cap 575 576 std::vector<u16> endcapIndices; 577 std::vector<SVertex> endcapVertices; 578 579 CreateLineCap( 580 vertices[2*n-2].m_Position, // second-to-last line vertex 581 vertices[2*n-1].m_Position, // last line vertex 582 (Centroid(vertices[2*n-2], vertices[2*n-1]) - Centroid(vertices[2*n-4], vertices[2*n-3])).Normalized(), 583 m_Line->m_EndCap, 584 endcapVertices, 585 endcapIndices 586 ); 587 588 if (!endcapVertices.empty() && !endcapIndices.empty()) 589 { 590 ENSURE(endcapIndices.size() % 3 == 0); // GL_TRIANGLES indices, so must be multiple of 3 591 592 m_EndCapIndexOffset = indices.size(); // end cap indices start where the line + start indices end 593 m_EndCapIndexCount = endcapIndices.size(); 594 595 // adjust the indices to take the existing line + start vertices into account (right now they're relative to their own vertices array) 596 for (size_t k = 0; k < m_EndCapIndexCount; ++k) 597 endcapIndices[k] += vertices.size(); 598 599 indices.insert(indices.end(), endcapIndices.begin(), endcapIndices.end()); // append end cap indices to VBO data 600 vertices.insert(vertices.end(), endcapVertices.begin(), endcapVertices.end()); // append end cap vertices to VBO data 601 } 602 603 } 419 604 420 605 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]); 606 m_VB->m_Owner->UpdateChunkVertices(m_VB, &vertices[0]); // copy data into VBO 422 607 423 608 // Update the indices to include the base offset of the vertex data 424 609 for (size_t k = 0; k < indices.size(); ++k) 425 610 indices[k] += m_VB->m_Index; 426 611 427 612 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]); 613 m_VBIndices->m_Owner->UpdateChunkVertices(m_VBIndices, &indices[0]); // copy data into VBO 614 } 615 616 void CTexturedLineRData::CreateLineCap(const CVector3D& corner1, const CVector3D& corner2, const CVector3D& normal, SOverlayTexturedLine::LineCap endCapType, 617 std::vector<SVertex>& verticesOut, std::vector<u16>& indicesOut) 618 { 619 620 if (endCapType == SOverlayTexturedLine::LINECAP_FLAT) 621 return; // no action needed, this is the default 622 623 CTerrain* terrain = m_Line->m_Terrain; 624 CmpPtr<ICmpWaterManager> cmpWaterManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); 625 626 // when not in closed mode, we've created artificial points for the start- and endpoints that extend the line in the 627 // direction of the first and the last segment, respectively. Thus, we know both the start and endpoints have perpendicular 628 // butt endings, i.e. the end corner vertices on either side of the line extend perpendicularly from the segment direction. 629 // That is to say, we will have something like 630 // . 631 // this: and not like this: /| 632 // ____. / | 633 // | / . 634 // | / 635 // ____. / 636 // 637 638 int roundCapPoints = 8; // = how many points to sample along the semicircle for rounded caps (including corner points) 639 float texCenterU = 0.5f; // U coordinate of butt end centroid 640 float texCenterV = 0.5f; // V coordinate of butt end centroid 641 642 float radius = m_Line->m_Thickness; 643 CVector3D centerPoint = (corner1 + corner2) * 0.5f; // middle between last two vertices (i.e. "butt" corner points) 644 645 switch (endCapType) 646 { 647 648 case SOverlayTexturedLine::LINECAP_SHARP: 649 { 650 roundCapPoints = 3; // creates only one point directly ahead 651 radius *= 1.2f; // make it a bit sharper (note that we don't use this for the butt corner points so it should be ok) 652 texCenterU = 0.485f; // slight visual correction to make the texture edges match up better (they're slightly too thin with only 3 points and a U coord of 0.5f) 653 } 654 case SOverlayTexturedLine::LINECAP_ROUND: 655 { 656 657 // Draw a rounded line cap in the 3D plane of the line specified by the two corner points and the normal vector of the 658 // line's direction. The terrain normal at the centroid between the two corner points is perpendicular to this plane. 659 // The way this works is by taking a vector from the corner points' centroid to one of the corner points (which is then of 660 // radius length), and rotate it around the terrain normal vector in that centroid. This will rotate the vector in the line's 661 // plane, producing the desired rounded cap. 662 663 // To please OpenGL's winding order thing, this angle needs to be negated depending on whether we start rotating from the 664 // (center -> corner1) or (center -> corner2) vector. For the (center -> corner2) vector, apparently we need to use the 665 // negated angle. There's probable some reason for it you can look up, but trial and error is a much more effective way of 666 // finding out. 667 float stepAngle = -(float)(M_PI/(roundCapPoints-1)); 668 669 // first push the vertices, then figure out the GL_TRIANGLES indices 670 verticesOut.push_back(SVertex(centerPoint, texCenterU, texCenterV)); 671 verticesOut.push_back(SVertex(corner2, 0.f, 0.f)); 672 673 // usually corner2 - centerPoint suffices for baseVector below since it is of radius length, but we want to support custom 674 // radii to support tuning the sharp caps (see above) 675 CVector3D baseVector = (corner2 - centerPoint).Normalized() * radius; 676 CVector3D centerPointNormalVector = terrain->CalcExactNormal(centerPoint.X, centerPoint.Z); // always normalized 677 678 for (int i = 1; i < roundCapPoints - 1; ++i) 679 { 680 // set up a quaternion rotation of the centerPoint -> corner vector by i*stepAngle 681 // radians around the terrain normal at centerPoint 682 CQuaternion quatRotation; 683 quatRotation.FromAxisAngle(centerPointNormalVector, i * stepAngle); 684 // at this point, quatRotation is always normalized because centerPointNormalVector is normalized. Note that we don't need 685 // to scale the rotated baseVector to the radius, as baseVector was already of radius length and rotation maintains vector length. 686 CVector3D worldPos3D = centerPoint + quatRotation.Rotate(baseVector); 687 688 // let v range from 0 to 1 as we move along the semi-circle, keep u fixed at 0 (i.e. curve the left vertical edge of the texture 689 // around the edge of the semicircle) 690 float u = 0.f; 691 float v = clamp((i/(float)(roundCapPoints-1)), 0.f, 1.f); 692 verticesOut.push_back(SVertex(worldPos3D, u, v)); // pos, u, v 693 } 694 695 // connect back to the other butt corner point to complete the semicircle 696 verticesOut.push_back(SVertex(corner1, 0.f, 1.f)); 697 698 // now push indices for GL_TRIANGLES; vertices[0] is the center, vertices[1] is the first corner point, then a bunch of 699 // radial samples, and then at the end we have the other corner point again. So: 700 for (int i=1; i < roundCapPoints; ++i) 701 { 702 indicesOut.push_back(0); // center point 703 indicesOut.push_back(i); 704 indicesOut.push_back(i+1); 705 } 706 707 } 708 break; 709 710 case SOverlayTexturedLine::LINECAP_SQUARE: 711 { 712 // extend the (corner1 -> corner2) vector along the direction normal and draw a triangle fan with 3 triangles (although actually 713 // drawn as GL_TRIANGLES) 714 // NOTE: the order in which the vertices are pushed out determines the visibility, as they 715 // are rendered only one-sided; the wrong order of vertices will only make the cap visible from the bottom. 716 717 verticesOut.push_back(SVertex(centerPoint, texCenterU, texCenterV)); // push center point 718 verticesOut.push_back(SVertex(corner2, 0.f, 0.f)); // push butt corner point 2 719 verticesOut.push_back(SVertex(corner2 + (normal * (m_Line->m_Thickness)), 0.f, 0.33333f)); // extend butt corner point 2 along the normal vector 720 verticesOut.push_back(SVertex(corner1 + (normal * (m_Line->m_Thickness)), 0.f, 0.66666f)); // extend butt corner point 1 along the normal vector 721 verticesOut.push_back(SVertex(corner1, 0.f, 1.0f)); // push butt corner point 1 722 723 for (int i=1; i < 4; ++i) 724 { 725 indicesOut.push_back(0); // center point 726 indicesOut.push_back(i); 727 indicesOut.push_back(i+1); 728 } 729 730 } 731 break; 732 733 default: 734 break; 735 736 } 737 429 738 } -
source/renderer/OverlayRenderer.h
diff --git a/source/renderer/OverlayRenderer.h b/source/renderer/OverlayRenderer.h index a48ae0a..7845cb4 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 the overlay lines currently registered in the internals (i.e. in m->texlines) in the order 92 * specified by @p indices. Used for splitting the textured overlay lines by their visibility status, and then batch 93 * rendering them with the right shader by passing the correct indices to this method. 94 */ 95 void RenderTexturedOverlayLines(const std::vector<size_t>& indices, CShaderProgramPtr shader); 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..ecf8930 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: 137 137 /** 138 138 * Class RenderModifierRenderer: Interface to a model renderer that can render 139 139 * its models via a RenderModifier that sets up fragment stages. 140 * TODO: this appears to be currently unused, should be considered for deletion 140 141 */ 141 142 class RenderModifierRenderer : public ModelRenderer 142 143 { -
source/simulation2/TypeList.h
diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h index a7ac94b..795a586 100644
a b COMPONENT(Position) // must be before VisualActor 114 114 INTERFACE(ProjectileManager) 115 115 COMPONENT(ProjectileManager) 116 116 117 INTERFACE(RallyPoint) 118 COMPONENT(RallyPoint) 119 117 120 INTERFACE(RangeManager) 118 121 COMPONENT(RangeManager) 119 122 -
new file source/simulation2/components/CCmpRallyPoint.cpp
diff --git a/source/simulation2/components/CCmpRallyPoint.cpp b/source/simulation2/components/CCmpRallyPoint.cpp new file mode 100644 index 0000000..56baf25
- + 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 "simulation2/system/Component.h" 21 #include "ICmpRallyPoint.h" 22 23 #include "simulation2/MessageTypes.h" 24 #include "simulation2/helpers/Render.h" 25 #include "simulation2/helpers/Geometry.h" 26 27 #include "simulation2/components/ICmpPosition.h" 28 #include "simulation2/components/ICmpPathFinder.h" 29 #include "simulation2/components/ICmpFootprint.h" 30 #include "simulation2/components/ICmpRangeManager.h" 31 #include "simulation2/components/ICmpPlayer.h" 32 #include "simulation2/components/ICmpOwnership.h" 33 #include "simulation2/components/ICmpTerrain.h" 34 #include "simulation2/components/ICmpPlayerManager.h" 35 #include "simulation2/components/ICmpPlayer.h" 36 #include "simulation2/components/ICmpObstructionManager.h" 37 38 #include "ps/CLogger.h" 39 #include "graphics/Overlay.h" 40 #include "graphics/TextureManager.h" 41 #include "renderer/Renderer.h" 42 43 struct SVisibilitySegment 44 { 45 bool m_Visible; 46 size_t m_StartIndex; 47 size_t m_EndIndex; // inclusive 48 49 SVisibilitySegment(bool visible, size_t startIndex, size_t endIndex) 50 : m_Visible(visible), m_StartIndex(startIndex), m_EndIndex(endIndex) 51 {} 52 53 bool operator==(const SVisibilitySegment& other) const 54 { 55 return (m_Visible == other.m_Visible && m_StartIndex == other.m_StartIndex && m_EndIndex == other.m_EndIndex); 56 } 57 58 bool operator!=(const SVisibilitySegment& other) const 59 { 60 return !(*this == other); 61 } 62 63 bool IsSinglePoint() 64 { 65 return (m_StartIndex == m_EndIndex); 66 } 67 }; 68 69 class CCmpRallyPoint : public ICmpRallyPoint 70 { 71 // import some types for less verbosity 72 typedef ICmpPathfinder::Waypoint Waypoint; 73 typedef ICmpPathfinder::Path Path; 74 typedef ICmpPathfinder::Goal Goal; 75 typedef ICmpRangeManager::CLosQuerier CLosQuerier; 76 typedef SOverlayTexturedLine::LineCap LineCap; 77 78 public: 79 static void ClassInit(CComponentManager& componentManager) 80 { 81 componentManager.SubscribeToMessageType(MT_RenderSubmit); 82 componentManager.SubscribeToMessageType(MT_OwnershipChanged); 83 componentManager.SubscribeToMessageType(MT_TurnStart); 84 // TODO: should probably also listen to movement messages 85 } 86 87 DEFAULT_COMPONENT_ALLOCATOR(RallyPoint) 88 89 protected: 90 91 /// Actual position of the rally point (in fixed-point world coordinates for stability) 92 CFixedVector2D m_RallyPoint; 93 /// Full path to the rally point as returned by the pathfinder, with some post-processing applied to reduce zig/zagging. 94 std::vector<CVector2D> m_FullPath; 95 std::deque<SVisibilitySegment> m_VisibilitySegments; 96 97 bool m_Displayed; ///< Should we render the rally point and its path line? (set from JS when e.g. the unit is selected/deselected) 98 bool m_SmoothPath; ///< Smooth the path before rendering? 99 100 entity_id_t m_MarkerEntity; ///< Entity ID of the rally point marker. Allocated when first displayed. 101 std::wstring m_MarkerTemplate; ///< Template name of the rally point marker. 102 103 /// Marker connector line settings (loaded from XML) 104 float m_LineThickness; 105 CColor m_LineColor; 106 CColor m_LineDashColor; 107 LineCap m_LineStartCap; 108 LineCap m_LineEndCap; 109 std::wstring m_LineTexturePath; 110 std::wstring m_LineTextureMaskPath; 111 112 CTexturePtr m_Texture; 113 CTexturePtr m_TextureMask; 114 115 ///< Textured overlay lines for the marker line. There can be multiple because we need to do dashes inside the SoD. 116 std::vector<SOverlayTexturedLine> m_TexturedOverlayLines; 117 118 /// Draw little overlay circles to indicate where the exact path points are? 119 bool m_EnableDebugNodeOverlay; 120 std::vector<SOverlayLine> m_DebugNodeOverlays; 121 122 public: 123 124 CCmpRallyPoint() 125 { 126 m_Displayed = false; 127 m_SmoothPath = true; 128 m_MarkerEntity = INVALID_ENTITY; 129 m_EnableDebugNodeOverlay = false; 130 } 131 132 ~CCmpRallyPoint(){} 133 134 static std::string GetSchema() 135 { 136 return 137 "<a:component />" 138 "<a:help>Displays a rally point where created units will gather when spawned.</a:help>" 139 "<a:example>" 140 "<MarkerTemplate>special/rallypoint</MarkerTemplate>" 141 "<LineThickness>0.75</LineThickness>" 142 "<LineStartCap>round</LineStartCap>" 143 "<LineEndCap>square</LineEndCap>" 144 "<LineColour r='20' g='128' b='240'></LineColour>" 145 "</a:example>" 146 "<element name='MarkerTemplate' a:help='Template name for the rally point marker entity (typically a waypoint flag actor).'>" 147 "<text/>" 148 "</element>" 149 "<optional>" 150 "<element name='LineColour'>" 151 "<attribute name='r'>" 152 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" 153 "</attribute>" 154 "<attribute name='g'>" 155 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" 156 "</attribute>" 157 "<attribute name='b'>" 158 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" 159 "</attribute>" 160 "</element>" 161 "</optional>" 162 "<optional>" 163 "<element name='LineStartCap'>" 164 "<choice>" 165 "<value a:help='Abrupt line ending; line endings are not closed.'>flat</value>" 166 "<value a:help='Semi-circular line end cap.'>round</value>" 167 "<value a:help='Sharp, pointy line end cap.'>sharp</value>" 168 "<value a:help='Square line end cap.'>square</value>" 169 "</choice>" 170 "</element>" 171 "</optional>" 172 "<optional>" 173 "<element name='LineEndCap'>" 174 "<choice>" 175 "<value a:help='Abrupt line ending; line endings are not closed.'>flat</value>" 176 "<value a:help='Semi-circular line end cap.'>round</value>" 177 "<value a:help='Sharp, pointy line end cap.'>sharp</value>" 178 "<value a:help='Square line end cap.'>square</value>" 179 "</choice>" 180 "</element>" 181 "</optional>" 182 "<optional>" 183 "<element name='LineThickness' a:help='Thickness of the marker line connecting the entity to the rally point marker.'>" 184 "<data type='decimal'/>" 185 "</element>" 186 "</optional>" 187 "<optional>" 188 "<element name='LineDashColour'>" 189 "<attribute name='r'>" 190 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" 191 "</attribute>" 192 "<attribute name='g'>" 193 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" 194 "</attribute>" 195 "<attribute name='b'>" 196 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" 197 "</attribute>" 198 "</element>" 199 "</optional>"; 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 // TODO should probably serialize the rally point location here 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 break; 230 } 231 case MT_OwnershipChanged: 232 { 233 CreateMarker(); 234 RepositionMarker(); // the new marker doesn't have a position yet, so let's make sure to update it 235 } 236 case MT_TurnStart: 237 { 238 UpdateOverlayLines(); // check for changes to the SoD and update the overlay lines accordingly 239 } 240 } 241 } 242 243 virtual CFixedVector2D GetPosition() 244 { 245 return m_RallyPoint; 246 } 247 248 virtual void SetPosition(CFixedVector2D pos) 249 { 250 bool posChanged = (pos.X != m_RallyPoint.X || pos.Y != m_RallyPoint.Y); 251 m_RallyPoint = pos; 252 253 if(posChanged) 254 { 255 RecomputeRallyPointPath(); 256 RepositionMarker(); 257 } 258 } 259 260 virtual void Unset() 261 { 262 SetPosition(CFixedVector2D()); // reset to zero 263 } 264 265 bool IsSet() 266 { 267 return !m_RallyPoint.IsZero(); 268 } 269 270 virtual void SetDisplayed(bool displayed) 271 { 272 bool displayChanged = (m_Displayed != displayed); 273 m_Displayed = displayed; 274 275 if(displayChanged) 276 { 277 RepositionMarker(); // move the marker out of oblivion and back into the real world, or vice-versa 278 UpdateOverlayLines(); // check for changes to the SoD and update the overlay lines accordingly 279 } 280 } 281 282 private: 283 284 /** 285 * (Re)creates the rally point marker entity. Called upon initialization and whenever the ownership of this entity changes, as the marker 286 * depends on the owner's civilization. If a marker entity already exists, it will be destroyed first. 287 */ 288 void CreateMarker(); 289 290 /** 291 * Repositions the rally point marker; moves it outside of the world (ie. hides it), or positions it at the rally point. Executed 292 * whenever either the position of the rally point changes (including whether it is set or not), or the display flag changes. 293 * 294 * TODO: this is executed from JS-executed methods, which must not fail under any circumstances. We'd probably have to change this to 295 * detect the changes in the rendering code rather than execute them directly here. 296 **/ 297 void RepositionMarker(); 298 299 /** 300 * Recomputes the full path from this entity to the rally point, and does all the necessary post-processing to make it prettier. 301 * Called whenever the rally point position changes. 302 */ 303 void RecomputeRallyPointPath(); 304 305 /** 306 * Sets up the overlay lines for rendering according to the current full path and visibility segments. Does all the necessary splitting 307 * of the line into solid and dashed pieces (for the SoD). Should be called whenever the SoD has changed. If no full path is currently 308 * set, this method does nothing. 309 */ 310 void ConstructOverlayLines(); 311 312 /** 313 * Checks for changes to the SoD to the previously saved state, and reconstructs the overlay lines to match if necessary. Does nothing 314 * if the rally point line is not currently set to be displayed, so that we don't waste compute cycles trying to update rally point 315 * overlays for every building all the time if they're not even gonna be visible anyway. 316 */ 317 void UpdateOverlayLines(); 318 319 /** 320 * Removes waypoints from m_rallyPointPath that are obstructed by the originating building's footprint, and links up the rally point path 321 * nicely to the edge of the building's footprint. Only needed if the pathfinder can possibly return obstructed tile waypoints, i.e. when 322 * pathfinding is started from an obstructed tile. 323 */ 324 void FixFootprintWaypoints(std::vector<CVector2D>& coords, CmpPtr<ICmpPosition> cmpPosition, CmpPtr<ICmpFootprint> cmpFootprint); 325 326 /** 327 * Removes waypoints that are inside the shroud of darkness, i.e. where the player shouldn't be able to get any information about the 328 * positions of various buildings and whatnot from the rally point path. 329 */ 330 void FixInvisibleWaypoints(std::vector<CVector2D>& coords); 331 332 /** 333 * Simplifies the path by removing waypoints that lie between two points that are visible from one another. This is primarily intended to 334 * reduce some unnecessary curviness of the path; the pathfinder returns a mathematically (near-)optimal path, which will happily curve 335 * and bend to reduce costs. Visually, it doesn't make sense for a rally point path to curve and bend when it could just as well have gone 336 * in a straight line, so that's why we have this. 337 * 338 * @p coords array of path coordinates to simplify 339 * @p maxSegmentLinks if non-zero, indicates the maximum amount of consecutive node-to-node links that can be eliminated to form a single 340 * link. If this value is set to e.g. 1, then no reductions will be performed. A value of 3 means that at most 3 341 * consecutive node links will be joined into a single link. 342 */ 343 void ReduceSegmentsByVisibility(std::vector<CVector2D>& coords, unsigned maxSegmentLinks = 0); 344 345 /** 346 * Returns a list of indices of waypoints in the current path (m_FullPath) where the LOS visibility changes, ordered from building to rally 347 * point. Used to construct the overlay line segments and track changes to the shroud of darkness. 348 */ 349 void GetVisibilitySegments(std::deque<SVisibilitySegment>& out); 350 351 /** 352 * Helper function to GetVisibilitySegments, factored out for testing. Merges single-point segments with its neighbouring segments. 353 * You should not have to call this method directly. 354 */ 355 static void MergeVisibilitySegments(std::deque<SVisibilitySegment>& segments); 356 357 void RenderSubmit(SceneCollector& collector); 358 359 }; 360 361 REGISTER_COMPONENT_TYPE(RallyPoint) 362 363 void CCmpRallyPoint::Init(const CParamNode& paramNode) 364 { 365 // set some defaults 366 m_LineThickness = .3f; 367 m_LineStartCap = SOverlayTexturedLine::LINECAP_FLAT; 368 m_LineEndCap = SOverlayTexturedLine::LINECAP_FLAT; 369 m_LineTexturePath = std::wstring(L"art/textures/misc/rallypoint_line.png"); 370 m_LineTextureMaskPath = std::wstring(L"art/textures/misc/rallypoint_line_mask.png"); 371 // implicit constructor defaults for m_LineColor and m_MarkerTemplate 372 373 // --------------------------------------------------------------------------------------------- 374 // load some XML configuration data 375 376 if (paramNode.GetChild("MarkerTemplate").IsOk()) 377 m_MarkerTemplate = paramNode.GetChild("MarkerTemplate").ToString(); 378 379 if (paramNode.GetChild("LineThickness").IsOk()) 380 m_LineThickness = paramNode.GetChild("LineThickness").ToFixed().ToFloat(); 381 382 const CParamNode& lineColor = paramNode.GetChild("LineColour"); 383 if (lineColor.IsOk() && lineColor.GetChild("@r").IsOk() && lineColor.GetChild("@g").IsOk() && lineColor.GetChild("@b").IsOk()) 384 { 385 m_LineColor = CColor( 386 lineColor.GetChild("@r").ToInt()/255.f, 387 lineColor.GetChild("@g").ToInt()/255.f, 388 lineColor.GetChild("@b").ToInt()/255.f, 389 1.f 390 ); 391 } 392 393 const CParamNode& lineDashColor = paramNode.GetChild("LineDashColour"); 394 if (lineDashColor.IsOk() && lineDashColor.GetChild("@r").IsOk() && lineDashColor.GetChild("@g").IsOk() && lineDashColor.GetChild("@b").IsOk()) 395 { 396 m_LineDashColor = CColor( 397 lineDashColor.GetChild("@r").ToInt()/255.f, 398 lineDashColor.GetChild("@g").ToInt()/255.f, 399 lineDashColor.GetChild("@b").ToInt()/255.f, 400 1.f 401 ); 402 } 403 else 404 { 405 m_LineDashColor = m_LineColor; // if not specified, use the same colour as the regular line segments 406 } 407 408 if (paramNode.GetChild("LineTexture").IsOk()) 409 m_LineTexturePath = paramNode.GetChild("LineTexture").ToString(); 410 411 if (paramNode.GetChild("LineMaskTexture").IsOk()) 412 m_LineTextureMaskPath = paramNode.GetChild("LineMaskTexture").ToString(); 413 414 if (paramNode.GetChild("LineStartCap").IsOk()) 415 { 416 const std::wstring& xmlLineStartCap = paramNode.GetChild("LineStartCap").ToString(); 417 if (!SOverlayTexturedLine::StrToLineCap(xmlLineStartCap, m_LineStartCap)) 418 LOGERROR(L"Unrecognized LineStartCap value \"%ls\"", xmlLineStartCap); // this shouldn't happen by virtue of the XML validation 419 } 420 421 if (paramNode.GetChild("LineEndCap").IsOk()) 422 { 423 const std::wstring& xmlLineEndCap = paramNode.GetChild("LineEndCap").ToString(); 424 if (!SOverlayTexturedLine::StrToLineCap(xmlLineEndCap, m_LineEndCap)) 425 LOGERROR(L"Unrecognized LineEndCap value \"%ls\"", xmlLineEndCap); // this shouldn't happen by virtue of the XML validation 426 } 427 428 // --------------------------------------------------------------------------------------------- 429 // load some textures 430 431 CTextureProperties texturePropsBase(m_LineTexturePath); 432 texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); 433 texturePropsBase.SetMaxAnisotropy(8.f); 434 m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase); 435 436 CTextureProperties texturePropsMask(m_LineTextureMaskPath); 437 texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); 438 texturePropsMask.SetMaxAnisotropy(8.f); 439 m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask); 440 441 // --------------------------------------------------------------------------------------------- 442 443 CreateMarker(); // TODO: evaluate how much load this puts on the entity IDs, if it's too high then we can do this on-demand) 444 445 } 446 447 void CCmpRallyPoint::CreateMarker() 448 { 449 450 CComponentManager& componentMgr = GetSimContext().GetComponentManager(); 451 452 // if a marker entity already exists, kill it first 453 if (m_MarkerEntity != INVALID_ENTITY) 454 { 455 CmpPtr<ICmpPosition> markerCmpPosition(GetSimContext(), m_MarkerEntity); 456 if (!markerCmpPosition.null()) 457 markerCmpPosition->MoveOutOfWorld(); 458 459 componentMgr.DestroyComponentsSoon(m_MarkerEntity); // queue entity for destruction 460 m_MarkerEntity = INVALID_ENTITY; // make sure any code below doesn't try to access the soon-to-be-destroyed entity anymore 461 } 462 463 // allocate a new entity for the marker 464 if (!m_MarkerTemplate.empty()) 465 { 466 m_MarkerEntity = componentMgr.AllocateNewLocalEntity(); 467 if (m_MarkerEntity != INVALID_ENTITY) 468 m_MarkerEntity = componentMgr.AddEntity(m_MarkerTemplate, m_MarkerEntity); 469 } 470 471 CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), GetEntityId()); 472 if (!cmpOwnership.null()) 473 { 474 player_id_t ownerId = cmpOwnership->GetOwner(); 475 if (ownerId != INVALID_PLAYER) 476 { 477 CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY); 478 ENSURE(!cmpPlayerManager.null()); 479 480 CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(ownerId)); 481 if (!cmpPlayer.null()) 482 { 483 // TODO: in the future, when variants are working properly for actors, we should set the variant corresponding to the 484 // owning player's civilization here. 485 } 486 } 487 } 488 489 } 490 491 void CCmpRallyPoint::RepositionMarker() 492 { 493 if (m_MarkerEntity == INVALID_ENTITY) 494 return; // there is no valid marker entity, no use trying to position it 495 496 CmpPtr<ICmpPosition> markerCmpPosition(GetSimContext(), m_MarkerEntity); 497 if (!markerCmpPosition.null()) 498 { 499 if(m_Displayed && IsSet()) 500 { 501 markerCmpPosition->JumpTo(m_RallyPoint.X, m_RallyPoint.Y); 502 } 503 else 504 { 505 markerCmpPosition->MoveOutOfWorld(); // hide it 506 } 507 } 508 } 509 510 void CCmpRallyPoint::RecomputeRallyPointPath() 511 { 512 513 m_FullPath.clear(); 514 m_VisibilitySegments.clear(); 515 516 if (!IsSet()) 517 return; // no use computing a path if the rally point isn't set 518 519 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId()); 520 if (cmpPosition.null() || !cmpPosition->IsInWorld()) 521 return; // no point going on if this entity doesn't have a position or is outside of the world 522 523 CmpPtr<ICmpFootprint> cmpFootprint(GetSimContext(), GetEntityId()); 524 CmpPtr<ICmpPathfinder> cmpPathFinder(GetSimContext(), SYSTEM_ENTITY); 525 //CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); 526 527 528 // ------------------------------------------------------------------------------------------------- 529 530 entity_pos_t pathStartX = cmpPosition->GetPosition2D().X; 531 entity_pos_t pathStartY = cmpPosition->GetPosition2D().Y; 532 533 // Find a long path to the goal point -- this uses the tile-based pathfinder, which will return a 534 // list of waypoints (i.e. a Path) from the building to the goal, where each waypoint is centered 535 // at a tile. We'll have to do some post-processing on the path to get it smooth. 536 Path path; 537 std::vector<Waypoint>& waypoints = path.m_Waypoints; 538 539 Goal goal = { Goal::POINT, m_RallyPoint.X, m_RallyPoint.Y }; 540 cmpPathFinder->ComputePath( 541 pathStartX, 542 pathStartY, 543 goal, 544 cmpPathFinder->GetPassabilityClass("default"), 545 cmpPathFinder->GetCostClass("default"), 546 path 547 ); 548 549 if (path.m_Waypoints.size() < 2) 550 return; // not likely to happen, but can't hurt to check 551 552 // from here on, we choose to represent the waypoints as CVector2D floats to avoid to have to convert back and forth 553 // between fixed-point Waypoint/CFixedVector2D and various other float-based formats used by interpolation and whatnot. 554 // Since we'll only be further using these points for rendering purposes, using floats should be fine. 555 556 // make sure to add the actual goal point as the last point (the long pathfinder only finds paths to 557 // the tile closest to the goal, so we need to complete the last bit from the closest tile to the rally 558 // point itself) 559 // NOTE: the points are returned in reverse order (from the goal to the start point), so we need to 560 // actually insert it at the front of the coordinate list. Hence, we'll do this first before appending the rest of the 561 // fixed waypoints as CVector2Ds. 562 563 Waypoint& lastWaypoint = waypoints.back(); 564 if (lastWaypoint.x != goal.x || lastWaypoint.z != goal.z) 565 m_FullPath.push_back(CVector2D(goal.x.ToFloat(), goal.z.ToFloat())); 566 567 // add the rest of the waypoints 568 for (size_t i = 0; i < waypoints.size(); ++i) 569 m_FullPath.push_back(CVector2D(waypoints[i].x.ToFloat(), waypoints[i].z.ToFloat())); 570 571 // ------------------------------------------------------------------------------------------- 572 // postprocessing 573 574 // linearize the path; 575 // pass through the waypoints , averaging each waypoint with its next one except the last one. Because the path 576 // goes from the marker to this entity and we want to keep the point at the marker's exact position, loop backwards through the 577 // waypoints so that the marker waypoint is maintained. 578 // TODO: see if we can do this at the same time as the waypoint -> coord conversion above 579 for(size_t i = m_FullPath.size() - 1; i > 0; --i) 580 m_FullPath[i] = (m_FullPath[i] + m_FullPath[i-1]) / 2.0f; 581 582 // if there's a footprint, remove any points returned by the pathfinder that may be on obstructed footprint tiles 583 if (!cmpFootprint.null()) 584 FixFootprintWaypoints(m_FullPath, cmpPosition, cmpFootprint); 585 586 // eliminate some consecutive waypoints that are visible from eachother. 587 ReduceSegmentsByVisibility(m_FullPath, 6); // reduce across a maximum distance of approx. 6 tiles (prevents segments that are too long to properly stick to the terrain) 588 589 //// <DEBUG> /////////////////////////////////////////////// 590 if (m_EnableDebugNodeOverlay) 591 m_DebugNodeOverlays.clear(); 592 593 if (m_EnableDebugNodeOverlay && m_SmoothPath) 594 { 595 // create separate control point overlays so we can differentiate when using smoothing (offset them a little higher from the 596 // terrain so we can still see them after the interpolated points are added) 597 for (size_t j = 0; j < m_FullPath.size(); ++j) 598 { 599 SOverlayLine overlayLine; 600 overlayLine.m_Color = CColor(1.0f, 0.0f, 0.0f, 1.0f); 601 overlayLine.m_Thickness = 2; 602 SimRender::ConstructSquareOnGround(GetSimContext(), m_FullPath[j].X, m_FullPath[j].Y, 0.2f, 0.2f, 1.0f, overlayLine, true); 603 m_DebugNodeOverlays.push_back(overlayLine); 604 } 605 } 606 //// </DEBUG> ////////////////////////////////////////////// 607 608 if (m_SmoothPath) 609 // the number of points to interpolate goes together with the maximum amount of node links allowed to be joined together by 610 // the visibility reduction. The more node links that can be joined together, the more interpolated points you need to generate 611 // to be able to deal with local terrain height changes 612 SimRender::InterpolatePointsRNS(m_FullPath, false, 0, 8); // no offset, keep line at its exact path 613 614 // ------------------------------------------------------------------------------------------- 615 616 // 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 617 GetVisibilitySegments(m_VisibilitySegments); 618 619 // build overlay lines for the new path 620 ConstructOverlayLines(); 621 622 } 623 624 void CCmpRallyPoint::ConstructOverlayLines() 625 { 626 627 // we need to create a new SOverlayTexturedLine every time we want to change the coordinates after having passed it to the renderer, 628 // because it does some fancy vertex buffering thing and caches them internally instead of recomputing them on every pass (which is 629 // only sensible) 630 631 m_TexturedOverlayLines.clear(); 632 633 if (m_FullPath.size() < 2) 634 return; 635 636 CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); 637 SOverlayTexturedLine::LineCap dashesLineCap = SOverlayTexturedLine::LINECAP_ROUND; // line caps to use for the dashed segments (and any other segment's edges that border it) 638 639 for (std::deque<SVisibilitySegment>::const_iterator it = m_VisibilitySegments.begin(); it != m_VisibilitySegments.end(); ++it) 640 { 641 const SVisibilitySegment& segment = (*it); 642 643 if (segment.m_Visible) 644 { 645 // does this segment border on the building or rally point flag on either side? 646 bool bordersBuilding = (segment.m_EndIndex == m_FullPath.size() - 1); 647 bool bordersFlag = (segment.m_StartIndex == 0); 648 649 // construct solid textured overlay line along a subset of the full path points from startPointIdx to endPointIdx 650 SOverlayTexturedLine overlayLine; 651 overlayLine.m_Thickness = m_LineThickness; 652 overlayLine.m_Terrain = cmpTerrain->GetCTerrain(); 653 overlayLine.m_TextureBase = m_Texture; 654 overlayLine.m_TextureMask = m_TextureMask; 655 overlayLine.m_Color = m_LineColor; 656 overlayLine.m_Closed = false; 657 // we should take care to only use m_LineXCap for the actual end points at the building and the rally point; any intermediate 658 // end points (i.e., that border a dashed segment) should have the dashed cap 659 // the path line is actually in reverse order as well, so let's swap out the start and end caps 660 overlayLine.m_StartCap = (bordersFlag ? m_LineEndCap : dashesLineCap); 661 overlayLine.m_EndCap = (bordersBuilding ? m_LineStartCap : dashesLineCap); 662 overlayLine.m_AlwaysVisible = true; 663 664 // push overlay line coordinates 665 ENSURE(segment.m_EndIndex > segment.m_StartIndex); 666 for (size_t j = segment.m_StartIndex; j <= segment.m_EndIndex; ++j) // end index is inclusive here 667 { 668 overlayLine.m_Coords.push_back(m_FullPath[j].X); 669 overlayLine.m_Coords.push_back(m_FullPath[j].Y); 670 } 671 672 m_TexturedOverlayLines.push_back(overlayLine); 673 } 674 else 675 { 676 // construct dashed line from startPointIdx to endPointIdx, add textured overlay lines for it to the render list 677 std::vector<CVector2D> straightLine; 678 straightLine.push_back(m_FullPath[segment.m_StartIndex]); 679 straightLine.push_back(m_FullPath[segment.m_EndIndex]); 680 681 // 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 682 // area is visually obvious. That implies that we want at least So, let's do some calculations to see what size we should make 683 // the dashes and clears. 684 685 float maxDashSize = 3.f; 686 float maxClearSize = 3.f; 687 688 float dashSize = maxDashSize; 689 float clearSize = maxClearSize; 690 float pairDashRatio = (dashSize / (dashSize + clearSize)); // ratio of the dash's length to a (dash + clear) pair's length 691 692 float distance = (m_FullPath[segment.m_StartIndex] - m_FullPath[segment.m_EndIndex]).Length(); // straight-line distance between the points 693 694 // see how many pairs (dash + clear) can fit into the distance unmodified. Then check the remaining distance; if it's not exactly 695 // a dash size's worth (and it likely won't be), then adjust the dash/clear sizes slightly so that it is. 696 int numFitUnmodified = floor(distance/(dashSize + clearSize)); 697 float remainderDistance = distance - (numFitUnmodified * (dashSize + clearSize)); 698 699 // Now we want to make remainderDistance equal exactly one dash size (i.e. maxDashSize) by scaling dashSize and clearSize slightly. 700 // We have (remainderDistance - maxDashSize) of space to distribute over numFitUnmodified instances of (dashSize + clearSize) to make 701 // it fit, so each (dashSize + clearSize) pair needs to adjust its length by (remainderDistance - maxDashSize)/numFitUnmodified 702 // (which will be positive or negative accordingly). This number can then be distributed further proportionally among the dash's 703 // length and the clear's length. 704 705 // we always want to have at least one dash/clear pair (i.e., "|===| |===|"); also, we need to avoid division by zero below. 706 numFitUnmodified = std::max(1, numFitUnmodified); 707 708 float pairwiseLengthDifference = (remainderDistance - maxDashSize)/numFitUnmodified; // can be either positive or negative 709 dashSize += pairDashRatio * pairwiseLengthDifference; 710 clearSize += (1 - pairDashRatio) * pairwiseLengthDifference; 711 712 // ------------------------------------------------------------------------------------------------ 713 714 SDashedLine dashedLine; 715 SimRender::ConstructDashedLine(straightLine, dashedLine, dashSize, clearSize); 716 717 // build overlay lines for dashes 718 size_t numDashes = dashedLine.m_StartIndices.size(); 719 for (size_t i=0; i < numDashes; i++) 720 { 721 SOverlayTexturedLine dashOverlay; 722 723 dashOverlay.m_Thickness = m_LineThickness; 724 dashOverlay.m_Terrain = cmpTerrain->GetCTerrain(); 725 dashOverlay.m_TextureBase = m_Texture; 726 dashOverlay.m_TextureMask = m_TextureMask; 727 dashOverlay.m_Color = m_LineDashColor; 728 dashOverlay.m_Closed = false; 729 dashOverlay.m_StartCap = dashesLineCap; 730 dashOverlay.m_EndCap = dashesLineCap; 731 dashOverlay.m_AlwaysVisible = true; 732 // TODO: maybe adjust the elevation of the dashes to be a little lower, so that it slides underneath the actual path 733 734 size_t dashStartIndex = dashedLine.m_StartIndices[i]; 735 size_t dashEndIndex = dashedLine.GetEndIndex(i); 736 ENSURE(dashEndIndex > dashStartIndex); 737 738 for (size_t n = dashStartIndex; n < dashEndIndex; n++) 739 { 740 dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].X); 741 dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].Y); 742 } 743 744 m_TexturedOverlayLines.push_back(dashOverlay); 745 } 746 747 } 748 } 749 750 //// <DEBUG> ////////////////////////////////////////////// 751 if (m_EnableDebugNodeOverlay) 752 { 753 for (size_t j = 0; j < m_FullPath.size(); ++j) 754 { 755 SOverlayLine overlayLine; 756 overlayLine.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f); 757 overlayLine.m_Thickness = 1; 758 SimRender::ConstructCircleOnGround(GetSimContext(), m_FullPath[j].X, m_FullPath[j].Y, 0.075f, overlayLine, true); 759 m_DebugNodeOverlays.push_back(overlayLine); 760 } 761 } 762 //// </DEBUG> ////////////////////////////////////////////// 763 } 764 765 void CCmpRallyPoint::UpdateOverlayLines() 766 { 767 // we should really only do this if the rally point is currently being displayed and set inside the world, otherwise it's a massive 768 // waste of time to calculate all this stuff every turn if there's nothing to show for it anyway 769 770 if (!m_Displayed || !IsSet()) 771 return; 772 773 // see if there have been any changes to the SoD by grabbing the visibility edge points and comparing them to the previous ones 774 std::deque<SVisibilitySegment> newVisibilitySegments; 775 GetVisibilitySegments(newVisibilitySegments); 776 777 // compare the two indices vectors; as soon as an element is different (and provided the full path hasn't changed), then the SoD 778 // has changed and we should recreate the overlay lines 779 bool same = (m_VisibilitySegments == newVisibilitySegments); 780 if (!same) 781 { 782 // the visibility segments have changed, so we want to reconstruct the overlay lines to match. Note that the path itself doesn't 783 // change, only the overlay lines we construct from them. 784 m_VisibilitySegments = newVisibilitySegments; // save the new visibility segments to compare against next time 785 ConstructOverlayLines(); 786 } 787 788 } 789 790 void CCmpRallyPoint::FixFootprintWaypoints(std::vector<CVector2D>& coords, CmpPtr<ICmpPosition> cmpPosition, CmpPtr<ICmpFootprint> cmpFootprint) 791 { 792 ENSURE(!cmpPosition.null()); 793 ENSURE(!cmpFootprint.null()); 794 795 // ----------------------------------------------------------------------------------------------------- 796 // TODO: nasty fixed/float conversions everywhere 797 798 // grab the shape and dimensions of the footprint 799 entity_pos_t footprintSize0, footprintSize1, footprintHeight; 800 ICmpFootprint::EShape footprintShape; 801 cmpFootprint->GetShape(footprintShape, footprintSize0, footprintSize1, footprintHeight); 802 803 // grab the center of the footprint 804 CFixedVector2D center = cmpPosition->GetPosition2D(); 805 806 // ----------------------------------------------------------------------------------------------------- 807 808 switch (footprintShape) 809 { 810 case ICmpFootprint::SQUARE: 811 { 812 // in this case, footprintSize0 and 1 respectively indicate the (unrotated) size along the X and Z axes 813 814 // the building's footprint could be rotated any which way, so let's get the rotation around the Y axis 815 // and the rotated unit vectors in the X/Z plane of the shape's footprint 816 // (the Footprint itself holds only the outline, the Position holds the orientation) 817 818 fixed s, c; // sine and cosine of the Y axis rotation angle (aka the yaw) 819 fixed a = cmpPosition->GetRotation().Y; 820 sincos_approx(a, s, c); 821 CFixedVector2D u(c, -s); // unit vector along the rotated X axis 822 CFixedVector2D v(s, c); // unit vector along the rotated Z axis 823 CFixedVector2D halfSize(footprintSize0/2, footprintSize1/2); 824 825 // starting from the start position, check if any points are within the footprint of the building 826 // (this is possible if the pathfinder was started from a point located within the footprint) 827 for(int i = (int)(coords.size() - 1); i >= 0; i--) 828 { 829 const CVector2D& wp = coords[i]; 830 if (Geometry::PointIsInSquare(CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center, u, v, halfSize)) 831 { 832 coords.erase(coords.begin() + i); 833 } 834 else 835 { 836 break; // point no longer inside footprint, from this point on neither will any of the following be 837 } 838 } 839 840 // 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 841 CFixedVector2D lastWaypoint(fixed::FromFloat(coords.back().X), fixed::FromFloat(coords.back().Y)); 842 CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(lastWaypoint - center, u, v, halfSize); // relative to the shape origin (center) 843 CVector2D footprintEdge((center.X + footprintEdgePoint.X).ToFloat(), (center.Y + footprintEdgePoint.Y).ToFloat()); 844 coords.push_back(footprintEdge); 845 846 } 847 break; 848 case ICmpFootprint::CIRCLE: 849 { 850 // in this case, both footprintSize0 and 1 indicate the circle's radius 851 852 for(int i = (int)(coords.size() - 1); i >= 0; i--) 853 { 854 const CVector2D& wp = coords[i]; 855 fixed pointDistance = (CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center).Length(); 856 if (pointDistance <= footprintSize0) 857 { 858 coords.erase(coords.begin() + i); 859 } 860 else 861 { 862 break; // point no longer inside footprint, from this point on neither will any of the following be 863 } 864 } 865 866 // add a point right on the edge of the footprint so that it links up nicely with the rest of the path 867 CFixedVector2D radiusEdgePoint(fixed::FromFloat(coords.back().X), fixed::FromFloat(coords.back().Y)); 868 radiusEdgePoint.Normalize(footprintSize1); 869 CVector2D footprintEdge((center.X + radiusEdgePoint.X).ToFloat(), (center.Y + radiusEdgePoint.Y).ToFloat()); 870 coords.push_back(footprintEdge); 871 872 } 873 break; 874 } 875 876 } 877 878 void CCmpRallyPoint::FixInvisibleWaypoints(std::vector<CVector2D>& coords) 879 { 880 CmpPtr<ICmpRangeManager> cmpRangeMgr(GetSimContext(), SYSTEM_ENTITY); 881 882 player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer(); 883 CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer)); 884 885 //for (std::vector<Waypoint>::iterator it = waypoints.begin(); it != waypoints.end();) 886 for(std::vector<CVector2D>::iterator it = coords.begin(); it != coords.end();) 887 { 888 int i = (fixed::FromFloat(it->X) / (int)CELL_SIZE).ToInt_RoundToNearest(); 889 int j = (fixed::FromFloat(it->Y) / (int)CELL_SIZE).ToInt_RoundToNearest(); 890 891 bool explored = losQuerier.IsExplored(i, j); 892 if (!explored) 893 { 894 it = coords.erase(it); 895 } 896 else 897 { 898 it++; 899 } 900 } 901 902 } 903 904 void CCmpRallyPoint::ReduceSegmentsByVisibility(std::vector<CVector2D>& coords, unsigned maxSegmentLinks) 905 { 906 907 CmpPtr<ICmpPathfinder> cmpPathFinder(GetSimContext(), SYSTEM_ENTITY); 908 CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); 909 910 if (coords.size() < 3) 911 return; 912 913 // 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 914 // 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 915 // process from there on until the entire line is checked. The output is the array of base nodes. 916 917 std::vector<CVector2D> newCoords; 918 StationaryObstructionFilter obstructionFilter; // TODO: is this the right one to use? 919 entity_pos_t lineRadius = fixed::FromFloat(m_LineThickness); 920 ICmpPathfinder::pass_class_t passabilityClass = cmpPathFinder->GetPassabilityClass("default"); 921 922 newCoords.push_back(coords[0]); // save the first base node 923 924 size_t baseNodeIdx = 0; 925 size_t curNodeIdx = 1; 926 927 // kind of ugly, but prevents code duplication below 928 #define UPDATE_BASENODE_XYZ() STMT(baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);\ 929 baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);\ 930 baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);) 931 932 float baseNodeY; 933 entity_pos_t baseNodeX; 934 entity_pos_t baseNodeZ; 935 936 UPDATE_BASENODE_XYZ(); // set initial base node coords 937 938 while (curNodeIdx < coords.size()) 939 { 940 ENSURE(curNodeIdx > baseNodeIdx); // this needs to be true at all times, otherwise we're checking visibility between a point and itself 941 942 entity_pos_t curNodeX = fixed::FromFloat(coords[curNodeIdx].X); 943 entity_pos_t curNodeZ = fixed::FromFloat(coords[curNodeIdx].Y); 944 float curNodeY = cmpTerrain->GetExactGroundLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y); 945 946 // find out whether curNode is visible from baseNode (careful; this is in 2D only; terrain height differences are ignored!) 947 bool curNodeVisible = cmpPathFinder->CheckMovement(obstructionFilter, baseNodeX, baseNodeZ, curNodeX, curNodeZ, lineRadius, passabilityClass); 948 949 // since height differences are ignored by CheckMovement, let's call two points visible from one another only if they're at roughly the same terrain elevation 950 curNodeVisible = curNodeVisible && fabsf(curNodeY - baseNodeY) < 3.f; // TODO: this could probably use some tuning 951 if (maxSegmentLinks > 0) 952 curNodeVisible = curNodeVisible && ((curNodeIdx - baseNodeIdx) <= maxSegmentLinks); // max. amount of node-to-node links to be eliminated (unsigned subtraction is valid because curNodeIdx is always > baseNodeIdx) 953 954 if (!curNodeVisible) 955 { 956 // current node is not visible from the base node, so the previous one was the last visible point from baseNode and should 957 // hence become the new base node for further iterations. 958 959 // if curNodeIdx is adjacent to the current baseNode (which is possible due to steep height differences, e.g. hills), then 960 // we should take care not to stay stuck at the current base node 961 if (curNodeIdx > baseNodeIdx + 1) 962 { 963 baseNodeIdx = curNodeIdx - 1; 964 } 965 else 966 { 967 // curNodeIdx == baseNodeIdx + 1 968 baseNodeIdx = curNodeIdx; 969 curNodeIdx++; // move the next candidate node one forward so that we don't test a point against itself in the next iteration 970 } 971 972 newCoords.push_back(coords[baseNodeIdx]); // add new base node to output list 973 974 UPDATE_BASENODE_XYZ(); 975 976 } 977 978 curNodeIdx++; 979 980 } 981 982 #undef UPDATE_BASENODE_XYZ 983 984 // 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 985 // base node, then the loop above just ends and no endpoint is ever added to the list. 986 ENSURE(curNodeIdx == coords.size()); 987 newCoords.push_back(coords[coords.size() - 1]); 988 989 coords.swap(newCoords); 990 991 } 992 993 void CCmpRallyPoint::GetVisibilitySegments(std::deque<SVisibilitySegment>& out) 994 { 995 996 out.clear(); 997 998 if (m_FullPath.size() < 2) 999 return; 1000 1001 CmpPtr<ICmpRangeManager> cmpRangeMgr(GetSimContext(), SYSTEM_ENTITY); 1002 1003 player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer(); 1004 CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer)); 1005 1006 // go through the path node list, comparing each node's visibility with the previous one. If it changes, end the current segment and start 1007 // a new one at the next point. 1008 1009 bool lastVisible = losQuerier.IsExplored( 1010 (fixed::FromFloat(m_FullPath[0].X) / (int) CELL_SIZE).ToInt_RoundToNearest(), 1011 (fixed::FromFloat(m_FullPath[0].Y) / (int) CELL_SIZE).ToInt_RoundToNearest() 1012 ); 1013 size_t curSegmentStartIndex = 0; // starting node index of the current segment 1014 1015 for (size_t k = 1; k < m_FullPath.size(); ++k) 1016 { 1017 // grab tile indices for this coord 1018 int i = (fixed::FromFloat(m_FullPath[k].X) / (int)CELL_SIZE).ToInt_RoundToNearest(); 1019 int j = (fixed::FromFloat(m_FullPath[k].Y) / (int)CELL_SIZE).ToInt_RoundToNearest(); 1020 1021 bool nodeVisible = losQuerier.IsExplored(i, j); 1022 if (nodeVisible != lastVisible) 1023 { 1024 // visibility changed; write out the segment that was just completed and get ready for the new one 1025 out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, k - 1)); 1026 1027 //curSegmentStartIndex = k; // new segment starts here 1028 curSegmentStartIndex = k - 1; 1029 lastVisible = nodeVisible; 1030 } 1031 1032 } 1033 1034 // terminate the last segment 1035 out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, m_FullPath.size() - 1)); 1036 1037 // --------------------------------------------------------------------------------------------------- 1038 MergeVisibilitySegments(out); 1039 1040 } 1041 1042 void CCmpRallyPoint::MergeVisibilitySegments(std::deque<SVisibilitySegment>& segments) 1043 { 1044 // scan for single-point segments; if they are inbetween two other segments, delete it and merge the surrounding segments; 1045 // if they're at either end of the path, include them in their bordering segment (but only if those bordering segments aren't 1046 // themselves single-point segments, because then we would want those to get absorbed by its surrounding ones first). 1047 1048 // first scan for absorptions of single-point surrounded segments (i.e. excluding edge segments) 1049 size_t numSegments = segments.size(); 1050 1051 // YE BE WARNED: HERE BE FOR LOOP TRICKERY 1052 for (size_t i = 1; i < numSegments - 1;) 1053 { 1054 SVisibilitySegment& segment = segments[i]; 1055 if (segment.IsSinglePoint()) 1056 { 1057 ENSURE(segments[i-1].m_Visible == segments[i+1].m_Visible); // since the segments' visibility alternates, the surrounding ones should have the same visibility 1058 segments[i-1].m_EndIndex = segments[i+1].m_EndIndex; // make previous segment span all the way across to the next 1059 segments.erase(segments.begin() + i); // erase this segment ... 1060 segments.erase(segments.begin() + i); // and the next (we removed [i], so [i+1] is now at position [i]) 1061 numSegments -= 2; // we removed 2 segments, so update the loop condition 1062 // in the next iteration, i should still point to the segment right after the one that got expanded, which is now 1063 // at position i; so don't increment i here 1064 } 1065 else 1066 { 1067 ++i; 1068 } 1069 } 1070 1071 ENSURE(numSegments == segments.size()); 1072 1073 // check to see if the first segment needs to be merged with its neighbour 1074 if (segments.size() >= 2 && segments[0].IsSinglePoint()) 1075 { 1076 int firstSegmentStartIndex = segments.front().m_StartIndex; 1077 ENSURE(firstSegmentStartIndex == 0); 1078 ENSURE(!segments[1].IsSinglePoint()); // at this point, the second segment should never be a single-point segment 1079 1080 segments.erase(segments.begin()); 1081 segments.front().m_StartIndex = firstSegmentStartIndex; 1082 1083 } 1084 1085 // check to see if the last segment needs to be merged with its neighbour 1086 if (segments.size() >= 2 && segments[segments.size()-1].IsSinglePoint()) 1087 { 1088 int lastSegmentEndIndex = segments.back().m_EndIndex; 1089 ENSURE(!segments[segments.size()-2].IsSinglePoint()); // at this point, the second-to-last segment should never be a single-point segment 1090 1091 segments.erase(segments.end()); 1092 segments.back().m_EndIndex = lastSegmentEndIndex; 1093 } 1094 1095 // -------------------------------------------------------------------------------------------------------- 1096 // at this point, every segment should have at least 2 points 1097 for (size_t i = 0; i < segments.size(); ++i) 1098 { 1099 ENSURE(!segments[i].IsSinglePoint()); 1100 ENSURE(segments[i].m_EndIndex > segments[i].m_StartIndex); 1101 } 1102 1103 } 1104 1105 void CCmpRallyPoint::RenderSubmit(SceneCollector& collector) 1106 { 1107 // we only get here if the rally point is set and should be displayed 1108 for (size_t i = 0; i < m_TexturedOverlayLines.size(); ++i) 1109 { 1110 if (!m_TexturedOverlayLines[i].m_Coords.empty()) 1111 collector.Submit(&m_TexturedOverlayLines[i]); 1112 } 1113 1114 if (m_EnableDebugNodeOverlay && !m_DebugNodeOverlays.empty()) 1115 { 1116 for (size_t i = 0; i < m_DebugNodeOverlays.size(); i++) 1117 collector.Submit(&m_DebugNodeOverlays[i]); 1118 } 1119 } -
source/simulation2/components/CCmpTerritoryManager.cpp
diff --git a/source/simulation2/components/CCmpTerritoryManager.cpp b/source/simulation2/components/CCmpTerritoryManager.cpp index 2a87eba..8f06d5a 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 SOverlayLine overlayNode; 807 if (j > boundaries[i].points.size() - 1 - 7) 808 overlayNode.m_Color = CColor(1.f, 0.f, 0.f, 1.f); 809 else if (j < 7) 810 overlayNode.m_Color = CColor(0.f, 1.f, 0.f, 1.f); 811 else 812 overlayNode.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f); 813 814 overlayNode.m_Thickness = 1; 815 SimRender::ConstructCircleOnGround(GetSimContext(), boundaries[i].points[j].X, boundaries[i].points[j].Y, 0.1f, overlayNode, true); 816 m_DebugBoundaryLineNodes.push_back(overlayNode); 817 } 799 818 } 819 800 820 } 801 821 } 802 822 … … void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector) 825 845 { 826 846 for (size_t i = 0; i < m_BoundaryLines.size(); ++i) 827 847 collector.Submit(&m_BoundaryLines[i].overlay); 848 849 for( size_t i = 0; i < m_DebugBoundaryLineNodes.size(); ++i) 850 collector.Submit(&m_DebugBoundaryLineNodes[i]); 851 828 852 } 829 853 830 854 player_id_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z) -
new file source/simulation2/components/ICmpRallyPoint.cpp
diff --git a/source/simulation2/components/ICmpRallyPoint.cpp b/source/simulation2/components/ICmpRallyPoint.cpp new file mode 100644 index 0000000..dee3f27
- + 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 "ICmpRallyPoint.h" 21 #include "simulation2/system/InterfaceScripted.h" 22 23 BEGIN_INTERFACE_WRAPPER(RallyPoint) 24 DEFINE_INTERFACE_METHOD_0("GetPosition", CFixedVector2D, ICmpRallyPoint, GetPosition) 25 DEFINE_INTERFACE_METHOD_1("SetPosition", void, ICmpRallyPoint, SetPosition, CFixedVector2D) 26 DEFINE_INTERFACE_METHOD_0("IsSet", bool, ICmpRallyPoint, IsSet) 27 DEFINE_INTERFACE_METHOD_0("Unset", void, ICmpRallyPoint, Unset) 28 DEFINE_INTERFACE_METHOD_1("SetDisplayed", void, ICmpRallyPoint, SetDisplayed, bool) 29 //DEFINE_INTERFACE_METHOD_0("GetMarkerEntityId", entity_id_t, ICmpRallyPoint, GetMarkerEntityId) 30 END_INTERFACE_WRAPPER(RallyPoint) -
new file source/simulation2/components/ICmpRallyPoint.h
diff --git a/source/simulation2/components/ICmpRallyPoint.h b/source/simulation2/components/ICmpRallyPoint.h new file mode 100644 index 0000000..3e019c6
- + 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 "simulation2/system/Interface.h" 22 #include "simulation2/helpers/Position.h" 23 24 #include "maths/FixedVector2D.h" 25 26 /** 27 * Rally Point. 28 * Holds the position of a unit's rally point, and renders it to screen. 29 */ 30 class ICmpRallyPoint : public IComponent 31 { 32 public: 33 34 virtual CFixedVector2D GetPosition() = 0; ///< Returns the position of the rally point 35 virtual void SetPosition(CFixedVector2D pos) = 0; ///< Set the position of the rally point 36 virtual void SetDisplayed(bool displayed) = 0; ///< Set whether the rally point marker and line should be displayed 37 virtual void Unset() = 0; ///< Unsets the current rally point position 38 virtual bool IsSet() = 0; ///< Is the rally point position currently set? 39 40 DECLARE_INTERFACE_TYPE(RallyPoint) 41 }; 42 43 #endif // INCLUDED_ICMPRALLYPOINT -
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 db63115..44675e8 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 201 201 return p + CVector2D(dp.Y*-offset, dp.X*offset); 202 202 } 203 203 204 void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset )204 void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset, int segmentSamples) 205 205 { 206 206 PROFILE("InterpolatePointsRNS"); 207 ENSURE(segmentSamples > 0); 207 208 208 209 std::vector<CVector2D> newPoints; 210 const float fSegmentSamples = (float) segmentSamples; 209 211 210 212 // (This does some redundant computations for adjacent vertices, 211 213 // but it's fairly fast (<1ms typically) so we don't worry about it yet) … … void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed 215 217 // curve with fewer points 216 218 217 219 size_t n = points.size(); 220 221 if (closed) 222 { 218 223 if (n < 1) 219 return; // can't do anything unless we have two points 224 return; // we need at least a single point to not crash 225 } 226 else 227 { 228 if (n < 2) 229 return; // in non-closed mode, we need at least n=2 to not crash 230 } 220 231 221 size_t imax = closed ? n : n-1; // TODO: we probably need to do a bit more to handle non-closed paths 232 size_t imax = closed ? n : n-1; 233 newPoints.reserve(imax*segmentSamples); 222 234 223 newPoints.reserve(imax*4); 235 // these are primarily used inside the loop, but for open paths we need them outside the loop once to compute the last point 236 CVector2D a0; 237 CVector2D a1; 238 CVector2D a2; 239 CVector2D a3; 224 240 225 241 for (size_t i = 0; i < imax; ++i) 226 242 { 227 // Get the relevant points for this spline segment 228 CVector2D p0 = points[(i-1+n)%n]; 243 244 // Get the relevant points for this spline segment; each step interpolates the segment between p1 and p2; p0 and p3 are the points 245 // before p1 and after p2, respectively; they're needed to compute tangents and whatnot. 246 CVector2D p0; // normally points[(i-1+n)%n], but it's a bit more complicated due to open/closed paths -- see below 229 247 CVector2D p1 = points[i]; 230 248 CVector2D p2 = points[(i+1)%n]; 231 CVector2D p3 = points[(i+2)%n]; 249 CVector2D p3; // normally points[(i+2)%n], but it's a bit more complicated due to open/closed paths -- see below 250 251 if (!closed && (i == 0)) 252 // 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 253 // that extends p1 -> p0 (i.e. the first segment's direction) 254 p0 = points[0] + (points[0] - points[1]); 255 else 256 // standard wrap-around case 257 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) 258 259 260 if (!closed && (i == n-2)) 261 // 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) 262 // (note that p2's index should not be out of bounds, because in non-closed mode imax is reduced by 1) 263 p3 = points[n-1] + (points[n-1] - points[n-2]); 264 else 265 // standard wrap-around case 266 p3 = points[(i+2)%n]; 267 232 268 233 269 // Do the RNS computation (based on GPG4 "Nonuniform Splines") 234 270 float l1 = (p2 - p1).Length(); // length of spline segment (i)..(i+1) … … void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed 239 275 CVector2D v2 = (s1 + s2).Normalized() * l1; // spline velocity at i+1 240 276 241 277 // Compute standard cubic spline parameters 242 CVector2D a0 = p1*2 + p2*-2 + v1 + v2; 243 CVector2D a1 = p1*-3 + p2*3 + v1*-2 + v2*-1; 244 CVector2D a2 = v1; 245 CVector2D a3 = p1; 278 a0 = p1*2 + p2*-2 + v1 + v2; 279 a1 = p1*-3 + p2*3 + v1*-2 + v2*-1; 280 a2 = v1; 281 a3 = p1; 282 283 // Interpolate at regular points across the interval 284 for (int sample = 0; sample < segmentSamples; sample++) 285 newPoints.push_back(EvaluateSpline(sample/fSegmentSamples, a0, a1, a2, a3, offset)); 246 286 247 // Interpolate at various points248 newPoints.push_back(EvaluateSpline(0.f, a0, a1, a2, a3, offset));249 newPoints.push_back(EvaluateSpline(1.f/4.f, a0, a1, a2, a3, offset));250 newPoints.push_back(EvaluateSpline(2.f/4.f, a0, a1, a2, a3, offset));251 newPoints.push_back(EvaluateSpline(3.f/4.f, a0, a1, a2, a3, offset));252 287 } 253 288 289 if (!closed) 290 // if the path is open, we should take care to include the last control point 291 // NOTE: we can't just do push_back(points[n-1]) here because that ignores the offset 292 newPoints.push_back(EvaluateSpline(1.f, a0, a1, a2, a3, offset)); 293 254 294 points.swap(newPoints); 255 295 } 296 297 void SimRender::ConstructDashedLine(const std::vector<CVector2D>& keyPoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength) 298 { 299 // sanity checks 300 if (dashLength <= 0) 301 return; 302 303 if (blankLength <= 0) 304 return; 305 306 if (keyPoints.size() < 2) 307 return; 308 309 dashedLineOut.m_Points.clear(); 310 dashedLineOut.m_StartIndices.clear(); 311 312 // walk the line, counting the total length so far at each node point. When the length exceeds dashLength, cut the last segment at the 313 // required length and continue for blankLength along the line to start a new dash segment. 314 315 // TODO: we should probably extend this function to also allow for closed lines. I was thinking of slightly scaling the dash/blank length 316 // 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 317 // (O(n) and lots of square roots). 318 319 bool buildingDash = true; // true if we're building a dash, false if a blank 320 float curDashLength = 0; // builds up the current dash/blank's length as we walk through the line nodes 321 CVector2D dashLastPoint = keyPoints[0]; // last point of the current dash/blank being built. 322 323 // register the first starting node of the first dash 324 dashedLineOut.m_Points.push_back(keyPoints[0]); 325 dashedLineOut.m_StartIndices.push_back(0); 326 327 // 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 328 // properly take a direction vector along the path. 329 size_t i = 0; 330 331 while(i < keyPoints.size() - 1) 332 { 333 // get length of this segment 334 CVector2D segmentVector = keyPoints[i + 1] - dashLastPoint; // vector from our current point along the path to nextNode 335 float segmentLength = segmentVector.Length(); 336 337 float targetLength = (buildingDash ? dashLength : blankLength); 338 if (curDashLength + segmentLength > targetLength) 339 { 340 // 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 341 // line that is of just the required length to complete the dash, then make it the base point for the next dash/blank. 342 float cutLength = targetLength - curDashLength; 343 CVector2D cutPoint = dashLastPoint + (segmentVector.Normalized() * cutLength); 344 345 // start a new dash or blank in the next iteration 346 curDashLength = 0; 347 buildingDash = !buildingDash; // flip from dash to blank and vice-versa 348 dashLastPoint = cutPoint; 349 350 // don't increment i, we haven't fully traversed this segment yet so we still need to use the same point to take the 351 // direction vector with in the next iteration 352 353 // 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 354 // in the points array. 355 dashedLineOut.m_Points.push_back(cutPoint); 356 357 if (buildingDash) 358 { 359 // 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 360 // index as a start index of a dash. 361 dashedLineOut.m_StartIndices.push_back(dashedLineOut.m_Points.size() - 1); 362 } 363 364 } 365 else 366 { 367 // the segment from lastDashPoint to keyPoints[i+1] doesn't suffice to complete the dash, so we need to add keyPoints[i+1] 368 // to this dash's points and continue from there 369 370 if (buildingDash) 371 // still building the dash, add it to the output (we don't need to store the blanks) 372 dashedLineOut.m_Points.push_back(keyPoints[i+1]); 373 374 curDashLength += segmentLength; 375 dashLastPoint = keyPoints[i+1]; 376 i++; 377 378 } 379 380 } 381 382 } -
source/simulation2/helpers/Render.h
diff --git a/source/simulation2/helpers/Render.h b/source/simulation2/helpers/Render.h index 0129b01..b991ed9 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 struct SOverlayLine; 29 30 31 32 33 struct SDashedLine 34 { 35 std::vector<CVector2D> m_Points; ///< Packed array of consecutive dashes' points. Use m_StartIndices to navigate it. 36 37 /** 38 * Start indices in m_Points of each dash. Dash n starts at point m_StartIndices[n] and ends at the point with index 39 * m_StartIndices[n+1] - 1, or at the end of the m_Points vector. Use the GetEndIndex(n) convenience method to abstract away the 40 * difference and get the (exclusive) end index of dash n. 41 */ 42 std::vector<size_t> m_StartIndices; 43 44 /// Returns the (exclusive) end point index (i.e. index within m_Points) of dash n. 45 size_t GetEndIndex(size_t i) 46 { 47 // for the last dash, there is no next starting index, so we need to use the end index of the m_Points array instead 48 return (i < m_StartIndices.size() - 1 ? m_StartIndices[i+1] : m_Points.size()); 49 } 50 }; 51 30 52 namespace SimRender 31 53 { 32 54 … … void SmoothPointsAverage(std::vector<CVector2D>& points, bool closed); 68 90 * the direction of the curve. 69 91 * If @p closed then the points are treated as a closed path (the last is connected 70 92 * to the first). 93 * @param segmentSamples Amount of intermediate points to sample between every two control points. 94 */ 95 void InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset, int segmentSamples = 4); 96 97 /** 98 * Creates a dashed line from the line specified by @points so that each dash is of length 99 * @p dashLength, and each blank inbetween is of length @p blankLength. The dashed line returned as a list of smaller lines 100 * in @p dashedLineOut. 101 * 102 * @param dashLength Length of a single dash. Must be strictly positive. 103 * @param blankLength Length of a single blank between dashes. Must be strictly positive. 71 104 */ 72 void InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset);105 void ConstructDashedLine(const std::vector<CVector2D>& linePoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength); 73 106 74 107 } // namespace 75 108