Source code for sierra.plugins.platform.argos.variables.cameras

# Copyright 2020 John Harwell, All rights reserved.
#
#  SPDX-License-Identifier: MIT

"""Classes for specifying ARGoS cameras.

Positions, timeline, and interpolation, for manipulating the frame
capture/rendering perspective.

"""

# Core packages
import typing as tp
import math

# 3rd party packages
import implements

# Project packages
from sierra.core.variables.base_variable import IBaseVariable
from sierra.core.utils import ArenaExtent
from sierra.core.experiment import xml
from sierra.core import types, config
from sierra.core.vector import Vector3D
import sierra.plugins.platform.argos.variables.exp_setup as exp


[docs]@implements.implements(IBaseVariable) class QTCameraTimeline(): """Defines when/how to switch between camera perspectives within ARGoS. Attributes: interpolate: Should we interpolate between camera positions on our timeline ? setup: Simulation experiment definitions. extents: List of (X,Y,Zs) tuple of dimensions of arena areas to generate camera definitions for. """ # If this default changes in ARGoS, it will need to be updated here too. kARGOS_N_CAMERAS = 12
[docs] def __init__(self, setup: exp.ExpSetup, cmdline: str, extents: tp.List[ArenaExtent]) -> None: self.cmdline = cmdline self.extents = extents self.setup = setup self.tag_adds = [] # type: tp.List[xml.TagAddList]
[docs] def gen_attr_changelist(self) -> tp.List[xml.AttrChangeSet]: """ No effect. All tags/attributes are either deleted or added. """ return []
[docs] def gen_tag_rmlist(self) -> tp.List[xml.TagRmList]: """Remove the ``<camera>`` tag if it exists. Obviously you *must* call this function BEFORE adding new definitions. """ return [xml.TagRmList(xml.TagRm("./visualization/qt-opengl", "camera"))]
[docs] def gen_tag_addlist(self) -> tp.List[xml.TagAddList]: if not self.tag_adds: adds = xml.TagAddList(xml.TagAdd('./visualization/qt-opengl', 'camera', {}, False), xml.TagAdd("./visualization/qt-opengl/camera", "placements", {}, False)) in_ticks = self.setup.n_secs_per_run * self.setup.n_ticks_per_sec adds.append(xml.TagAdd('.//qt-opengl/camera', 'timeline', { 'loop': str(in_ticks) }, False)) for ext in self.extents: # generate keyframes for switching between camera perspectives self._gen_keyframes(adds, self.kARGOS_N_CAMERAS, in_ticks) info = [] for c in range(0, self.kARGOS_N_CAMERAS): info.append(self._gen_camera_config(ext, c, self.kARGOS_N_CAMERAS)) for index, up, look_at, pos in info: camera = xml.TagAdd('.//camera/placements', 'placement', { 'index': f"{index}", 'up': f"{up.x},{up.y},{up.z}", 'position': f"{pos.x},{pos.y},{pos.z}", 'look_at': f"{look_at.x},{look_at.y},{look_at.z}", }, True) adds.append(camera) self.tag_adds = [adds] return self.tag_adds
[docs] def gen_files(self) -> None: pass
[docs] def _gen_keyframes(self, adds: xml.TagAddList, n_cameras: int, cycle_length: int) -> None: for c in range(0, n_cameras): index = c % n_cameras adds.append(xml.TagAdd('.//qt-opengl/camera/timeline', 'keyframe', { 'placement': str(index), 'step': str(int(cycle_length / n_cameras * c)) }, True )) if 'interp' in self.cmdline and c < n_cameras: adds.append(xml.TagAdd('.//qt-opengl/camera/timeline', 'interpolate', {}, True))
[docs] def _gen_camera_config(self, ext: ArenaExtent, index: int, n_cameras) -> tuple: angle = (index % n_cameras) * (2.0 * math.pi / n_cameras) look_at = Vector3D(ext.xsize() / 2.0, ext.ysize() / 2.0, 0.0) hyp = math.sqrt(2 * max(look_at.x, look_at.y) ** 2) pos_x = (hyp * math.cos(angle) + look_at.x) pos_y = (hyp * math.sin(angle) + look_at.y) pos_z = (max(ext.xsize(), ext.ysize()) * 0.50) pos = Vector3D(pos_x, pos_y, pos_z) # This is what the ARGoS source does for the up vector for the default # camera configuration up = Vector3D(0, 0, 1) return index, up, look_at, pos
[docs]@implements.implements(IBaseVariable) class QTCameraOverhead(): """Defines a single overhead camera perspective within ARGoS. Attributes: extents: List of (X,Y,Z) tuple of dimensions of arena areas to generate camera definitions for. """
[docs] def __init__(self, extents: tp.List[ArenaExtent]) -> None: self.extents = extents self.tag_adds = [] # type: tp.List[xml.TagAddList]
[docs] def gen_attr_changelist(self) -> tp.List[xml.AttrChangeSet]: """No effect. All tags/attributes are either deleted or added. """ return []
[docs] def gen_tag_rmlist(self) -> tp.List[xml.TagRmList]: """Remove the ``<camera>`` tag if it exists. Obviously you *must* call this function BEFORE adding new definitions. """ return [xml.TagRmList(xml.TagRm("./visualization/qt-opengl", "camera"))]
[docs] def gen_tag_addlist(self) -> tp.List[xml.TagAddList]: if not self.tag_adds: adds = xml.TagAddList(xml.TagAdd('./visualization/qt-opengl', 'camera', {}, False), xml.TagAdd("./visualization/qt-opengl/camera", "placements", {}, False)) for ext in self.extents: height = max(ext.xsize(), ext.ysize()) * 0.75 camera = xml.TagAdd('.//camera/placements', 'placement', { 'index': '0', 'position': "{0}, {1}, {2}".format(ext.xsize() / 2.0, ext.ysize() / 2.0, height), 'look_at': "{0}, {1}, 0".format(ext.xsize() / 2.0, ext.ysize() / 2.0), }, True) adds.append(camera) self.tag_adds = [adds] return self.tag_adds
[docs] def gen_files(self) -> None: pass
def factory(cmdopts: types.Cmdopts, extents: tp.List[ArenaExtent]): """Create cameras for a list of arena extents. """ if cmdopts['camera_config'] == 'overhead': return QTCameraOverhead(extents) else: return QTCameraTimeline(exp.factory(cmdopts["exp_setup"])(), # type: ignore cmdopts['camera_config'], extents) __api__ = [ 'QTCameraTimeline', 'QTCameraOverhead', ]