-
Notifications
You must be signed in to change notification settings - Fork 40
Example Scripts
This will be the place to store relevant examples for newcomers to learn about Skadi. All of these scripts require a demo file path, and some require some common scripts/variables that were developed as well. Current versions of these scripts, and the common items can be found at https://github.com/garth5689/skadi/tree/explore/explore. If you have questions, ask in IRC. Enjoy!
Total Gold Earned
Pudge Hooks
Print Neutrals
Buyback Cooldown
Total Distance Traveled
Midas Efficiency
Hero Attributes
Team Convex Hulls
##Total Gold Earned
back to top
The following example shows you how to plot each player's total gold earned vs. game time
from matplotlib import pyplot as plt
from common import DEMO_FILE_PATH, PLAYER_COLORS
from skadi import demo as d
def main():
# first, construct the demo file so Skadi can access the information
game = d.construct(DEMO_FILE_PATH)
total_gold_earned = {}
game_time = []
player_names = []
# This loop gets all the player names from the file info. This information
# could also be obtained through the DT_DOTA_PlayerResource.
# This will also create dict keys using the player names.
for player in game.file_info.game_info.dota.player_info:
name = player.player_name.encode('UTF-8')
player_names.append(name)
total_gold_earned[name] = []
# the tick specifies a certain point in the replay. In this loop, a stream
# is created to loop through the replay and grab the gold values throughout.
for tick, user_messages, game_events, world, modifiers in game.stream(tick=0):
# Here we access the respective DTs. A DT is a dict where information
# is stored. In the DT_DOTAGamerulesProxy, we can find meta information
# about the game. This is how game time is calcluated. The
# DT_DOTA_PlayerResource contains the players gold totals. The ehandle
# is a replay-wide unique identifier to relate different values.
players_ehandle, players_state = world.find_by_dt('DT_DOTA_PlayerResource')
rules_ehandle, rules_state = world.find_by_dt('DT_DOTAGamerulesProxy')
# Wait until game has started
if rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')] != 0.0:
# Calculate game time
time = rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_fGameTime')] \
- rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')]
game_time.append(time / 60)
# Append each player's gold value to the dict. The DT_EndScore...
# is the key we use to find each player's gold value.
for number, player in enumerate(player_names):
player_num = str(number).zfill(4)
player_gold_DT = ('DT_EndScoreAndSpectatorStats', 'm_iTotalEarnedGold.{ID}'.format(ID=player_num))
total_gold_earned[player].append(players_state[player_gold_DT])
# Determine max gold for plot.
maxgold = 0
for gold in total_gold_earned.itervalues():
if max(gold) > maxgold:
maxgold = max(gold)
# Plot results
figure, axis = plt.subplots()
figure.patch.set_facecolor('white')
axis.set_ylabel('m_iTotalEarnedGold [gold]')
axis.set_xlabel('Game Time [min]')
for i in range(10):
axis.plot(game_time, total_gold_earned[player_names[i]], \
color=PLAYER_COLORS[i], linewidth=3.0, label=player_names[i])
axis.axis((0, game_time[-1], 0, maxgold))
axis.legend(loc='upper left', labelspacing=0.5, handletextpad=2)
axis.set_title('Total Gold Earned [gold] vs. Time [min]')
plt.show()
if __name__ == '__main__':
main()
##Pudge Hooks
back to top
This script will save an image showing all the pudge hooks throughout a game, there are some things to be done yet, but it is functional.
import os
import matplotlib.pyplot as plt
from matplotlib.pyplot import savefig
import matplotlib.offsetbox as ob
from common import HERO_ICONS_PATH, MINIMAP_PATH, HEROID, DEMO_FILE_PATH, worldcoordfromcell, imagecoordfromworld
from skadi import demo as d
'''
This script should plot all the hooks that occured in a game with pudge. It uses the modifer placed on a hooked
target as the indicator for a hook. You will need the folder of Hero Icons to plot the targets. Hooks that
do not hit a hero, but do hit a creep are red lines. Hooks that miss completely, or kill a hero on impact are
not implemented yet. These are features for a future version.
'''
def main():
game = d.construct(DEMO_FILE_PATH)
game_time = []
hooks = []
for tick, user_messages, game_events, world, modifiers in game.stream(tick=0):
rules_ehandle, rules_state = world.find_by_dt('DT_DOTAGamerulesProxy')
if rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')] != 0.0:
time = rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_fGameTime')] - \
rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')]
game_time.append(time / 60)
# Here we want to start going through the modifiers.
# We are looking for 'modifier_pudge_meat_hook'.
# Once this modifier is encountered, we find the location of pudge and the target
# and the target's model index. We will use this for plotting later.
for parent, parent_modifiers in modifiers.by_parent.iteritems():
for mod_num, mod_dict in parent_modifiers.iteritems():
if mod_dict['name'] == 'modifier_pudge_meat_hook':
if not hooks:
# Each hook will be a dict with the following information to make plotting easy.
hooks.append({'tick': tick, 'target': parent,
'target_index': world.find(parent)[('DT_DOTA_BaseNPC', 'm_iUnitNameIndex')],
'time': time / 60.0,
'pudge_pos': worldcoordfromcell(world.find(mod_dict['caster'])),
'target_pos': worldcoordfromcell(world.find(parent))})
elif tick - hooks[-1]['tick'] > 100:
hooks.append({'tick': tick, 'target': parent,
'target_index': world.find(parent)[('DT_DOTA_BaseNPC', 'm_iUnitNameIndex')],
'time': time / 60.0,
'pudge_pos': worldcoordfromcell(world.find(mod_dict['caster'])),
'target_pos': worldcoordfromcell(world.find(parent))})
else:
pass
# In my replay, there was an error with me hooking an allied hero. This hook kept showing up in each tick,
# so I removed it manually.
# hooks = [hooks[i] for i in range(len(hooks)) if hooks[i]['target'] != 832840]
return hooks
def hook_plotting(hooks):
# Add our map image, scale & color axes appropriately, and make the axes take up the whole figure
map_img = plt.imread(MINIMAP_PATH)
fig, ax = plt.subplots(figsize=(10.25, 10.25))
ax.set_position([0, 0, 1, 1])
plt.imshow(map_img)
fig.patch.set_facecolor('black')
ax.patch.set_facecolor('black')
ax.axis((0, 1024, 1024, 0))
# Each hero icon is plotted as an OffsetImage. Basically the image is an OffsetImage, which is
# then added to the plot as an OffsetBox.
pudge_img = plt.imread(os.path.abspath(os.path.join(HERO_ICONS_PATH, 'npc_dota_hero_pudge.png')))
pudge_oi = ob.OffsetImage(pudge_img, zoom=0.75)
for hook in hooks:
px, py = imagecoordfromworld(hook['pudge_pos'][0], hook['pudge_pos'][1])
tx, ty = imagecoordfromworld(hook['target_pos'][0], hook['target_pos'][1])
ax.plot([px, tx], [py, ty], color='r', zorder=3, linewidth=5)
pudge_ab = ob.AnnotationBbox(pudge_oi, (px, py))
pudge_ab.patch.set_alpha(0)
pudge_art = ax.add_artist(pudge_ab)
pudge_art.set(zorder=4)
#Can't remember right now why I had a KeyError exception here.
try:
hero_img_name = HEROID[hook['target_index']]
target_img = plt.imread(
os.path.abspath(os.path.join(HERO_ICONS_PATH, '{hero}.png'.format(hero=hero_img_name))))
target_oi = ob.OffsetImage(target_img, zoom=0.75)
target_ab = ob.AnnotationBbox(target_oi, (tx, ty))
target_ab.patch.set_alpha(0)
target_art = ax.add_artist(target_ab)
target_art.set(zorder=5)
except KeyError:
pass
#Replace this with a suitable save location if you want to save
savefig('/Users/Andrew/Desktop/hooks.png', dpi=100)
if __name__ == '__main__':
hooks = main()
hook_plotting(hooks)
##Print Neutrals
back to top
This script will print out each neutral from the game, with their model index
import re
from sets import Set
from common import DEMO_FILE_PATH
from skadi import demo as d
def main():
# Construct a demo and make a set to store neutral model indices
game = d.construct(DEMO_FILE_PATH)
creep_set = Set()
# Iterate through each tick of the replay, finding all instances of
# 'DT_DOTA_BaseNPC_Creep_Neutral'. These are the DTs that specify neutral
# creep units.
for tick, user_messages, game_events, world, modifiers in game.stream(tick=0):
neutral = world.find_all_by_dt('DT_DOTA_BaseNPC_Creep_Neutral')
# neutral at this point is a list of ehandles. ehandles are unique
# identifiers, so we want to use world.find(ehandle) to access the data
# Once we find them, world.find returns a dict.
# ('DT_BaseEntity', 'm_nModelIndex') is the key for that dict that returns
# the model index. All of the data stored in DTs is access this way,
# using ehandles and keys.
for creep in neutral:
creep_set.add(world.find(creep)[('DT_BaseEntity', 'm_nModelIndex')])
# Skip to the end of the replay and grab the table that allows us to convert
# model index into a useful name.
model_table = game.stream(tick=game.file_info.playback_ticks - 5).string_tables['modelprecache']
creep_list = sorted(creep_set)
for creep in creep_list:
# This regex will take the name of the model, and strip the .mdl extension
# this gives us a better idea of which creep is which.
creep_name = re.findall('(?<=/)[a-z\_]+(?=\.mdl)', model_table.by_index[creep][0])[0]
print '{0: <3}'.format(creep), ':', creep_name.encode('UTF-8')
if __name__ == '__main__':
main()
##Buyback Cooldown
back to top
This script plots buyback cooldown for a specific player vs. game time
import matplotlib.pyplot as plt
from common import DEMO_FILE_PATH
from skadi import demo as d
def bbcd():
game = d.construct(DEMO_FILE_PATH)
bbcd = []
game_time = []
for tick, user_messages, game_events, world, modifiers in game.stream(tick=0):
players_ehandle, players_state = world.find_by_dt('DT_DOTA_PlayerResource')
rules_ehandle, rules_state = world.find_by_dt('DT_DOTAGamerulesProxy')
if rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')] != 0.0:
time = rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_fGameTime')] \
- rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')]
game_time.append(time / 60)
bbcd.append(players_state[('DT_DOTA_PlayerResource', 'm_flBuybackCooldownTime.0001')])
return game_time, bbcd
def bbcd_plotting(game_time, bbcd):
fig, ax = plt.subplots()
fig.patch.set_facecolor('white')
ax.set_ylabel('m_flBuybackCooldownTime')
ax.set_xlabel('Game Time [min]')
ax.axis((0, 40, 0, 3500))
ax.plot(game_time, bbcd, 'k', linewidth=3.0)
plt.show()
if __name__ == '__main__':
game_time, bbcd = bbcd()
bbcd_plotting(game_time, bbcd)
##Total Distance Traveled
back to top
This script will print out each player's name and the total distance that they traveled in game units during the game
from itertools import islice
from math import sqrt
from common import DEMO_FILE_PATH, worldcoordfromcell
from skadi import demo as d
def main():
game = d.construct(DEMO_FILE_PATH)
player_names = []
player_coords = {}
dist = {}
for player in game.file_info.game_info.dota.player_info:
name = player.player_name.encode('UTF-8')
player_names.append(name)
player_coords[name] = []
dist[name] = 0
for tick, user_messages, game_events, world, modifiers in islice(game.stream(tick=0), 0, None, 30):
players_ehandle, players_states = world.find_by_dt('DT_DOTA_PlayerResource')
rules_ehandle, rules_state = world.find_by_dt('DT_DOTAGamerulesProxy')
if rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')] != 0.0:
for number, player in enumerate(player_names):
player_id = str(number).zfill(4)
hero = world.find(
players_states[('DT_DOTA_PlayerResource', 'm_hSelectedHero.{ID}'.format(ID=player_id))])
player_coords[player].append(worldcoordfromcell(hero))
for player in player_names:
for time in range(1, len(player_coords[player])):
x2, y2 = player_coords[player][time]
x1, y1 = player_coords[player][time - 1]
dist[player] += sqrt((y2 - y1) ** 2 + (x2 - x1) ** 2)
print '{player:''>20} : {dist}'.format(player=player, dist=dist[player])
if __name__ == '__main__':
main()
##Midas Efficiency
back to top
This script calculates an "efficiency" of each player's hand of midas. Unsure how it works with players getting more than one hand of midas...
from common import DEMO_FILE_PATH
from skadi import demo as d
'''
This script will take all players, check if they have a hand of midas, and if so, check how often it is on cooldown.
This is a pseduo-metric to determine how efficient they are at using their midas every time it is available, for maximum
gain. This does not take into account strategic uses, such as possessed neutral greeps, etc. If a player did not get
a midas during the game, it will print their name with 'no midas'.
'''
def main():
game = d.construct(DEMO_FILE_PATH)
player_names = []
midas_on_cd = {}
# Get all the player names
for player in game.file_info.game_info.dota.player_info:
name = player.player_name.encode('UTF-8')
player_names.append(name)
midas_on_cd[name] = []
for tick, user_messages, game_events, world, modifiers in game.stream(tick=0):
players_ehandle, players_states = world.find_by_dt('DT_DOTA_PlayerResource')
rules_ehandle, rules_state = world.find_by_dt('DT_DOTAGamerulesProxy')
if rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')] != 0.0:
# For each player,
for player_num in range(10):
player_id = str(player_num).zfill(4)
hero = world.find(
players_states[('DT_DOTA_PlayerResource', 'm_hSelectedHero.{ID}'.format(ID=player_id))])
# For each item in that player's inventory
for item_num in range(6):
item_id = str(item_num).zfill(4)
# Try querying their hand of midas cooldown. If they don't have a hand of midas, we'll
# except that KeyError and continue to the next player.
try:
item = world.find(hero[('DT_DOTA_UnitInventory', 'm_hItems.{ID}'.format(ID=item_id))])
if item[('DT_BaseEntity', 'm_iName')] == 'item_hand_of_midas':
# If they do have a midas, we'll add an item to the boolean
# list to determine if it's on cooldown or not.
item_cd = item[('DT_DOTABaseAbility', 'm_fCooldown')]
game_time = rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_fGameTime')]
midas_on_cd[player_names[player_num]].append(item_cd > game_time)
except KeyError:
pass
for player in player_names:
if not midas_on_cd[player]:
print '{player:''>20} : no midas'.format(player=player)
else:
# If midas_on_cd isn't empty, print the efficiency as a percentage
print '{player:''>20} : {midaseff}'.format(player=player, midaseff=str(
(float(midas_on_cd[player].count(True)) / len(midas_on_cd[player])) * 100))
if __name__ == '__main__':
main()
##Hero Attributes
back to top
This script will plot each hero's attributes (str, agi, int) over the course of the game vs time.
import matplotlib.pyplot as plt
from common import DEMO_FILE_PATH
from skadi import demo as d
'''
This script plots the attributes of a single hero over the entire game.
'''
def stragiint():
game = d.construct(DEMO_FILE_PATH)
# strength, agi, intel are the base stats from levels
strength = []
agi = []
intel = []
# the totals are the base + any attributes added by items or buffs
str_tot = []
agi_tot = []
int_tot = []
game_time = []
for tick, user_messages, game_events, world, modifiers in game.stream(tick=0):
players_ehandle, players_state = world.find_by_dt('DT_DOTA_PlayerResource')
rules_ehandle, rules_state = world.find_by_dt('DT_DOTAGamerulesProxy')
if rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')] != 0.0:
# This is the selected hero. For each tick, grab all the attributes and add them to the appropriate list
hero = world.find(players_state[('DT_DOTA_PlayerResource', 'm_hSelectedHero.0001')])
agi.append(hero[('DT_DOTA_BaseNPC_Hero', 'm_flAgility')])
strength.append(hero[('DT_DOTA_BaseNPC_Hero', 'm_flStrength')])
intel.append(hero[('DT_DOTA_BaseNPC_Hero', 'm_flIntellect')])
agi_tot.append(hero[('DT_DOTA_BaseNPC_Hero', 'm_flAgilityTotal')])
int_tot.append(hero[('DT_DOTA_BaseNPC_Hero', 'm_flIntellectTotal')])
str_tot.append(hero[('DT_DOTA_BaseNPC_Hero', 'm_flStrengthTotal')])
time = rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_fGameTime')] \
- rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')]
game_time.append(time / 60)
return game_time, strength, agi, intel, str_tot, agi_tot, int_tot
def stragiintplotting(game_time, strength, agi, intel, str_tot, agi_tot, int_tot):
fig, ax = plt.subplots()
fig.patch.set_facecolor('white')
ax.set_ylabel('Attributes')
ax.set_xlabel('Game Time [min]')
ax.axis((0, 40, 0, 100))
ax.plot(game_time, agi, 'g', label='agi base', linewidth=3.0)
ax.plot(game_time, agi_tot, 'g--', label='agi total', linewidth=3.0)
ax.plot(game_time, intel, 'b', label='int base', linewidth=3.0)
ax.plot(game_time, int_tot, 'b--', label='int total', linewidth=3.0)
ax.plot(game_time, strength, 'r', label='str base', linewidth=3.0)
ax.plot(game_time, str_tot, 'r--', label='str total', linewidth=3.0)
ax.legend(loc='upper left')
plt.show()
if __name__ == '__main__':
game_time, strength, agi, intel, str_tot, agi_tot, int_tot = stragiint()
stragiintplotting(game_time, strength, agi, intel, str_tot, agi_tot, int_tot)
##Team Hulls
back to top
This script takes each team, makes a convex hull around them, and then plots it. I am unable to get the animation working (unsure if it's a computer/python installation issue or script issue). If you find anything, please let garth5689
know in IRC.
import itertools
import os
import matplotlib.pyplot as plt
import matplotlib.offsetbox as ob
import numpy as np
import scipy.spatial as spatial
import matplotlib.patches as patches
from common import MINIMAP_PATH, HERO_ICONS_PATH, HEROID, DEMO_FILE_PATH, worldcoordfromcell, imagecoordfromworld
from skadi import demo as d
'''
This script creates a convex hull between all of the alive heros on the radiant/dire sides and plots it. As it is
currently written, this script will calculate all the data, and then when the plotting method is called, it will draw
and show each frame one-by-one. I could not find a suitable way to animate the frames, including saving them and then
animating them. If you can find a way, please let me know and submit a pull request to
https://github.com/garth5689/skadi/tree/explore I use the scipy.spatial module, and several extra hull functions
from http://tomswitzer.net/2010/03/graham-scan/
'''
TURN_LEFT, TURN_RIGHT, TURN_NONE = (1, -1, 0)
# This code allows us to take the simplex points from the convex hull and order them to draw the polygon
# Code from http://tomswitzer.net/2010/03/graham-scan/
def turn(p, q, r):
return cmp((q[0] - p[0]) * (r[1] - p[1]) - (r[0] - p[0]) * (q[1] - p[1]), 0)
def _keep_left(hull, r):
while len(hull) > 1 and turn(hull[-2], hull[-1], r) != TURN_LEFT:
hull.pop()
if not len(hull) or hull[-1] != r:
hull.append(r)
return hull
def convex_hull(points):
"""Returns points on convex hull of an array of points in CCW order."""
points = sorted(points)
l = reduce(_keep_left, points, [])
u = reduce(_keep_left, reversed(points), [])
return l.extend(u[i] for i in xrange(1, len(u) - 1)) or l
def main():
game = d.construct(DEMO_FILE_PATH)
hero_handles = []
player_nums = [str(i).zfill(4) for i in range(10)]
# These will be lists of lists. the length of each will be the number of frame generated, and each
# will refer to a tick.
unit_ids = []
rad_pos = []
dire_pos = []
for tick, user_messages, game_events, world, modifiers in itertools.islice(game.stream(tick=0), 0, None, 60):
players_ehandle, players_state = world.find_by_dt('DT_DOTA_PlayerResource')
rules_ehandle, rules_state = world.find_by_dt('DT_DOTAGamerulesProxy')
if rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')] != 0.0:
# The first time through this loop, generate a list of the hero handles present in the game.
if not hero_handles:
for player_num in player_nums:
hero_handles.append(
players_state[('DT_DOTA_PlayerResource', 'm_hSelectedHero.{ID}'.format(ID=player_num))])
temp_unit_ids = []
temp_rad_pos = []
temp_dire_pos = []
for num, hero_handle in enumerate(hero_handles):
hero = world.find(hero_handle)
if hero[('DT_DOTA_BaseNPC', 'm_lifeState')] == 0:
# Add each unit ID to the temp ID list, and each coords to the correct team's coords. This
# keeps the radiant and dire separate so we can draw two polygons.
if num <= 4:
dx, dy = worldcoordfromcell(hero)
x, y = imagecoordfromworld(dx, dy)
temp_rad_pos.append([x, y])
temp_unit_ids.append(hero[('DT_DOTA_BaseNPC', 'm_iUnitNameIndex')])
elif num >= 4:
dx, dy = worldcoordfromcell(hero)
x, y = imagecoordfromworld(dx, dy)
temp_dire_pos.append([x, y])
temp_unit_ids.append(hero[('DT_DOTA_BaseNPC', 'm_iUnitNameIndex')])
if not (temp_unit_ids == [] and temp_rad_pos == [] and temp_dire_pos == []):
unit_ids.append(temp_unit_ids)
rad_pos.append(np.array(temp_rad_pos))
dire_pos.append(np.array(temp_dire_pos))
return unit_ids, rad_pos, dire_pos
def hull_plotting(unit_ids, rad_pos, dire_pos):
for index in range(len(rad_pos)):
# set up the initial plot, add the map, maximize axes, etc.
fig, ax = plt.subplots(figsize=(10.25, 10.25))
map_img = plt.imread(MINIMAP_PATH)
ax.set_position([0, 0, 1, 1])
plt.imshow(map_img)
fig.patch.set_facecolor('black')
ax.patch.set_facecolor('black')
ax.axis((0, 1024, 1024, 0))
# Plot all the hero icons in their coordinates
for num, hero in enumerate(unit_ids[index]):
hero_img_name = HEROID[hero]
hero_img = plt.imread(
os.path.abspath(os.path.join(HERO_ICONS_PATH, '{hero}.png'.format(hero=hero_img_name))))
hero_oi = ob.OffsetImage(hero_img, zoom=0.75)
if num < len(rad_pos[index]):
hero_ab = ob.AnnotationBbox(hero_oi, (rad_pos[index][num, 0], rad_pos[index][num, 1]))
else:
hero_ab = ob.AnnotationBbox(hero_oi, (
dire_pos[index][num - len(rad_pos[index]), 0], dire_pos[index][num - len(rad_pos[index]), 1]))
hero_ab.patch.set_alpha(0)
hero_art = ax.add_artist(hero_ab)
hero_art.set(zorder=5)
# For each of the teams, if there are more than 2 heros, plot the polygon for the convex hull.
# If there are only two heros, draw a line between them. else, don't draw any lines, etc.
if len(rad_pos[index]) >= 3:
rad_hull = spatial.ConvexHull(rad_pos[index])
rad_points = []
for simplex in rad_hull.simplices:
p1 = [rad_pos[index][simplex, 0][0], rad_pos[index][simplex, 1][0]]
p2 = [rad_pos[index][simplex, 0][1], rad_pos[index][simplex, 1][1]]
if p1 not in rad_points:
rad_points.append(p1)
if p2 not in rad_points:
rad_points.append(p2)
rad_points = convex_hull(rad_points)
hull_poly = patches.Polygon(rad_points, fc='green', ec='green', alpha=0.4, lw=3)
ax.add_artist(hull_poly)
elif len(rad_pos[index]) == 2:
plt.plot(rad_pos[index][:, 0], rad_pos[index][:, 1], 'g-', linewidth=3, alpha=0.4, zorder=3)
if len(dire_pos[index]) >= 3:
dire_hull = spatial.ConvexHull(dire_pos[index])
dire_points = []
for simplex in dire_hull.simplices:
p1 = [dire_pos[index][simplex, 0][0], dire_pos[index][simplex, 1][0]]
p2 = [dire_pos[index][simplex, 0][1], dire_pos[index][simplex, 1][1]]
if p1 not in dire_points:
dire_points.append(p1)
if p2 not in dire_points:
dire_points.append(p2)
dire_points = convex_hull(dire_points)
hull_poly = patches.Polygon(dire_points, fc='red', ec='red', alpha=0.4, lw=3)
ax.add_artist(hull_poly)
elif len(dire_pos[index]) == 2:
plt.plot(dire_pos[index][:, 0], dire_pos[index][:, 1], 'g-', linewidth=3, alpha=0.4, zorder=3)
plt.show()
if __name__ == '__main__':
unit_ids, rad_pos, dire_pos = main()
hull_plotting(unit_ids, rad_pos, dire_pos)