hexMap.py (17960B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\maps\hexMap.py 2 import math 3 import localization 4 import trinity 5 from eve.client.script.ui.shared.maps.mapcommon import LegendItem, COLOR_STANDINGS_BAD, COLOR_STANDINGS_GOOD, COLOR_STANDINGS_NEUTRAL 6 Y_SCALE = 0.8660254037844386 7 MAP_HEIGHT = -100.0 8 TIED_TILE_COLOR = (0.4, 9 0.4, 10 0.4, 11 1.0) 12 UNCLAIMED_TILE_COLOR = (0.1, 13 0.1, 14 0.1, 15 1.0) 16 17 class HexMapController(object): 18 19 def __init__(self, rootTransform, solarSystemGenerator): 20 self.rootTransform = rootTransform 21 self.baseTile = trinity.Load('res:/dx9/model/UI/HexTile.red') 22 self.tilePool = [] 23 self.layoutCache = {} 24 self.systemInfoMap = {} 25 self.aabb = AABB() 26 systems = [] 27 self.mapHeight = 0.0 28 for systemID, system in solarSystemGenerator: 29 scaledSystemPos = system.scaledCenter 30 factionID = system.factionID 31 systemInfo = HexSystemInfo(systemID, factionID, scaledSystemPos[0], scaledSystemPos[2]) 32 self.systemInfoMap[systemID] = systemInfo 33 systems.append(systemInfo) 34 self.aabb.IncludePoint(systemInfo.point) 35 self.mapHeight = max(self.mapHeight, scaledSystemPos[1]) 36 37 self.systemQuadTree = QuadTree(systems, depth=6, aabb=self.aabb) 38 self.centerPoint = self.systemQuadTree.center 39 self.mapHeight *= 1.1 40 self.legend = [] 41 self.CalculateHexagonParameters() 42 self.nextTileNum = 0 43 self.firstUnusedTileNum = None 44 self.SetupScene() 45 46 def __del__(self): 47 self.highlightBinding = None 48 del self.rootTransform.curveSets[:] 49 self.rootTransform = None 50 51 def SetupScene(self): 52 curveSet = trinity.TriCurveSet() 53 curveSet.name = 'HighlighCurveSet' 54 curveSet.playOnLoad = False 55 curveSet.Stop() 56 curveSet.scaledTime = 0.0 57 self.rootTransform.curveSets.append(curveSet) 58 curve = trinity.Tr2CurveScalar() 59 curve.extrapolationAfter = trinity.Tr2CurveExtrapolation.CYCLE 60 curveSet.curves.append(curve) 61 curve.AddKey(0.0, 0.0) 62 curve.AddKey(1.0, 1.0) 63 curve.AddKey(2.0, 0.0) 64 self.highlightCurveSet = curveSet 65 self.highlightCurve = curve 66 curveSet.Play() 67 68 def CalculateHexagonParameters(self): 69 mmin = trinity.TriVector(-1.0, 0.0, -0.866025447845459) 70 mmax = trinity.TriVector(1.0, 0.0, 0.866025447845459) 71 maxx = mmax.z / math.sin(math.radians(60.0)) 72 topRightX = math.cos(math.radians(60.0)) * maxx 73 self.tileMin = Point(-maxx, mmin.z) 74 self.tileMax = Point(maxx, mmax.z) 75 self.hexPoints = (Point(topRightX, self.tileMax.y), 76 Point(self.tileMax.x, 0.0), 77 Point(topRightX, self.tileMin.y), 78 Point(-topRightX, self.tileMin.y), 79 Point(self.tileMin.x, 0.0), 80 Point(-topRightX, self.tileMax.y)) 81 self.tileWidth = maxx * 2.0 * 0.75 82 self.tileHeight = mmax.z * 2.0 83 84 def GetNextGeom(self): 85 if len(self.tilePool) > self.nextTileNum: 86 nextTile = self.tilePool[self.nextTileNum] 87 else: 88 nextTile = HexGeom(self) 89 self.tilePool.append(nextTile) 90 self.nextTileNum += 1 91 return nextTile 92 93 def GetAdaptedTileAABB(self, tile): 94 v = Point(tile.center[0], tile.center[2]) 95 return AABB(self.tileMin * tile.scale + v, self.tileMax * tile.scale + v) 96 97 def GetAdaptedHexPoints(self, tile): 98 newPoints = [] 99 pos = Point(tile.center[0], tile.center[2]) 100 for point in self.hexPoints: 101 newPoint = point * tile.scale + pos 102 newPoints.append(newPoint) 103 104 return newPoints 105 106 def AssignSystemsToTile(self, tile): 107 aabb = self.GetAdaptedTileAABB(tile) 108 systemInfos = self.systemQuadTree.Hit(aabb) 109 if len(systemInfos) > 0: 110 hexVertices = self.GetAdaptedHexPoints(tile) 111 for info in systemInfos: 112 if self.PointWithinTile(info.point, hexVertices): 113 tile.systems[info.systemID] = info 114 115 def PointWithinTile(self, point, vertices): 116 numPoint = len(vertices) 117 for i in xrange(numPoint): 118 p0 = vertices[i] 119 p1 = vertices[(i + 1) % numPoint] 120 test = (point.y - p0.y) * (p1.x - p0.x) - (point.x - p0.x) * (p1.y - p0.y) 121 if test > 0: 122 return False 123 124 return True 125 126 def GetLayout(self, sizeX): 127 if sizeX not in self.layoutCache: 128 layout = [] 129 startX, startY = self.aabb.min.x, self.aabb.min.y 130 sizeY = int(math.ceil(sizeX * Y_SCALE)) 131 scale = self.aabb.Width() / (sizeX - 1) / self.tileWidth 132 sizeY = int(math.ceil(self.aabb.Height() / (self.tileHeight * scale))) + 1 133 height = self.tileHeight * scale 134 width = self.tileWidth * scale 135 offset = -height * 0.5 136 for y in xrange(sizeY): 137 for x in xrange(sizeX): 138 tile = HexTile() 139 tile.scale = scale 140 shift = 0.0 if x % 2 == 0 else offset 141 tile.center = (startX + x * width, 0.0, startY + y * height + shift) 142 self.AssignSystemsToTile(tile) 143 layout.append(tile) 144 145 self.layoutCache[sizeX] = layout 146 return self.layoutCache[sizeX] 147 148 def LayoutTiles(self, size, sovereigntyInfo, changeList, colorByStandings = False): 149 changedSystems = set([ info.solarSystemID for info in changeList ]) 150 for info in self.systemInfoMap.itervalues(): 151 if info.systemID in sovereigntyInfo: 152 info.sovID = sovereigntyInfo[info.systemID] 153 info.changed = info.systemID in changedSystems 154 155 layout = self.GetLayout(size) 156 self.nextTileNum = 0 157 for i, tile in enumerate(layout): 158 if tile.IsEmpty(): 159 continue 160 tile.Update(colorByStandings) 161 hex = self.GetNextGeom() 162 hex.SetTile(tile) 163 hex.index = i 164 165 if self.firstUnusedTileNum is not None and self.firstUnusedTileNum > self.nextTileNum: 166 for i in xrange(self.nextTileNum, self.firstUnusedTileNum): 167 self.tilePool[i].Show(False) 168 169 self.firstUnusedTileNum = self.nextTileNum 170 self.currentLayout = layout 171 self.currentSize = size 172 if colorByStandings: 173 self.legend = [LegendItem(0, localization.GetByLabel('UI/PeopleAndPlaces/GoodStanding'), trinity.TriColor(*COLOR_STANDINGS_GOOD), data=None), LegendItem(1, localization.GetByLabel('UI/PeopleAndPlaces/StandingNeutral'), trinity.TriColor(*COLOR_STANDINGS_NEUTRAL), data=None), LegendItem(2, localization.GetByLabel('UI/PeopleAndPlaces/BadStanding'), trinity.TriColor(*COLOR_STANDINGS_BAD), data=None)] 174 else: 175 legend = [ (tile.sovID, tile.color) for tile in self.currentLayout if tile.sovID is not None ] 176 self.legend = [LegendItem(0, localization.GetByLabel('UI/Inflight/Brackets/SystemUnclaimed'), trinity.TriColor(*UNCLAIMED_TILE_COLOR), data=None), LegendItem(1, localization.GetByLabel('UI/Inflight/Brackets/SystemContested'), trinity.TriColor(*TIED_TILE_COLOR), data=None)] 177 factions = [] 178 for sovID, color in set(legend): 179 name = cfg.eveowners.Get(sovID).name 180 factions.append(LegendItem(2, name, trinity.TriColor(*color), data=sovID)) 181 182 self.legend += factions 183 self.highlightCurveSet.Play() 184 185 def Enable(self, enable = True): 186 self.rootTransform.display = enable 187 188 def HighlightTiles(self, dataList, colorList): 189 colors = [ (color.r, 190 color.g, 191 color.b, 192 1.0) for color in colorList ] 193 for geom in self.tilePool: 194 geom.Highlight(False) 195 if self.currentLayout[geom.index].sovID in dataList: 196 geom.Highlight(True) 197 continue 198 c2 = self.currentLayout[geom.index].color 199 for c1 in colors: 200 if abs(c1[0] - c2[0]) < 0.01 and abs(c1[1] - c2[1]) < 0.01 and abs(c1[2] - c2[2]) < 0.01: 201 geom.Highlight(True) 202 break 203 204 def EnableOutlines(self, enable): 205 numTiles = len(self.currentLayout) 206 for geom in self.tilePool: 207 if not geom.tile.display: 208 break 209 if not enable: 210 geom.SetOutlines(0) 211 continue 212 info = self.currentLayout[geom.index] 213 if info.sovID is None: 214 geom.SetOutlines(0) 215 else: 216 if geom.index % 2 == 0: 217 neighbors = [(32, geom.index + self.currentSize + 1), 218 (16, geom.index + self.currentSize), 219 (8, geom.index + self.currentSize - 1), 220 (4, geom.index - 1), 221 (2, geom.index - self.currentSize), 222 (1, geom.index + 1)] 223 else: 224 neighbors = [(32, geom.index + 1), 225 (16, geom.index + self.currentSize), 226 (8, geom.index - 1), 227 (4, geom.index - self.currentSize - 1), 228 (2, geom.index - self.currentSize), 229 (1, geom.index - self.currentSize + 1)] 230 tileIndex = 0 231 for bit, index in neighbors: 232 if 0 <= index < numTiles: 233 neighborTile = self.currentLayout[index] 234 if neighborTile.color == info.color: 235 continue 236 tileIndex += bit 237 238 geom.SetOutlines(tileIndex) 239 240 241 class HexSystemInfo(object): 242 243 def __init__(self, systemID, sovID, x, y): 244 self.systemID = systemID 245 self.sovID = sovID 246 self.point = Point(x, y) 247 self.changed = False 248 249 def GetPoint(self): 250 return self.point 251 252 min = property(GetPoint) 253 max = property(GetPoint) 254 255 256 class HexGeom(object): 257 258 def __init__(self, controller): 259 self.controller = controller 260 self.tile = controller.baseTile.CopyTo() 261 parameters = self.tile.mesh.additiveAreas[0].effect.parameters 262 self.color = [ p for p in parameters if p.name == 'DiffuseColor' ][0] 263 self.highlight = [ p for p in parameters if p.name == 'Highlight' ][0] 264 self.tileIndex = [ p for p in parameters if p.name == 'TileIndex' ][0] 265 self.index = None 266 controller.rootTransform.children.append(self.tile) 267 self.highlightBinding = None 268 269 def SetTile(self, tile): 270 self.Show() 271 self.SetColor(tile.color) 272 self.tile.translation = tile.center 273 self.tile.scaling = (tile.scale, tile.scale, tile.scale) 274 self.Flash(tile.changed) 275 276 def Show(self, enable = True): 277 self.tile.display = enable 278 279 def Highlight(self, enable = True): 280 if enable: 281 self.highlight.value = 1.0 282 else: 283 self.highlight.value = 0.0 284 285 def Flash(self, enable = True): 286 if enable and self.highlightBinding is None: 287 binding = trinity.TriValueBinding() 288 binding.sourceAttribute = 'currentValue' 289 binding.destinationAttribute = 'value' 290 binding.scale = 1.0 291 binding.sourceObject = self.controller.highlightCurve 292 binding.destinationObject = self.highlight 293 self.highlightBinding = binding 294 if enable: 295 if self.highlightBinding not in self.controller.highlightCurveSet.bindings: 296 self.controller.highlightCurveSet.bindings.append(self.highlightBinding) 297 else: 298 if self.highlightBinding in self.controller.highlightCurveSet.bindings: 299 self.controller.highlightCurveSet.bindings.remove(self.highlightBinding) 300 self.highlight.value = 0.0 301 302 def SetOutlines(self, index): 303 self.tileIndex.value = index 304 305 def SetColor(self, color): 306 self.color.value = color 307 308 309 class HexTile(object): 310 311 def __init__(self): 312 self.center = None 313 self.color = None 314 self.sovID = None 315 self.systems = {} 316 self.changed = False 317 318 def IsEmpty(self): 319 return len(self.systems) == 0 320 321 def Update(self, colorByStandings = False): 322 alliances = {} 323 self.changed = False 324 starmap = sm.GetService('starmap') 325 for info in self.systems.itervalues(): 326 if info.changed: 327 self.changed = True 328 if info.sovID is None: 329 continue 330 if info.sovID not in alliances: 331 alliances[info.sovID] = 0 332 alliances[info.sovID] += 1 333 334 histogram = [ (count, sovID) for sovID, count in alliances.iteritems() if sovID is not None ] 335 histogram.sort() 336 if len(histogram) == 0: 337 self.sovID = None 338 self.color = UNCLAIMED_TILE_COLOR 339 elif len(histogram) == 1 or histogram[-1][0] != histogram[-2][0]: 340 self.sovID = histogram[-1][1] 341 if colorByStandings: 342 c = starmap.GetColorByStandings(self.sovID) 343 color = (c[0], 344 c[1], 345 c[2], 346 1.0) 347 else: 348 c = starmap.GetFactionOrAllianceColor(self.sovID) 349 color = (c.r, 350 c.g, 351 c.b, 352 1.0) 353 self.color = color 354 elif histogram[-1][0] == histogram[-2][0]: 355 self.sovID == None 356 self.color = TIED_TILE_COLOR 357 else: 358 self.sovID == None 359 self.color = UNCLAIMED_TILE_COLOR 360 361 def __repr__(self): 362 return 'HexTile(sov=%s center=%s color=%s systems=%s)' % (str(self.sovID), 363 str(self.center), 364 str(self.color), 365 str([ i for i in self.systems ])) 366 367 368 class AABB(object): 369 370 def __init__(self, minPoint = None, maxPoint = None): 371 if minPoint is None: 372 self.min = Point() 373 else: 374 self.min = minPoint 375 if maxPoint is None: 376 self.max = Point() 377 else: 378 self.max = maxPoint 379 380 def Width(self): 381 return self.max.x - self.min.x 382 383 def Height(self): 384 return self.max.y - self.min.y 385 386 def IncludePoint(self, point): 387 self.min.x = min(self.min.x, point.x) 388 self.min.y = min(self.min.y, point.y) 389 self.max.x = max(self.max.x, point.x) 390 self.max.y = max(self.max.y, point.y) 391 392 def Center(self): 393 return (self.min + self.max) * 0.5 394 395 def __repr__(self): 396 return '(%f, %f, %f, %f)' % (self.min.x, 397 self.min.y, 398 self.max.x, 399 self.max.y) 400 401 402 class Point(object): 403 404 def __init__(self, x = 0, y = 0): 405 self.x = x 406 self.y = y 407 408 def __mul__(self, scalar): 409 return Point(self.x * scalar, self.y * scalar) 410 411 def __add__(self, other): 412 return Point(self.x + other.x, self.y + other.y) 413 414 def dot(self, other): 415 return self.x * other.x + self.y * other.y 416 417 def __repr__(self): 418 return '(%f, %f)' % (self.x, self.y) 419 420 421 class QuadTree(object): 422 423 def __init__(self, items, depth = 8, aabb = None): 424 self.nw = self.ne = self.se = self.sw = None 425 depth -= 1 426 if depth == 0: 427 self.items = items 428 return 429 if aabb is None: 430 aabb = AABB() 431 aabb.min.x = min((item.min.x for item in items)) 432 aabb.min.y = min((item.min.y for item in items)) 433 aabb.max.x = max((item.max.x for item in items)) 434 aabb.max.y = max((item.max.y for item in items)) 435 self.center = center = aabb.Center() 436 self.items = [] 437 nw_items = [] 438 ne_items = [] 439 se_items = [] 440 sw_items = [] 441 for item in items: 442 in_nw = item.min.x <= center.x and item.min.y <= center.y 443 in_sw = item.min.x <= center.x and item.max.y >= center.y 444 in_ne = item.max.x >= center.x and item.min.y <= center.y 445 in_se = item.max.x >= center.x and item.max.y >= center.y 446 if in_nw and in_ne and in_se and in_sw: 447 self.items.append(item) 448 else: 449 if in_nw: 450 nw_items.append(item) 451 if in_ne: 452 ne_items.append(item) 453 if in_se: 454 se_items.append(item) 455 if in_sw: 456 sw_items.append(item) 457 458 if nw_items: 459 self.nw = QuadTree(nw_items, depth, AABB(Point(aabb.min.x, aabb.min.y), Point(center.x, center.y))) 460 if ne_items: 461 self.ne = QuadTree(ne_items, depth, AABB(Point(center.x, aabb.min.y), Point(aabb.max.x, center.y))) 462 if se_items: 463 self.se = QuadTree(se_items, depth, AABB(Point(center.x, center.y), Point(aabb.max.x, aabb.max.y))) 464 if sw_items: 465 self.sw = QuadTree(sw_items, depth, AABB(Point(aabb.min.x, center.y), Point(center.x, aabb.max.y))) 466 467 def Overlaps(self, rect, item): 468 return rect.max.x >= item.min.x and rect.min.x <= item.max.x and rect.max.y >= item.min.y and rect.min.y <= item.max.y 469 470 def Hit(self, rect): 471 hits = set((item for item in self.items if self.Overlaps(rect, item))) 472 if self.nw and rect.min.x <= self.center.x and rect.min.y <= self.center.y: 473 hits |= self.nw.Hit(rect) 474 if self.sw and rect.min.x <= self.center.x and rect.max.y >= self.center.y: 475 hits |= self.sw.Hit(rect) 476 if self.ne and rect.max.x >= self.center.x and rect.min.y <= self.center.y: 477 hits |= self.ne.Hit(rect) 478 if self.se and rect.max.x >= self.center.x and rect.max.y >= self.center.y: 479 hits |= self.se.Hit(rect) 480 return hits