Commit ceaf7b0c authored by Karol Actun's avatar Karol Actun
Browse files

implemented/modified marking(rasenmaeher)-algorithm for 3D + saving scenarios + small bugfixes

parent a68c1bf2
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
.idea/*
.ides*
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
......
......@@ -3,5 +3,5 @@
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8" project-jdk-type="Python SDK" />
</project>
\ No newline at end of file
......@@ -5,7 +5,7 @@
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.7" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.8" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
......
......@@ -24,9 +24,9 @@ gui = default_gui
# Grid Configs. uncomment your choice!
;# Quadratic Grid grid default configs:
;grid_class = QuadraticGrid
;# .obj (Wavefront) filenames in lib/visualization/models
;particle_model_file = 2d_particle.obj
;tile_model_file = 2d_quad_tile.obj
;location_model_file = 2d_location.obj
......@@ -35,37 +35,42 @@ gui = default_gui
# Triangular grid default configs:
grid_class = TriangularGrid
# .obj (Wavefront) filenames in lib/visualization/models
particle_model_file = 2d_particle.obj
tile_model_file = 2d_hex_tile.obj
location_model_file = 2d_location.obj
grid_size = 100
;grid_class = TriangularGrid
;particle_model_file = 2d_particle.obj
;tile_model_file = 2d_hex_tile.obj
;location_model_file = 2d_location.obj
;grid_size = 100
# end of Triangular grid configs
;# cubic grid default configs:
;grid_class = CubicGrid
;particle_model_file = 3d_particle_low_poly.obj
;tile_model_file = 3d_cube_tile.obj
;location_model_file = 3d_location.obj
;grid_size = 10
;# end of cubic grid configs
# cubic grid default configs:
grid_class = CubicGrid
particle_model_file = 3d_particle_low_poly.obj
tile_model_file = 3d_cube_tile.obj
location_model_file = 3d_location.obj
grid_size = 10
# end of cubic grid configs
;
;# ccp grid default configs:
;grid_class = CCPGrid
;particle_model_file = 3d_particle.obj
;tile_model_file = 3d_ccp_tile.obj
;location_model_file = 3d_location.obj
;location_model_file = 3d_location_2.obj
;grid_size = 10
;# end of ccp grid configs
;
# matter default colors (rgba)
particle_color = (0.8, 0.3, 0.3, 1.0)
tile_color = (0.3, 0.3, 0.8, 1.0)
particle_scaling = (1.0, 1.0, 1.0)
tile_color = (0.0, 0.0, 0.0, 0.5)
tile_scaling = (0.1, 0.1, 0.1)
location_color = (0.3, 0.8, 0.3, 1.0)
location_scaling = (1.2, 1.2, 1.2)
grid_color = (0.0, 0.0, 0.0, 1.0)
cursor_color = (0.5, 0.5, 0.5, 0.5)
center_color = (1.0, 0.0, 0.0, 0.5)
......@@ -77,13 +82,13 @@ line_color = (0.0, 0.0, 0.0, 1.0)
# length/scaling of the grid lines (max should be 1,1,1)
line_scaling = (1.0, 1.0, 1.0)
# flag for showing the lines
show_lines = True
show_lines = False
# color of grid coordinates (rgba)
coordinates_color = (0.0, 0.0, 0.0, 1.0)
# size/scaling of the coordinate model. wouldn't make it bigger than 0.2, 0.2, 0.2
coordinates_scaling = (0.05, 0.05, 0.05)
# flag for showing the coordinate models
show_coordinates = True
show_coordinates = False
# flag for showing the center of the grid
show_center = False
# camera focus color
......@@ -125,6 +130,6 @@ particle_mm_size = 2
tile_mm_size = 2
[File]
scenario = test_interfaces
solution = test_all_the_interfaces
scenario = marking_3d_scenario
solution = marking_3d_local
from abc import ABC, abstractmethod
import numpy as np
class Grid(ABC):
......@@ -195,3 +196,33 @@ class Grid(ABC):
current_ns = tmp
return current_ns
def get_nearest_direction(self, position, target):
"""
calculates the best/nearest direction for going from position to target.
its a general formula, there are/should be better solutions for specific grids!
calculates the angles of the target-position vector to the directions
and returns the direction with minimal angle
:param position: the starting position
:param target: the target position
:return: direction (float, float, float)
"""
v = (target[0]-position[0], target[1] - position[1], target[2] - position[2])
v_length = np.sqrt((v[0]*v[0])+(v[1]*v[1])+(v[2]*v[2]))
s = self.get_scaling()
best = None
best_angle = 0
for d in self.get_directions_list():
sd = (d[0]*s[0], d[1]*s[1], d[2]*s[2])
d_length = np.sqrt((sd[0]*sd[0])+(sd[1]*sd[1])+(sd[2]*sd[2]))
if d_length*v_length != 0:
angle = np.arccos((v[0]*sd[0]+v[1]*sd[1]+v[2]*sd[2])/(d_length * v_length))
if best is None:
best = d
best_angle = angle
else:
if angle < best_angle:
best_angle = angle
best = d
return best
......@@ -97,8 +97,16 @@ def sim_tab(vis, world):
world.reset()
reset_button.clicked.connect(reset_sim)
save_scenario_button = QPushButton("save Scenario")
def save_scenario():
world.save_scenario()
save_scenario_button.clicked.connect(save_scenario)
layout.addWidget(screenshot_button, alignment=Qt.AlignBaseline)
layout.addWidget(reset_button, alignment=Qt.AlignBaseline)
layout.addWidget(save_scenario_button, alignment=Qt.AlignBaseline)
layout.addStretch(0)
layout.addWidget(start_stop_button, alignment=Qt.AlignBaseline)
tab.setLayout(layout)
......@@ -115,7 +123,7 @@ def get_rps_slider(vis):
vis.set_rounds_per_second(value)
desc.setText("rounds per second (%d) : " % vis.get_rounds_per_second())
hbox.addWidget(create_slider(10, 2, 60, 1, vis.get_rounds_per_second(), set_rps), alignment=Qt.AlignBaseline)
hbox.addWidget(create_slider(10, 2, 120, 1, vis.get_rounds_per_second(), set_rps), alignment=Qt.AlignBaseline)
return hbox
......@@ -199,12 +207,13 @@ def get_particle_scaler(vis):
new_scaling = (current_scaling[0], current_scaling[1], value/10.0)
vis.set_particle_scaling(new_scaling)
current_scaling = vis.get_particle_scaling()
x_desc = QLabel("x scale:")
y_desc = QLabel("y scale:")
z_desc = QLabel("z scale:")
x_scaler = create_slider(2, 2, 20, 1, 10, x_scaler_change)
y_scaler = create_slider(2, 2, 20, 1, 10, y_scaler_change)
z_scaler = create_slider(2, 2, 20, 1, 10, z_scaler_change)
x_scaler = create_slider(2, 2, 20, 1, current_scaling[0]*10, x_scaler_change)
y_scaler = create_slider(2, 2, 20, 1, current_scaling[0]*10, y_scaler_change)
z_scaler = create_slider(2, 2, 20, 1, current_scaling[0]*10, z_scaler_change)
hbox1 = QHBoxLayout()
hbox1.addWidget(x_desc, alignment=Qt.AlignBaseline)
......@@ -245,12 +254,13 @@ def get_tile_scaler(vis):
new_scaling = (current_scaling[0], current_scaling[1], value/10.0)
vis.set_tile_scaling(new_scaling)
current_scaling = vis.get_tile_scaling()
x_desc = QLabel("x scale:")
y_desc = QLabel("y scale:")
z_desc = QLabel("z scale:")
x_scaler = create_slider(2, 2, 20, 1, 10, x_scaler_change)
y_scaler = create_slider(2, 2, 20, 1, 10, y_scaler_change)
z_scaler = create_slider(2, 2, 20, 1, 10, z_scaler_change)
x_scaler = create_slider(2, 2, 20, 1, current_scaling[0]*10, x_scaler_change)
y_scaler = create_slider(2, 2, 20, 1, current_scaling[1]*10, y_scaler_change)
z_scaler = create_slider(2, 2, 20, 1, current_scaling[2]*10, z_scaler_change)
hbox1 = QHBoxLayout()
hbox1.addWidget(x_desc, alignment=Qt.AlignBaseline)
......@@ -291,12 +301,13 @@ def get_location_scaler(vis):
new_scaling = (current_scaling[0], current_scaling[1], value/10.0)
vis.set_location_scaling(new_scaling)
current_scaling = vis.get_location_scaling()
x_desc = QLabel("x scale:")
y_desc = QLabel("y scale:")
z_desc = QLabel("z scale:")
x_scaler = create_slider(2, 2, 20, 1, 10, x_scaler_change)
y_scaler = create_slider(2, 2, 20, 1, 10, y_scaler_change)
z_scaler = create_slider(2, 2, 20, 1, 10, z_scaler_change)
x_scaler = create_slider(2, 2, 20, 1, current_scaling[0]*10, x_scaler_change)
y_scaler = create_slider(2, 2, 20, 1, current_scaling[1]*10, y_scaler_change)
z_scaler = create_slider(2, 2, 20, 1, current_scaling[2]*10, z_scaler_change)
hbox1 = QHBoxLayout()
hbox1.addWidget(x_desc, alignment=Qt.AlignBaseline)
......
......@@ -42,6 +42,9 @@ class ConfigData:
self.particle_color = make_tuple(config.get("Visualization", "particle_color"))
self.tile_color = make_tuple(config.get("Visualization", "tile_color"))
self.location_color = make_tuple(config.get("Visualization", "location_color"))
self.particle_scaling = make_tuple(config.get("Visualization", "particle_scaling"))
self.tile_scaling = make_tuple(config.get("Visualization", "tile_scaling"))
self.location_scaling = make_tuple(config.get("Visualization", "location_scaling"))
self.grid_color = make_tuple(config.get("Visualization", "grid_color"))
self.cursor_color = make_tuple(config.get("Visualization", "cursor_color"))
self.background_color = make_tuple(config.get("Visualization", "background_color"))
......
......@@ -85,7 +85,7 @@ class Matter:
def delete_memeory_with(self, key):
del self._memory[key]
def delete_whole_memeory(self):
def delete_whole_memory(self):
self._memory.clear()
def get_id(self):
......
......@@ -9,7 +9,6 @@ import time
from lib.visualization.camera import Camera
from lib.visualization.utils import LoadingWindow
import OpenGL.GL as GL
def close(_):
exit(0)
......@@ -202,9 +201,6 @@ class Visualization:
self._wait_while_not_running()
time_elapsed = time.perf_counter() - round_start_timestamp
GL.glFinish()
end = time.perf_counter_ns()
def remove_particle(self, particle):
"""
removes a particle from the visualization.
......@@ -385,12 +381,14 @@ class Visualization:
def set_show_center(self, show_center: bool):
self._viewer.show_center = show_center
self._viewer.glDraw()
def get_show_focus(self):
return self._viewer.show_focus
def set_show_focus(self, show_focus: bool):
self._viewer.show_focus = show_focus
self._viewer.glDraw()
def take_screenshot(self):
self._viewer.take_screenshot()
......@@ -404,18 +402,21 @@ class Visualization:
def set_particle_scaling(self, scaling):
self._viewer.programs["particle"].set_model_scaling(scaling)
self._viewer.glDraw()
def get_tile_scaling(self):
return self._viewer.programs["tile"].get_model_scaling()
def set_tile_scaling(self, scaling):
self._viewer.programs["tile"].set_model_scaling(scaling)
self._viewer.glDraw()
def get_location_scaling(self):
return self._viewer.programs["location"].get_model_scaling()
def set_location_scaling(self, scaling):
self._viewer.programs["location"].set_model_scaling(scaling)
self._viewer.glDraw()
def set_on_cursor_click_matter_type(self, matter_type):
if matter_type == 'tile' or matter_type == 'particle' or matter_type == 'location':
......
......@@ -119,19 +119,21 @@ class OGLWidget(QtOpenGL.QGLWidget):
GL.glEnable(GL.GL_DEPTH_TEST)
GL.glEnable(GL.GL_BLEND)
GL.glEnable(GL.GL_LINE_SMOOTH)
GL.glEnable(GL.GL_CULL_FACE)
GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA)
GL.glClearColor(*self.background, 1.0)
# initialize the openGL programs
self.programs["particle"] = OffsetColorCarryProgram(self.world.config_data.particle_model_file)
self.programs["particle"].set_world_scaling(self.world.grid.get_scaling())
self.programs["particle"].set_model_scaling(self.world.config_data.particle_scaling)
self.programs["tile"] = OffsetColorCarryProgram(self.world.config_data.tile_model_file)
self.programs["tile"].set_world_scaling(self.world.grid.get_scaling())
self.programs["tile"].set_model_scaling(self.world.config_data.tile_scaling)
self.programs["location"] = OffsetColorProgram(self.world.config_data.location_model_file)
self.programs["location"].set_world_scaling(self.world.grid.get_scaling())
self.programs["location"].set_model_scaling(self.world.config_data.location_scaling)
self.programs["grid"] = GridProgram(self.world.grid, self.world.config_data.line_color,
self.world.config_data.coordinates_color,
......@@ -246,7 +248,7 @@ class OGLWidget(QtOpenGL.QGLWidget):
"""
The main Draw Method.
All drawing calls originate here.
It will be called only when a new frame is needed. No new data -> no new frame
It will be called only when a new frame is needed. No change -> no new frame
:return:
"""
# clear the screen
......@@ -254,8 +256,8 @@ class OGLWidget(QtOpenGL.QGLWidget):
# draw
self.programs["particle"].draw()
self.programs["tile"].draw()
self.programs["location"].draw()
self.programs["tile"].draw()
self.programs["grid"].draw()
# center
......
This diff is collapsed.
......@@ -8,8 +8,11 @@ import importlib
import logging
import random
import threading
import os
import time
from lib import csv_generator, particle, tile, location, vis3d
from lib.swarm_sim_header import eprint
class World:
......@@ -125,6 +128,27 @@ class World:
if self.config_data.particle_random_order:
random.shuffle(self.particles)
def save_scenario(self):
if os.path.exists("scenario") and os.path.isdir("scenario"):
try:
f = open("scenario/scenario_%s.py" % str(time.perf_counter_ns()), "w+")
f.write("def solution(world):\n")
for p in self.particle_map_coordinates.values():
f.write("\tworld.add_particle(%s, color=%s)\n" % (str(p.coordinates), str(p.get_color())))
for t in self.tile_map_coordinates.values():
f.write("\tworld.add_tile(%s, color=%s)\n" % (str(t.coordinates), str(t.get_color())))
for l in self.location_map_coordinates.values():
f.write("\tworld.add_location(%s, color=%s)\n" % (str(l.coordinates), str(l.get_color())))
f.flush()
f.close()
except IOError as e:
eprint(e)
else:
eprint("\"scenario\" folder doesn't exist. "
"Please create it in the running directory before saving scenarios.")
def csv_aggregator(self):
self.csv_round.aggregate_metrics()
particle_csv = csv_generator.CsvParticleFile(self.config_data.direction_name)
......
from lib.swarm_sim_header import get_coordinates_in_direction
import random
def scenario(world):
center = world.grid.get_center()
dirs = world.grid.get_directions_list()
world.add_particle(center)
world.add_particle(get_coordinates_in_direction(center, random.choice(dirs)))
size = 5
ring = world.grid.get_n_sphere_border(center, size)
for c in ring:
world.add_tile(c, color=(0.0, 0.0, 0.0, 0.5))
import random
import math
graph = []
visited = []
unvisited_queue = []
class Location:
def __init__(self, coords):
self.coords = coords
self.adjacent = {}
self.visited = False
self.next_to_wall = False
def __eq__(self, other):
return self.coords == other.coords
def __str__(self):
return str(self.coords) + ' | Adjacent: ' + str([(direction, location.coords, location.next_to_wall) for direction, location in self.adjacent.items()])
# Checks if a location exists in a graph
def location_exists(graph, coords):
for location in graph:
if location.coords == coords:
return True
return False
# Returns the location from a graph given the coordinates
def get_location_with_coords(graph, coords):
for location in graph:
if location.coords == coords:
return location
return False
# Returns the direction of an adjacent location relative to the current location
# Returns the direction of an adjacent location relative to the current location
def get_dir(current_location, target_location):
return target_location.coords[0] - current_location.coords[0], \
target_location.coords[1] - current_location.coords[1], \
target_location.coords[2] - current_location.coords[2]
# Adds a new location to a graph
def add_location_to_graph(world, graph, location, directions):
if location in graph:
return
graph.append(location)
location.visited = True
for direction in directions:
adjacent_location_coords = world.grid.get_coordinates_in_direction(location.coords,
world.grid.get_directions_list()[direction])
if location_exists(graph, adjacent_location_coords):
if location in get_location_with_coords(graph, adjacent_location_coords).adjacent.values():
continue
get_location_with_coords(graph, adjacent_location_coords).adjacent[
get_opposite_bearing(world, direction)] = location
if is_border(world, adjacent_location_coords):
if location.next_to_wall is True:
continue
location.next_to_wall = True
continue
# Checks if the given coordinates are valid simulator coordinates
def valid_sim_coords(world, coords):
return world.grid.are_valid_coordinates(coords)
# Checks if the location at the given coordinates is a border or not
def is_border(world, coords):
for tile in world.get_tiles_list():
if coords == tile.coordinates:
if tile.color == (0.0, 0.0, 0.0, 0.5):
return True
return False
# Initializes the new custom particle attributes
def set_particle_attributes(world, particle, search_alg):
directions = list(range(len(world.grid.get_directions_list())))
search_algo = []
if search_alg == 0:
search_algo = [0]
elif search_alg == 1:
search_algo = [-1]
elif search_alg == 2:
search_algo = [-1, 0]
search_algorithm = random.choice(search_algo)
setattr(particle, "direction", directions)
setattr(particle, "search_algorithm", search_algorithm)
setattr(particle, "origin_coords", particle.coordinates)
setattr(particle, "start_location", Location(particle.origin_coords))
setattr(particle, "current_location", None)
setattr(particle, "next_location", None)
setattr(particle, "target_location", particle.start_location)
setattr(particle, "stuck_location", None)
setattr(particle, "alternative_location", None)
setattr(particle, "bearing", None)
setattr(particle, "previous_location", None)
setattr(particle, "last_visited_locations", [])
setattr(particle, "alternative_locations", [])
setattr(particle, "reverse_path", [])
setattr(particle, "stuck", False)
setattr(particle, "alternative_reached", True)
setattr(particle, "target_reached", True)
setattr(particle, "done", False)
# Discovers the adjacent (Neighbour) locations relative to the particle's current location
def discover_adjacent_locations(world, particle):
global graph
global unvisited_queue
for direction in particle.direction:
adjacent_location_coords = world.grid.get_coordinates_in_direction(particle.current_location.coords,
world.grid.get_directions_list()[direction])
if not valid_sim_coords(world, adjacent_location_coords):
continue
if is_border(world, adjacent_location_coords):
if particle.current_location.next_to_wall is True:
continue
particle.current_location.next_to_wall = True
continue
if location_exists(graph, adjacent_location_coords):
if get_location_with_coords(graph, adjacent_location_coords) in particle.current_location.adjacent.values():
continue
particle.current_location.adjacent[direction] = get_location_with_coords(graph, adjacent_location_coords)
continue
new_location = Location(adjacent_location_coords)
particle.create_location_on(adjacent_location_coords)
world.new_location.set_color((0.0, 0.0, 1.0, 1.0))
particle.current_location.adjacent[direction] = new_location
unvisited_queue.append(new_location)
add_location_to_graph(world, graph, new_location, particle.direction)
# Marks the particle's current location as visited and removes it from the particle's unvisited queue
def mark_location(world, particle):
global visited
global unvisited_queue
particle.current_location.visited = True
visited.append(particle.current_location)
unvisited_queue = [location for location in unvisited_queue if location not in visited]
current_location = world.location_map_coordinates[particle.coordinates]
if current_location.color == (0.0, 0.8, 0.8, 1.0):
return
particle.delete_location()
particle.create_location()
world.new_location.set_color((0.0, 0.8, 0.8, 1.0))
# Returns the distance between 2 locations
def get_distance(location1, location2):
x1 = location1.coords[0]
x2 = location2.coords[0]
y1 = location1.coords[1]
y2 = location2.coords[1]
z1 = location1.coords[2]
z2 = location2.coords[2]
return abs(math.sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2) + ((z2 - z1) ** 2)))
# Returns the nearest location in the particle's unvisited queue relative to the particle's current location
def get_nearest_unvisited(particle):