Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions examples/custom_drawers/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from gamms.VisualizationEngine import Color, Shape
from gamms.VisualizationEngine.artist import Artist
from gamms.VisualizationEngine.default_drawers import render_rectangle
from gamms.VisualizationEngine.render_command import RenderCommand

import pickle

Expand Down Expand Up @@ -73,16 +74,16 @@ def custom_circle_drawer(ctx: IContext, data: dict):
y = data.get('y')
radius = data.get('radius')
color = data.get('color')
ctx.visual.render_circle(x, y, radius, color)
return [RenderCommand.circle(x, y, radius, color)]

# Special nodes
n1 = ctx.graph.graph.get_node(0)
n2 = ctx.graph.graph.get_node(1)

# You can create the artist directly
custom_artist1 = Artist(ctx, Shape.Circle, 5)
# custom_artist1 = Artist(ctx, Shape.Circle, 5)
# Alternatively, you can use the custom drawer
# custom_artist1 = Artist(ctx, custom_circle_drawer, 5)
custom_artist1 = Artist(ctx, custom_circle_drawer, 5)
custom_artist1.data['x'] = n1.x
custom_artist1.data['y'] = n1.y
custom_artist1.data['radius'] = 10
Expand Down
2 changes: 0 additions & 2 deletions gamms/VisualizationEngine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ class Color:
Brown = (210, 105, 30)
Purple = (128, 0, 128)


class Space(IntEnum):
World = 0
Screen = 1
Viewport = 2


class Shape(Enum):
Circle = auto()
Rectangle = auto()
Expand Down
38 changes: 26 additions & 12 deletions gamms/VisualizationEngine/artist.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
from gamms.typing import IArtist, ArtistType, IContext
from gamms.typing import IArtist, ArtistType, IContext, IRenderCommand
from gamms.VisualizationEngine.default_drawers import render_circle, render_rectangle
from gamms.VisualizationEngine import Shape
from typing import Callable, Union, Dict, Any
from typing import Callable, Union, Dict, List, Any

class Artist(IArtist):
def __init__(self, ctx: IContext, drawer: Union[Callable[[IContext, Dict[str, Any]], None], Shape], layer: int = 30):
def __init__(self, ctx: IContext, drawer: Union[Callable[[IContext, Dict[str, Any]], List[IRenderCommand]], Shape], layer: int = 30):
self.data = {}

self._ctx = ctx
self._layer = layer
self._layer_dirty = False
self._visible = True
self._will_draw = True
self._is_rendering = True
self._artist_type = ArtistType.GENERAL
self._render_commands: List[IRenderCommand] = []
if isinstance(drawer, Shape):
if drawer == Shape.Circle:
self._drawer = render_circle
Expand All @@ -31,6 +32,10 @@ def layer_dirty(self) -> bool:
def layer_dirty(self, value: bool):
self._layer_dirty = value

@property
def render_commands(self) -> List[IRenderCommand]:
return self._render_commands

def set_layer(self, layer: int):
if self._layer == layer:
return
Expand All @@ -44,7 +49,7 @@ def get_layer(self) -> int:
def set_visible(self, visible: bool):
self._visible = visible

def get_visible(self) -> bool:
def is_visible(self) -> bool:
return self._visible

def set_drawer(self, drawer: Callable[[IContext, Dict[str, Any]], None]):
Expand All @@ -53,21 +58,30 @@ def set_drawer(self, drawer: Callable[[IContext, Dict[str, Any]], None]):
def get_drawer(self) -> Callable[[IContext, Dict[str, Any]], None]:
return self._drawer

def get_will_draw(self) -> bool:
return self._will_draw
def is_rendering(self) -> bool:
return self._is_rendering

def set_will_draw(self, will_draw: bool):
self._will_draw = will_draw
def set_rendering(self, is_rendering: bool):
self._is_rendering = is_rendering

def get_artist_type(self) -> ArtistType:
return self._artist_type

def set_artist_type(self, artist_type: ArtistType):
self._artist_type = artist_type

def draw(self):
def draw(self, force: bool=False):
if self._is_rendering and not force:
return

try:
self._drawer(self._ctx, self.data)
self._render_commands = self._drawer(self._ctx, self.data)
except Exception as e:
self._ctx.logger.error(f"Error drawing artist: {e}")
self._ctx.logger.debug(f"Artist data: {self.data}")
self._ctx.logger.debug(f"Artist data: {self.data}")

def clear(self):
if self._is_rendering:
return

self._render_commands.clear()
66 changes: 44 additions & 22 deletions gamms/VisualizationEngine/default_drawers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from gamms.VisualizationEngine import Color
from gamms.VisualizationEngine.builtin_artists import AgentData, GraphData
from gamms.VisualizationEngine.render_command import RenderCommand
from gamms.typing import IContext, OSMEdge, Node, ColorType

from typing import Dict, Any, cast, List
Expand All @@ -19,7 +20,8 @@ def render_circle(ctx: IContext, data: Dict[str, Any]):
y = data.get('y')
radius = data.get('radius')
color = data.get('color', Color.Cyan)
ctx.visual.render_circle(x, y, radius, color)
render_command = RenderCommand.circle(x, y, radius, color)
return [render_command]


def render_rectangle(ctx: IContext, data: Dict[str, Any]):
Expand All @@ -35,7 +37,9 @@ def render_rectangle(ctx: IContext, data: Dict[str, Any]):
width = data.get('width')
height = data.get('height')
color = data.get('color', Color.Cyan)
ctx.visual.render_rectangle(x, y, width, height, color)
render_command = RenderCommand.rectangle(x, y, width, height, color)
return [render_command]


def render_agent(ctx: IContext, data: Dict[str, Any]):
"""
Expand Down Expand Up @@ -84,7 +88,8 @@ def render_agent(ctx: IContext, data: Dict[str, Any]):
point2 = (position[0] + size * math.cos(angle + 2.5), position[1] + size * math.sin(angle + 2.5))
point3 = (position[0] + size * math.cos(angle - 2.5), position[1] + size * math.sin(angle - 2.5))

ctx.visual.render_polygon([point1, point2, point3], color)
render_command = RenderCommand.polygon([point1, point2, point3], color)
return [render_command]


def render_graph(ctx: IContext, data: Dict[str, Any]):
Expand All @@ -95,6 +100,7 @@ def render_graph(ctx: IContext, data: Dict[str, Any]):
ctx (Context): The current simulation context.
data (dict): The data containing the graph's information.
"""
command_list = []
graph_data = cast(GraphData, data.get('graph_data'))
node_color = graph_data.node_color
node_size = graph_data.node_size
Expand All @@ -103,11 +109,14 @@ def render_graph(ctx: IContext, data: Dict[str, Any]):

for edge_id in ctx.graph.graph.get_edges():
edge = ctx.graph.graph.get_edge(edge_id)
_render_graph_edge(ctx, graph_data, edge, edge_color)
_render_graph_edge(ctx, graph_data, edge, edge_color, command_list)

for node_id in ctx.graph.graph.get_nodes():
node = ctx.graph.graph.get_node(node_id)
_render_graph_node(ctx, node, node_color, node_size, draw_id)
_render_graph_node(ctx, node, node_color, node_size, draw_id, command_list)

return command_list


def render_input_overlay(ctx: IContext, data: Dict[str, Any]):
"""
Expand All @@ -117,14 +126,15 @@ def render_input_overlay(ctx: IContext, data: Dict[str, Any]):
ctx (Context): The current simulation context.
data (dict): The data containing the graph's information.
"""
command_list = []
graph_data = cast(GraphData, data.get('graph_data'))
waiting_agent_name = data.get('_waiting_agent_name', None)
waiting_agent_name: str | None = data.get('_waiting_agent_name', None)
input_options = data.get('_input_options', {})
waiting_user_input = data.get('_waiting_user_input', False)

# Break checker
if waiting_agent_name == None or waiting_user_input == False or input_options == {}:
return
if waiting_agent_name is None or waiting_user_input == False or input_options == {}:
return []

graph = ctx.graph.graph
node_color = graph_data.node_color
Expand All @@ -134,19 +144,22 @@ def render_input_overlay(ctx: IContext, data: Dict[str, Any]):
target_node_id_set = set(input_options.values())

for node in target_node_id_set:
_render_graph_node(ctx, graph.get_node(node), node_color, node_size, draw_id)
_render_graph_node(ctx, graph.get_node(node), node_color, node_size, draw_id, command_list)

active_edges: List[OSMEdge] = []
for edge_id in graph.get_edges():
edge = graph.get_edge(edge_id)
current_waiting_agent = ctx.agent.get_agent(waiting_agent_name)
if (edge.source == current_waiting_agent.current_node_id and edge.target in target_node_id_set):
if edge.source == current_waiting_agent.current_node_id and edge.target in target_node_id_set:
active_edges.append(edge)

for edge in active_edges:
_render_graph_edge(ctx, graph_data, edge, edge_color)
_render_graph_edge(ctx, graph_data, edge, edge_color, command_list)

return command_list

def _render_graph_edge(ctx: IContext, graph_data: GraphData, edge: OSMEdge, color: ColorType):

def _render_graph_edge(ctx: IContext, graph_data: GraphData, edge: OSMEdge, color: ColorType, command_list: List[RenderCommand]):
"""Draw an edge as a curve or straight line based on the linestring."""
source = ctx.graph.graph.get_node(edge.source)
target = ctx.graph.graph.get_node(edge.target)
Expand All @@ -160,16 +173,16 @@ def _render_graph_edge(ctx: IContext, graph_data: GraphData, edge: OSMEdge, colo
edge_line_points[edge.id] = linestring

line_points = edge_line_points[edge.id]
ctx.visual.render_linestring(line_points, color, is_aa=True, perform_culling_test=False)
command_list.append(RenderCommand.linestring(line_points, color, perform_culling_test=False))
else:
ctx.visual.render_line(source.x, source.y, target.x, target.y, color, 2, perform_culling_test=False, is_aa=False)
command_list.append(RenderCommand.line(source.x, source.y, target.x, target.y, color, 2, is_aa=False, perform_culling_test=False))


def _render_graph_node(ctx: IContext, node: Node, color: ColorType, radius: float, draw_id: bool):
ctx.visual.render_circle(node.x, node.y, radius, color)
def _render_graph_node(ctx: IContext, node: Node, color: ColorType, radius: float, draw_id: bool, command_list: List[RenderCommand]):
command_list.append(RenderCommand.circle(node.x, node.y, radius, color))

if draw_id:
ctx.visual.render_text(str(node.id), node.x, node.y + 10, (0, 0, 0))
command_list.append(RenderCommand.text(node.x, node.y + 10, str(node.id), (0, 0, 0)))


def render_neighbor_sensor(ctx: IContext, data: Dict[str, Any]):
Expand All @@ -180,12 +193,15 @@ def render_neighbor_sensor(ctx: IContext, data: Dict[str, Any]):
ctx (Context): The current simulation context.
data (Dict[str, Any]): The data containing the sensor's information.
"""
command_list = []
sensor = ctx.sensor.get_sensor(data.get('name'))
color = data.get('color', Color.Cyan)
sensor_data = cast(List[int], sensor.data)
for neighbor_node_id in sensor_data:
neighbor_node = ctx.graph.graph.get_node(neighbor_node_id)
ctx.visual.render_circle(neighbor_node.x, neighbor_node.y, 2, color)
command_list.append(RenderCommand.circle(neighbor_node.x, neighbor_node.y, 2, color))

return command_list


def render_map_sensor(ctx: IContext, data: Dict[str, Any]):
Expand All @@ -196,6 +212,7 @@ def render_map_sensor(ctx: IContext, data: Dict[str, Any]):
ctx (Context): The current simulation context.
data (Dict[str, Any]): The data containing the sensor's information.
"""
command_list = []
sensor = ctx.sensor.get_sensor(data.get('name'))
node_color = data.get('node_color', Color.Cyan)
sensor_data = cast(Dict[str, Any], sensor.data)
Expand All @@ -204,7 +221,7 @@ def render_map_sensor(ctx: IContext, data: Dict[str, Any]):
sensed_nodes = list(sensed_nodes.keys())
for node_id in sensed_nodes:
node = ctx.graph.graph.get_node(node_id)
ctx.visual.render_circle(node.x, node.y, 1, node_color)
command_list.append(RenderCommand.circle(node.x, node.y, 1, node_color))

edge_color = data.get('edge_color', Color.Cyan)
sensed_edges = sensor_data.get('edges', [])
Expand All @@ -218,9 +235,11 @@ def render_map_sensor(ctx: IContext, data: Dict[str, Any]):
line_points = ([(source.x, source.y)] + [(x, y) for (x, y) in edge.linestring.coords] +
[(target.x, target.y)])

ctx.visual.render_linestring(line_points, edge_color, 4, is_aa=False, perform_culling_test=False)
command_list.append(RenderCommand.linestring(line_points, edge_color, 4, is_aa=False, perform_culling_test=False))
else:
ctx.visual.render_line(source.x, source.y, target.x, target.y, edge_color, 4, perform_culling_test=False)
command_list.append(RenderCommand.line(source.x, source.y, target.x, target.y, edge_color, 4, perform_culling_test=False))

return command_list


def render_agent_sensor(ctx: IContext, data: Dict[str, Any]):
Expand All @@ -231,6 +250,7 @@ def render_agent_sensor(ctx: IContext, data: Dict[str, Any]):
ctx (Context): The current simulation context.
data (Dict[str, Any]): The data containing the sensor's information.
"""
command_list = []
sensor = ctx.sensor.get_sensor(data.get('name'))
color = data.get('color', Color.Cyan)
size = data.get('size', 8)
Expand All @@ -244,4 +264,6 @@ def render_agent_sensor(ctx: IContext, data: Dict[str, Any]):
point2 = (position[0] + size * math.cos(angle + 2.5), position[1] + size * math.sin(angle + 2.5))
point3 = (position[0] + size * math.cos(angle - 2.5), position[1] + size * math.sin(angle - 2.5))

ctx.visual.render_polygon([point1, point2, point3], color)
command_list.append(RenderCommand.polygon([point1, point2, point3], color))

return command_list
8 changes: 6 additions & 2 deletions gamms/VisualizationEngine/no_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
IArtist,
IContext,
IVisualizationEngine,
ColorType
ColorType,
IRenderCommand
)
from gamms.typing.opcodes import OpCodes
from gamms.VisualizationEngine.artist import Artist
Expand Down Expand Up @@ -36,6 +37,9 @@ def add_artist(self, name: str, artist: Union[IArtist, Dict[str, Any]]) -> IArti
def remove_artist(self, name: str):
return

def execute_command_list(self, command_list: List[IRenderCommand]) -> None:
pass

def simulate(self):
if self.ctx.record.record():
self.ctx.record.write(opCode=OpCodes.SIMULATE, data={})
Expand All @@ -59,7 +63,7 @@ def render_circle(self, x: float, y: float, radius: float, color: ColorType = Co
return

def render_line(self, start_x: float, start_y: float, end_x: float, end_y: float, color: ColorType = Color.Black,
width: int=1, is_aa: bool=False, perform_culling_test: bool=True, force_no_aa: bool = False):
width: int=1, is_aa: bool=False, perform_culling_test: bool=True):
return

def render_linestring(self, points: List[Tuple[float, float]], color: ColorType =Color.Black, width: int=1, closed: bool = False,
Expand Down
Loading