diff --git a/.gitignore b/.gitignore index c413fb7c41..0980cb3856 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,21 @@ -bin/ -build/ -cache/ -tmp/ -ext/SGCT -.DS_Store -*.swp -.vscode - -# Windows system files: -Thumbs.db - -# Emacs backup files: *~ +*.swp +.DS_Store +.vscode +/bin/ +/build/ +/cache/ +Thumbs.db +tmp/ + +/documentation +/doc +/ext/SGCT -# generated glsl files *.gglsl *.GhoulGenerated.glsl +*.OpenSpaceGenerated.glsl + shaders/generated/* # CMake stuff @@ -29,135 +29,67 @@ install_manifest.txt .cproject .project -# Doxygen stuff -html/ -latex/ shaders/ABuffer/constants.hglsl -*.OpenSpaceGenerated.glsl -LuaScripting.txt -Properties.txt log.html -gui/externaltimecontrol/CMakeLists.txt -gui/externaltimecontrol/main.cpp -gui/externaltimecontrol/mainwindow.cpp -gui/externaltimecontrol/mainwindow.h -data/scene/rosetta/67P/obj/67P_rotated_5_130.obj -data/spice/NewHorizonsKernels/ -data/spice/RosettaKernels/ -data/scene/newhorizons/pluto/pluto/textures/ -data/scene/newhorizons/pluto/pluto/utcEvents.txt -data/scene/rosetta/rosetta/obj/mainbodyros.obj -data/scene/rosetta/rosetta/obj/solarpanelleft.obj -data/scene/rosetta/rosetta/obj/solarpanelright.obj -data/scene/rosetta/rosetta/textures/defaultProj.png -data/scene/rosetta/rosetta/textures/glare_blue.png -data/scene/rosetta/rosetta/textures/gray.png -data/scene/rosetta/rosetta/textures/squarefov.png -data/scene/saturn/textures/saturn.jpg -data/scene/stars/colorbv.cmap -data/scene/stars/speck/stars.speck -data/scene/stars/textures/glare.png -data/scene/stars/textures/halo.png -data/scene/sun/textures/marker.png -data/scene/sun/textures/sun-glare.png -data/scene/sun/textures/sun.jpg -data/scene/uranus/textures/uranus.jpg -data/scene/venus/textures/venus.jpg -data/scene/dawn/vestaprojection/VestaComet/VestaComet_5000.obj -data/spice/DawnKernels/ -data/scene/newhorizons/jupiter/jupiter/ProjectionsOfInterest/ -data/scene/rosetta/67P/textures/black.jpg -data/scene/rosetta/67P/textures/defaultProj.png -data/scene/rosetta/67P/textures/gray.jpg -data/scene/rosetta/67P/textures/gray.png -data/scene/rosetta/67P/textures/texmapflipped.jpg -data/scene/rosetta/67P/textures/white.jpg -data/scene/rosetta/67P/textures/white.png -data/scene/newhorizons/jupiter/callisto/textures/callisto.jpg -data/scene/dawn/ceres/textures/gray.png -data/scene/newhorizons/pluto/charon/textures/Charon-Text.png -data/scene/newhorizons/pluto/charon/textures/charon_highres.jpg -data/scene/newhorizons/pluto/charon/textures/charon_highres_annotated.jpg -data/scene/newhorizons/pluto/charon/textures/defaultProj.png -data/scene/dawn/dawn/obj/mainbodydawn.obj -data/scene/dawn/dawn/obj/solarpanelleft.obj -data/scene/dawn/dawn/obj/solarpanelright.obj -data/scene/dawn/dawn/textures/glare_blue.png -data/scene/dawn/dawn/textures/gray.png -data/scene/earth/textures/ToastMapOfEarth.jpg -data/scene/earth/textures/earth_bluemarble.jpg -data/scene/earth/textures/earth_bluemarble_height.jpg -data/scene/earth/textures/earth_night.jpg -data/scene/earth/textures/marker.png -data/scene/earth/textures/earth_clouds.jpg -data/scene/earth/textures/earth_reflectance.png -data/scene/moon/textures/Moon16k.dds -data/scene/newhorizons/jupiter/europa/textures/europa.jpg -data/scene/newhorizons/jupiter/ganymede/textures/ganymede.jpg -data/scene/newhorizons/jupiter/io/textures/io.jpg -data/scene/jupiter/jupiter/textures/jupiter.jpg -data/scene/mars/textures/mars.jpg -data/scene/mercury/textures/mercury.jpg -data/scene/milkyway/textures/DarkUniverse_mellinger_8k.jpg -data/scene/neptune/textures/neptune.jpg -data/scene/newhorizons/newhorizons/models/Labels.obj -data/scene/newhorizons/newhorizons/models/NewHorizonsCleanModel.obj -data/scene/newhorizons/newhorizons/textures/NHTexture.jpg -data/scene/newhorizons/newhorizons/textures/goldfoilbump.tif -data/scene/newhorizons/newhorizons/textures/labels.png -data/scene/pluto/textures/ -data/scene/pluto/textures/Shenk_180.jpg -data/scene/pluto/textures/pluto_highres_180.jpg -data/scene/newhorizons/pluto/pluto/assets/core_v9h_obs_getmets_v8_time_fix_nofrcd_mld.txt -data/scene/newhorizons/pluto/pluto/textures/3.jpg -data/scene/newhorizons/pluto/pluto/textures/Pluto-Text.png -data/scene/dawn/vestaprojection/VestaComet/VestaComet.mtl -data/scene/dawn/vestaprojection/textures/defaultProj_backup.png -data/scene/dawn/vestaprojection/textures/dummy.jpg -data/scene/dawn/vestaprojection/textures/glare.png -data/scene/dawn/vestaprojection/textures/projectMe.png -data/spice/MAR063.BSP -data/spice/de430_1850-2150.bsp -data/spice/jup260.bsp -data/BATSRUS.cdf -data/ENLIL.cdf -data/scene/newhorizons/pluto/pluto/images -data/spice/nh_kernels/ -data/scene/jupiter/jupiter/textures/Jupiter-text.png -data/scene/jupiter/callisto/textures/callisto.jpg -data/scene/jupiter/europa/textures/europa.jpg -data/scene/jupiter/ganymede/textures/ganymede.jpg -data/scene/jupiter/io/textures/io.jpg -data/scene/milkyway-eso/textures/eso0932a_blend.png -data/scene/stars-denver/denver_colorbv.cmap -data/scene/stars-denver/speck/stars.speck -data/scene/stars-denver/textures/halo.png -data/scene/newhorizons/pluto/pluto/full_images/ -data/scene/rosetta/67P/rosettaimages/ -data/spice/RosettaKernels_New/ -data/scene/newhorizons/pluto/charon/utcEvents.txt -data/scene/rosetta/67P/obj/67P_HD_2015-05-09.obj -data/scene/rosetta/67P/obj/may9_map.jpg -data/scene/rosetta/67P/textures/may9_map.jpg -data/scene/newhorizons/pluto/charon/textures/cpdem-Mcolor2-MLorriCA-lr-5_ZMfs-cyl.jpg -data/scene/newhorizons/pluto/charon/textures/cpmap_cyl_HR_0e.jpg -data/scene/volumetricmilkyway/milkyway/ ScriptLog.txt -data/scene/atmosphereearth/textures/ToastMapOfEarth.jpg -data/scene/atmosphereearth/textures/earth_bluemarble.jpg -data/scene/atmosphereearth/textures/earth_bluemarble_height.jpg -data/scene/atmosphereearth/textures/earth_clouds.jpg -data/scene/atmosphereearth/textures/earth_night.jpg -data/scene/atmosphereearth/textures/earth_reflectance.png -data/scene/atmosphereearth/textures/marker.png -data/scene/juno/juno/textures -data/scene/juno/juno/spice + +data/scene/atmosphereearth/textures +data/scene/dawn/ceres/textures +data/scene/dawn/dawn/obj +data/scene/dawn/dawn/textures +data/scene/dawn/vestaprojection/textures +data/scene/dawn/vestaprojection/VestaComet +data/scene/debugglobe/textures +data/scene/earth/textures data/scene/juno/juno/Juno.mtl data/scene/juno/juno/Juno.obj -KeyboardMapping.txt -saturn_rings.png -data/scene/debugglobe/textures/earth_clouds.jpg -data/scene/debugglobe/textures/earth_reflectance.png -data/scene/rosetta/rosetta/obj/Rosetta.obj -data/scene/rosetta/rosetta/rosetta/ -data/scene/rosetta/rosetta/textures/ +data/scene/juno/juno/spice +data/scene/juno/juno/textures +data/scene/jupiter/callisto/textures +data/scene/jupiter/europa/textures +data/scene/jupiter/ganymede/textures +data/scene/jupiter/io/textures +data/scene/jupiter/jupiter/textures +data/scene/mars/textures +data/scene/mercury/textures +data/scene/milkyway/textures +data/scene/milkyway-eso/textures +data/scene/moon/textures +data/scene/neptune/textures +data/scene/newhorizons/jupiter/callisto/textures +data/scene/newhorizons/jupiter/europa/textures +data/scene/newhorizons/jupiter/ganymede/textures +data/scene/newhorizons/jupiter/io/textures +data/scene/newhorizons/jupiter/jupiter/ProjectionsOfInterest +data/scene/newhorizons/newhorizons/models +data/scene/newhorizons/newhorizons/textures +data/scene/newhorizons/pluto/charon/textures +data/scene/newhorizons/pluto/pluto/assets +data/scene/newhorizons/pluto/pluto/full_images +data/scene/newhorizons/pluto/pluto/images +data/scene/newhorizons/pluto/pluto/textures +data/scene/pluto/textures +data/scene/saturn/textures +data/scene/rosetta/67P/obj +data/scene/rosetta/67P/rosettaimages +data/scene/rosetta/67P/textures +data/scene/rosetta/rosetta/rosetta +data/scene/rosetta/rosetta/textures +data/scene/stars/colorbv.cmap +data/scene/stars/speck +data/scene/stars/textures +data/scene/stars-denver/denver_colorbv.cmap +data/scene/stars-denver/speck +data/scene/stars-denver/textures +data/scene/sun/textures +data/scene/uranus/textures +data/scene/venus/textures +data/scene/volumetricmilkyway/milkyway +data/spice/DawnKernels +data/spice/jup260.bsp +data/spice/de430_1850-2150.bsp +data/spice/MAR063.BSP +data/spice/NewHorizonsKernels +data/spice/nh_kernels +data/spice/Rosetta + diff --git a/Doxyfile b/Doxyfile index f2d2cc9a81..5217764619 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,45 +1,19 @@ -# Doxyfile 1.7.6.1 +# Doxyfile 1.8.8 #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- - + DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = OpenSpace PROJECT_NUMBER = PROJECT_LOGO = OUTPUT_DIRECTORY = doc/ -CREATE_SUBDIRS = NO -OUTPUT_LANGUAGE = English -BRIEF_MEMBER_DESC = YES -REPEAT_BRIEF = YES ALWAYS_DETAILED_SEC = YES -INLINE_INHERITED_MEMB = NO -FULL_PATH_NAMES = YES JAVADOC_AUTOBRIEF = YES QT_AUTOBRIEF = YES - -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". -# You can put \n's in the value part of an alias to insert newlines. - -ALIASES = -BUILTIN_STL_SUPPORT = NO - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and -# unions are shown inside the group in which they are included (e.g. using -# @ingroup) instead of on a separate page (for HTML and Man pages) or -# section (for LaTeX and RTF). - -INLINE_GROUPED_CLASSES = NO INLINE_SIMPLE_STRUCTS = YES -TYPEDEF_HIDES_STRUCT = NO -LOOKUP_CACHE_SIZE = 1 +LOOKUP_CACHE_SIZE = 9 #--------------------------------------------------------------------------- # Build related configuration options @@ -48,405 +22,83 @@ LOOKUP_CACHE_SIZE = 1 EXTRACT_ALL = YES EXTRACT_PRIVATE = YES EXTRACT_STATIC = YES -EXTRACT_LOCAL_CLASSES = YES -EXTRACT_LOCAL_METHODS = NO -EXTRACT_ANON_NSPACES = NO -HIDE_IN_BODY_DOCS = NO -INTERNAL_DOCS = NO CASE_SENSE_NAMES = NO HIDE_SCOPE_NAMES = YES SHOW_INCLUDE_FILES = YES -FORCE_LOCAL_INCLUDES = NO INLINE_INFO = NO -SORT_MEMBER_DOCS = YES SORT_BRIEF_DOCS = YES SORT_MEMBERS_CTORS_1ST = YES -SORT_GROUP_NAMES = NO -SORT_BY_SCOPE_NAME = NO -STRICT_PROTO_MATCHING = NO -GENERATE_TODOLIST = YES +GENERATE_TODOLIST = NO GENERATE_TESTLIST = NO -GENERATE_BUGLIST = YES +GENERATE_BUGLIST = NO GENERATE_DEPRECATEDLIST= YES MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = NO -SHOW_FILES = NO -SHOW_NAMESPACES = YES +SHOW_FILES = YES LAYOUT_FILE = +EXTENSION_MAPPING = inl=C++ +BUILTIN_STL_SUPPORT = YES #--------------------------------------------------------------------------- -# configuration options related to warning and progress messages +# Configuration options related to warning and progress messages #--------------------------------------------------------------------------- QUIET = YES -WARNINGS = YES -WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = YES WARN_FORMAT = "$file:$line: $text" #--------------------------------------------------------------------------- -# configuration options related to the input files +# Configuration options related to the input files #--------------------------------------------------------------------------- INPUT = src \ include INPUT_ENCODING = UTF-8 -FILE_PATTERNS = *.c \ - *.cpp \ +FILE_PATTERNS = *.cpp \ *.h \ *.inl RECURSIVE = YES -EXCLUDE = -EXCLUDE_SYMLINKS = NO -EXCLUDE_PATTERNS = /ext/* bin/* build/* config/* gui/* kernels/* openspace-data/* scripts/* shaders/* -EXCLUDE_SYMBOLS = +EXCLUDE_PATTERNS = ext/* bin/* build/* config/* gui/* kernels/* data/* scripts/* shaders/* +USE_MDFILE_AS_MAINPAGE = README.md #--------------------------------------------------------------------------- -# configuration options related to source browsing +# Configuration options related to source browsing #--------------------------------------------------------------------------- SOURCE_BROWSER = YES -INLINE_SOURCES = NO -STRIP_CODE_COMMENTS = YES -REFERENCED_BY_RELATION = NO -REFERENCES_RELATION = NO -REFERENCES_LINK_SOURCE = YES VERBATIM_HEADERS = NO #--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index +# Configuration options related to the HTML output #--------------------------------------------------------------------------- -ALPHABETICAL_INDEX = YES -COLS_IN_ALPHA_INDEX = 4 -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- - -GENERATE_HTML = YES -HTML_OUTPUT = html -HTML_FILE_EXTENSION = .html HTML_HEADER = support/doxygen/header.html HTML_FOOTER = support/doxygen/footer.html HTML_STYLESHEET = support/doxygen/stylesheet.css -#HTML_EXTRA_FILES = -HTML_COLORSTYLE_HUE = 200 -HTML_COLORSTYLE_SAT = 100 -HTML_COLORSTYLE_GAMMA = 80 -HTML_TIMESTAMP = NO -HTML_DYNAMIC_SECTIONS = NO -# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. -GENERATE_DOCSET = NO -DOCSET_FEEDNAME = -DOCSET_BUNDLE_ID = -DOCSET_PUBLISHER_ID = -DOCSET_PUBLISHER_NAME = -GENERATE_HTMLHELP = NO -CHM_FILE = -HHC_LOCATION = -GENERATE_CHI = NO -CHM_INDEX_ENCODING = -BINARY_TOC = NO -TOC_EXPAND = NO -DISABLE_INDEX = YES +HTML_COLORSTYLE_HUE = 225 +HTML_COLORSTYLE_SAT = 18 +HTML_COLORSTYLE_GAMMA = 100 GENERATE_TREEVIEW = YES ENUM_VALUES_PER_LINE = 6 TREEVIEW_WIDTH = 300 -EXT_LINKS_IN_WINDOW = NO -FORMULA_FONTSIZE = 10 -FORMULA_TRANSPARENT = YES -USE_MATHJAX = NO -MATHJAX_RELPATH = http://www.mathjax.org/mathjax -MATHJAX_EXTENSIONS = -SEARCHENGINE = YES -SERVER_BASED_SEARCH = NO #--------------------------------------------------------------------------- -# configuration options related to the LaTeX output +# Other output configuration options #--------------------------------------------------------------------------- - GENERATE_LATEX = NO -LATEX_OUTPUT = latex -LATEX_CMD_NAME = latex -MAKEINDEX_CMD_NAME = makeindex -COMPACT_LATEX = NO -PAPER_TYPE = a4wide -EXTRA_PACKAGES = -LATEX_HEADER = -LATEX_FOOTER = -PDF_HYPERLINKS = YES -USE_PDFLATEX = YES -LATEX_BATCHMODE = NO -LATEX_HIDE_INDICES = NO -LATEX_SOURCE_CODE = NO -LATEX_BIB_STYLE = plain - -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- - GENERATE_RTF = NO -RTF_OUTPUT = rtf -COMPACT_RTF = NO -RTF_HYPERLINKS = NO -RTF_STYLESHEET_FILE = -RTF_EXTENSIONS_FILE = - -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- - GENERATE_MAN = NO -MAN_OUTPUT = man -MAN_EXTENSION = .3 -MAN_LINKS = NO - -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- - GENERATE_XML = NO -XML_OUTPUT = xml -XML_SCHEMA = -XML_DTD = -XML_PROGRAMLISTING = YES - -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- - GENERATE_AUTOGEN_DEF = NO - -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- - GENERATE_PERLMOD = NO -PERLMOD_LATEX = NO -PERLMOD_PRETTY = YES -PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- -ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES -SEARCH_INCLUDES = YES -INCLUDE_PATH = -INCLUDE_FILE_PATTERNS = -PREDEFINED = EXPAND_AS_DEFINED = YES SKIP_FUNCTION_MACROS = NO - -#--------------------------------------------------------------------------- -# Configuration::additions related to external references -#--------------------------------------------------------------------------- - -TAGFILES = -GENERATE_TAGFILE = -ALLEXTERNALS = NO -EXTERNAL_GROUPS = YES -PERL_PATH = /usr/bin/perl - -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- - -CLASS_DIAGRAMS = YES -MSCGEN_PATH = -HIDE_UNDOC_RELATIONS = YES - -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz, a graph visualization -# toolkit from AT&T and Lucent Bell Labs. The other options in this section -# have no effect if this option is set to NO (the default) - -HAVE_DOT = NO - -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is -# allowed to run in parallel. When set to 0 (the default) doxygen will -# base this on the number of processors available in the system. You can set it -# explicitly to a value larger than 0 to get control over the balance -# between CPU load and processing speed. - -DOT_NUM_THREADS = 0 - -# By default doxygen will use the Helvetica font for all dot files that -# doxygen generates. When you want a differently looking font you can specify -# the font name using DOT_FONTNAME. You need to make sure dot is able to find -# the font, which can be done by putting it in a standard location or by setting -# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the -# directory containing the font. - -DOT_FONTNAME = Helvetica - -# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. -# The default size is 10pt. - -DOT_FONTSIZE = 10 - -# By default doxygen will tell dot to use the Helvetica font. -# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to -# set the path where dot can find it. - -DOT_FONTPATH = - -# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect inheritance relations. Setting this tag to YES will force the -# CLASS_DIAGRAMS tag to NO. - -CLASS_GRAPH = YES - -# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect implementation dependencies (inheritance, containment, and -# class references variables) of the class with other documented classes. - -COLLABORATION_GRAPH = YES - -# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for groups, showing the direct groups dependencies - -GROUP_GRAPHS = YES - -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling -# Language. - -UML_LOOK = NO - -# If set to YES, the inheritance and collaboration graphs will show the -# relations between templates and their instances. - -TEMPLATE_RELATIONS = NO - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT -# tags are set to YES then doxygen will generate a graph for each documented -# file showing the direct and indirect include dependencies of the file with -# other documented files. - -INCLUDE_GRAPH = YES - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and -# HAVE_DOT tags are set to YES then doxygen will generate a graph for each -# documented header file showing the documented files that directly or -# indirectly include this file. - -INCLUDED_BY_GRAPH = YES - -# If the CALL_GRAPH and HAVE_DOT options are set to YES then -# doxygen will generate a call dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable call graphs -# for selected functions only using the \callgraph command. - -CALL_GRAPH = NO - -# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then -# doxygen will generate a caller dependency graph for every global function -# or class method. Note that enabling this option will significantly increase -# the time of a run. So in most cases it will be better to enable caller -# graphs for selected functions only using the \callergraph command. - -CALLER_GRAPH = NO - -# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen -# will generate a graphical hierarchy of all classes instead of a textual one. - -GRAPHICAL_HIERARCHY = YES - -# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES -# then doxygen will show the dependencies a directory has on other directories -# in a graphical way. The dependency relations are determined by the #include -# relations between the files in the directories. - -DIRECTORY_GRAPH = YES - -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. Possible values are svg, png, jpg, or gif. -# If left blank png will be used. If you choose svg you need to set -# HTML_FILE_EXTENSION to xhtml in order to make the SVG files -# visible in IE 9+ (other browsers do not have this requirement). - -DOT_IMAGE_FORMAT = png - -# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to -# enable generation of interactive SVG images that allow zooming and panning. -# Note that this requires a modern browser other than Internet Explorer. -# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you -# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files -# visible. Older versions of IE do not have SVG support. - -INTERACTIVE_SVG = NO - -# The tag DOT_PATH can be used to specify the path where the dot tool can be -# found. If left blank, it is assumed the dot tool can be found in the path. - -DOT_PATH = - -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the -# \dotfile command). - -DOTFILE_DIRS = - -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the -# \mscfile command). - -MSCFILE_DIRS = - -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of -# nodes that will be shown in the graph. If the number of nodes in a graph -# becomes larger than this value, doxygen will truncate the graph, which is -# visualized by representing a node as a red box. Note that doxygen if the -# number of direct children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note -# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. - -DOT_GRAPH_MAX_NODES = 50 - -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the -# graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes -# that lay further from the root node will be omitted. Note that setting this -# option to 1 or 2 may greatly reduce the computation time needed for large -# code bases. Also note that the size of a graph can be further restricted by -# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. - -MAX_DOT_GRAPH_DEPTH = 0 - -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not -# seem to support this out of the box. Warning: Depending on the platform used, -# enabling this option may lead to badly anti-aliased labels on the edges of -# a graph (i.e. they become hard to read). - -DOT_TRANSPARENT = NO - -# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) -# support this, this feature is disabled by default. - -DOT_MULTI_TARGETS = NO - -# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will -# generate a legend page explaining the meaning of the various boxes and -# arrows in the dot generated graphs. - -GENERATE_LEGEND = YES - -# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will -# remove the intermediate dot files that are used to generate -# the various graphs. - -DOT_CLEANUP = YES diff --git a/config/sgct/VRArenaSimCenter.xml b/config/sgct/VRArenaSimCenter.xml index 3ce0236e5f..f57ec51789 100644 --- a/config/sgct/VRArenaSimCenter.xml +++ b/config/sgct/VRArenaSimCenter.xml @@ -1,82 +1,82 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/sgct/single.xml b/config/sgct/single.xml index 62aea727b1..7b20bcb10c 100644 --- a/config/sgct/single.xml +++ b/config/sgct/single.xml @@ -1,24 +1,24 @@ - - - - - - - + + + + + + + - - - + + + - - - - - - + + + + + + diff --git a/config/sgct/single_4k.xml b/config/sgct/single_4k.xml new file mode 100644 index 0000000000..cb4dd6339c --- /dev/null +++ b/config/sgct/single_4k.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/sgct/single_fisheye.xml b/config/sgct/single_fisheye.xml index b72758eb6c..b9bc7415dd 100644 --- a/config/sgct/single_fisheye.xml +++ b/config/sgct/single_fisheye.xml @@ -1,37 +1,37 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/config/sgct/single_gui.xml b/config/sgct/single_gui.xml index e0d1b6bfa8..a3956e40f6 100644 --- a/config/sgct/single_gui.xml +++ b/config/sgct/single_gui.xml @@ -1,37 +1,37 @@ - - - - - - - + + + + + + + - - - + + + - - - - - + + + + + - - - + + + - - - - - - + + + + + + diff --git a/config/sgct/single_sbs_stereo.xml b/config/sgct/single_sbs_stereo.xml index ab3d4826fb..d92f95ed66 100644 --- a/config/sgct/single_sbs_stereo.xml +++ b/config/sgct/single_sbs_stereo.xml @@ -1,21 +1,21 @@ - - - - - - - - + + + + + + + + - - - - - - + + + + + + \ No newline at end of file diff --git a/config/sgct/single_stereo.xml b/config/sgct/single_stereo.xml index 71f83845c3..8497af6188 100644 --- a/config/sgct/single_stereo.xml +++ b/config/sgct/single_stereo.xml @@ -1,25 +1,25 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/sgct/single_two_win.xml b/config/sgct/single_two_win.xml index 7286afb127..4cd802423a 100644 --- a/config/sgct/single_two_win.xml +++ b/config/sgct/single_two_win.xml @@ -1,40 +1,40 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/sgct/two_nodes.xml b/config/sgct/two_nodes.xml index 456976ebe5..9ec00bf7bc 100644 --- a/config/sgct/two_nodes.xml +++ b/config/sgct/two_nodes.xml @@ -1,44 +1,44 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/scene/atmosphereearth/atmosphereearth.mod b/data/scene/atmosphereearth/atmosphereearth.mod index 7ab2b2121e..166a0bdbad 100644 --- a/data/scene/atmosphereearth/atmosphereearth.mod +++ b/data/scene/atmosphereearth/atmosphereearth.mod @@ -129,7 +129,8 @@ return { Size = {3.0, 11.0}, Origin = "Center", Billboard = true, - Texture = "textures/marker.png" + Texture = "textures/marker.png", + BlendMode = "Additive" }, Ephemeris = { Type = "Static", diff --git a/data/scene/earth/earth.mod b/data/scene/earth/earth.mod index ca3488ede6..0456dfaa13 100644 --- a/data/scene/earth/earth.mod +++ b/data/scene/earth/earth.mod @@ -76,7 +76,12 @@ return { Size = {3.0, 11.0}, Origin = "Center", Billboard = true, - Texture = "textures/marker.png" + Texture = "textures/marker.png", + BlendMode = "Additive" + }, + Ephemeris = { + Type = "Static", + Position = {0, 0, 0, 5} } } ]] diff --git a/data/scene/newhorizons/jupiter/callisto/callisto.mod b/data/scene/newhorizons/jupiter/callisto/callisto.mod index 81fbdb7005..44bae2484b 100644 --- a/data/scene/newhorizons/jupiter/callisto/callisto.mod +++ b/data/scene/newhorizons/jupiter/callisto/callisto.mod @@ -22,6 +22,7 @@ return { Observer = "NEW HORIZONS", Target = "CALLISTO", Aberration = "NONE", + AspectRatio = 2 }, Instrument = { Name = "NH_LORRI", @@ -78,7 +79,8 @@ return { Size = {1.0, 7.4}, Origin = "Center", Billboard = true, - Texture = "textures/Callisto-Text.png" + Texture = "textures/Callisto-Text.png", + BlendMode = "Additive" }, --[[ Ephemeris = { diff --git a/data/scene/newhorizons/jupiter/europa/europa.mod b/data/scene/newhorizons/jupiter/europa/europa.mod index cc8fed3967..a3e9b4a882 100644 --- a/data/scene/newhorizons/jupiter/europa/europa.mod +++ b/data/scene/newhorizons/jupiter/europa/europa.mod @@ -22,6 +22,7 @@ return { Observer = "NEW HORIZONS", Target = "EUROPA", Aberration = "NONE", + AspectRatio = 2 }, Instrument = { Name = "NH_LORRI", @@ -78,7 +79,8 @@ return { Size = {1.0, 7.4}, Origin = "Center", Billboard = true, - Texture = "textures/Europa-Text.png" + Texture = "textures/Europa-Text.png", + BlendMode = "Additive" }, --[[ Ephemeris = { diff --git a/data/scene/newhorizons/jupiter/ganymede/ganymede.mod b/data/scene/newhorizons/jupiter/ganymede/ganymede.mod index 1ef503e6fb..11fcfe432b 100644 --- a/data/scene/newhorizons/jupiter/ganymede/ganymede.mod +++ b/data/scene/newhorizons/jupiter/ganymede/ganymede.mod @@ -22,6 +22,7 @@ return { Observer = "NEW HORIZONS", Target = "GANYMEDE", Aberration = "NONE", + AspectRatio = 2 }, Instrument = { Name = "NH_LORRI", @@ -78,7 +79,8 @@ return { Size = {1.0, 7.4}, Origin = "Center", Billboard = true, - Texture = "textures/Ganymede-Text.png" + Texture = "textures/Ganymede-Text.png", + BlendMode = "Additive" }, Transform = { Translation = { diff --git a/data/scene/newhorizons/jupiter/io/io.mod b/data/scene/newhorizons/jupiter/io/io.mod index 553ae825f5..76615815b8 100644 --- a/data/scene/newhorizons/jupiter/io/io.mod +++ b/data/scene/newhorizons/jupiter/io/io.mod @@ -22,6 +22,7 @@ return { Observer = "NEW HORIZONS", Target = "IO", Aberration = "NONE", + AspectRatio = 2 }, Instrument = { Name = "NH_LORRI", @@ -78,7 +79,8 @@ return { Size = {1.0, 7.4}, Origin = "Center", Billboard = true, - Texture = "textures/Io-Text.png" + Texture = "textures/Io-Text.png", + BlendMode = "Additive" }, Transform = { Translation = { diff --git a/data/scene/newhorizons/jupiter/jupiter/jupiter.mod b/data/scene/newhorizons/jupiter/jupiter/jupiter.mod index c8076e913e..fd8222df3b 100644 --- a/data/scene/newhorizons/jupiter/jupiter/jupiter.mod +++ b/data/scene/newhorizons/jupiter/jupiter/jupiter.mod @@ -41,6 +41,7 @@ return { Observer = "NEW HORIZONS", Target = "JUPITER", Aberration = "NONE", + AspectRatio = 2 }, DataInputTranslation = { Instrument = { @@ -106,7 +107,8 @@ return { Size = {1.0, 7.5}, Origin = "Center", Billboard = true, - Texture = "textures/Jupiter-text.png" + Texture = "textures/Jupiter-text.png", + BlendMode = "Additive" }, Transform = { Translation = { diff --git a/data/scene/newhorizons/pluto/charon/charon.mod b/data/scene/newhorizons/pluto/charon/charon.mod index 1e51526c97..8fbe1de3e5 100644 --- a/data/scene/newhorizons/pluto/charon/charon.mod +++ b/data/scene/newhorizons/pluto/charon/charon.mod @@ -40,6 +40,7 @@ return { Observer = "NEW HORIZONS", Target = "CHARON", Aberration = "NONE", + AspectRatio = 2 }, Instrument = { Name = "NH_LORRI", @@ -93,7 +94,8 @@ return { Size = {1.0, 6.3}, Origin = "Center", Billboard = true, - Texture = "textures/Charon-Text.png" + Texture = "textures/Charon-Text.png", + BlendMode = "Additive" }, Transform = { Translation = { diff --git a/data/scene/newhorizons/pluto/pluto/pluto.mod b/data/scene/newhorizons/pluto/pluto/pluto.mod index ab6abd4dff..59fe90b53b 100644 --- a/data/scene/newhorizons/pluto/pluto/pluto.mod +++ b/data/scene/newhorizons/pluto/pluto/pluto.mod @@ -67,6 +67,7 @@ return { Observer = "NEW HORIZONS", Target = "PLUTO", Aberration = "NONE", + AspectRatio = 2 }, DataInputTranslation = { Instrument = { @@ -223,7 +224,8 @@ return { Size = {1.0, 6.3}, Origin = "Center", Billboard = true, - Texture = "textures/Pluto-Text.png" + Texture = "textures/Pluto-Text.png", + BlendMode = "Additive" }, Transform = { Translation = { diff --git a/data/scene/newhorizons/pluto/styx/styx.mod b/data/scene/newhorizons/pluto/styx/styx.mod index b53af6a70c..ec081220b2 100644 --- a/data/scene/newhorizons/pluto/styx/styx.mod +++ b/data/scene/newhorizons/pluto/styx/styx.mod @@ -66,7 +66,8 @@ return { Size = {1.0, 6.3}, Origin = "Center", Billboard = true, - Texture = "textures/Styx-Text.png" + Texture = "textures/Styx-Text.png", + BlendMode = "Additive" }, Transform = { Translation = { diff --git a/data/scene/osirisrex/bennu/bennu.mod b/data/scene/osirisrex/bennu/bennu.mod index 50367253ff..33722c7ca9 100644 --- a/data/scene/osirisrex/bennu/bennu.mod +++ b/data/scene/osirisrex/bennu/bennu.mod @@ -51,6 +51,7 @@ return { Observer = "OSIRIS-REX", Target = BENNU_BODY, Aberration = "NONE", + AspectRatio = 2 }, DataInputTranslation = { Instruments = { diff --git a/data/scene/rosetta.scene b/data/scene/rosetta.scene index 4701468832..befa637e83 100644 --- a/data/scene/rosetta.scene +++ b/data/scene/rosetta.scene @@ -9,8 +9,16 @@ function preInitialization() openspace.spice.loadKernel("${SPICE}/naif0011.tls") openspace.spice.loadKernel("${SPICE}/pck00010.tpc") - openspace.time.setTime("2014-08-15T03:05:18.101") - -- openspace.time.setTime("2014-11-17T03:05:18.101") + -- Usual start + openspace.time.setTime("2014-08-01T03:05:18.101") + + -- Philae release + -- openspace.time.setTime("2014-11-12T08:00:00.000") + + -- Shadow flyby + -- openspace.time.setTime("2015-02-14T12:00:00.000") + + -- openspace.time.setTime("2015-07-29T06:02:10.000") -- openspace.time.setTime("2014 AUG 21 18:00:00") -- openspace.time.setTime("2015 SEP 10 19:39:00") @@ -50,7 +58,7 @@ return { "venus", "earth", "mars", - "jupiter", + "jupiter/jupiter", "saturn", "uranus", "neptune", diff --git a/data/scene/rosetta/67P/67P.data b/data/scene/rosetta/67P/67P.data index 3c899a9480..57f10bc7d3 100644 --- a/data/scene/rosetta/67P/67P.data +++ b/data/scene/rosetta/67P/67P.data @@ -5,6 +5,5 @@ return { }, TorrentFiles = { { File = "67P_rotated_5_130.obj.torrent", Destination = "obj" }, - { File = "RosettaKernels.torrent", Destination = "${SPICE}" } } } \ No newline at end of file diff --git a/data/scene/rosetta/67P/67P.mod b/data/scene/rosetta/67P/67P.mod index 296d2f5c53..d28d3874ff 100644 --- a/data/scene/rosetta/67P/67P.mod +++ b/data/scene/rosetta/67P/67P.mod @@ -9,28 +9,6 @@ return { Body = "CHURYUMOV-GERASIMENKO", Reference = "GALACTIC", Observer = "SUN", - Kernels = { - --needed - "${OPENSPACE_DATA}/spice/de430_1850-2150.bsp", - -- SPK - --long term orbits loaded first - '${OPENSPACE_DATA}/spice/RosettaKernels_New/SPK/LORL_DL_009_02____P__00268.BSP', - '${OPENSPACE_DATA}/spice/RosettaKernels_New/SPK/RORL_DL_009_02____P__00268.BSP', - '${OPENSPACE_DATA}/spice/RosettaKernels_New/SPK/CORL_DL_009_02____P__00268.BSP', - - '${OPENSPACE_DATA}/spice/RosettaKernels/SPK/LORL_DL_006_01____H__00156.BSP', - '${OPENSPACE_DATA}/spice/RosettaKernels/SPK/RORL_DL_006_01____H__00156.BSP', - '${OPENSPACE_DATA}/spice/RosettaKernels/SPK/CORL_DL_006_01____H__00156.BSP', - - --Jan 2014 - May 2015 (version match with 00162 ck files) - "${OPENSPACE_DATA}/spice/RosettaKernels/SPK/CORB_DV_097_01_______00162.BSP", - "${OPENSPACE_DATA}/spice/RosettaKernels/SPK/RORB_DV_097_01_______00162.BSP", - "${OPENSPACE_DATA}/spice/RosettaKernels/SPK/LORB_DV_097_01_______00162.BSP", - - "${OPENSPACE_DATA}/spice/RosettaKernels_New/SPK/CORB_DV_211_01_______00288.BSP", - "${OPENSPACE_DATA}/spice/RosettaKernels_New/SPK/RORB_DV_211_01_______00288.BSP", - "${OPENSPACE_DATA}/spice/RosettaKernels_New/SPK/LORB_DV_211_01_______00288.BSP", - } }, }, GuiName = "/Solar/67PBarycenter", @@ -50,6 +28,7 @@ return { Textures = { Type = "simple", Color = "textures/gray.jpg", + -- Color = "textures/may9_map.jpg", Project = "textures/defaultProj.png", Default = "textures/defaultProj.png" }, @@ -63,7 +42,8 @@ return { Observer = "ROSETTA", Target = "CHURYUMOV-GERASIMENKO", Aberration = "NONE", - TextureMap = true + TextureMap = true, + ShadowMap = true }, DataInputTranslation = { Instrument = { @@ -98,10 +78,9 @@ return { Method = "ELLIPSOID", Aberration = "NONE", Fovy = 5.00, - Aspect = 1, - Near = 0.01, - Far = 1000000, + Aspect = 1 }, + BoundingSphereRadius = 5000.0 }, Transform = { Rotation = { diff --git a/data/scene/rosetta/67P/RosettaKernels.torrent b/data/scene/rosetta/67P/RosettaKernels.torrent deleted file mode 100644 index be150ad729..0000000000 Binary files a/data/scene/rosetta/67P/RosettaKernels.torrent and /dev/null differ diff --git a/data/scene/rosetta/rosetta/Rosetta.torrent b/data/scene/rosetta/rosetta/Rosetta.torrent new file mode 100644 index 0000000000..c714826b8c Binary files /dev/null and b/data/scene/rosetta/rosetta/Rosetta.torrent differ diff --git a/data/scene/rosetta/rosetta/RosettaKernels.torrent b/data/scene/rosetta/rosetta/RosettaKernels.torrent deleted file mode 100644 index be150ad729..0000000000 Binary files a/data/scene/rosetta/rosetta/RosettaKernels.torrent and /dev/null differ diff --git a/data/scene/rosetta/rosetta/RosettaKernels_New.torrent b/data/scene/rosetta/rosetta/RosettaKernels_New.torrent deleted file mode 100644 index cdb32f8b5d..0000000000 Binary files a/data/scene/rosetta/rosetta/RosettaKernels_New.torrent and /dev/null differ diff --git a/data/scene/rosetta/rosetta/rosetta.data b/data/scene/rosetta/rosetta/rosetta.data index f49beed4ff..fbef76bd99 100644 --- a/data/scene/rosetta/rosetta/rosetta.data +++ b/data/scene/rosetta/rosetta/rosetta.data @@ -4,7 +4,6 @@ return { { Identifier = "rosetta_textures", Destination = "textures", Version = 2 } }, TorrentFiles = { - { File = "RosettaKernels.torrent", Destination = "${SPICE}" }, - { File = "RosettaKernels_New.torrent", Destination = "${SPICE}" } + { File = "Rosetta.torrent", Destination = "${SPICE}" }, } } \ No newline at end of file diff --git a/data/scene/rosetta/rosetta/rosetta.mod b/data/scene/rosetta/rosetta/rosetta.mod index 0796af4c13..70572026ac 100644 --- a/data/scene/rosetta/rosetta/rosetta.mod +++ b/data/scene/rosetta/rosetta/rosetta.mod @@ -1,58 +1,61 @@ RosettaKernels = { - --needed - "${OPENSPACE_DATA}/spice/de430_1850-2150.bsp", - -- SPK - --long term orbits loaded first - -- '${OPENSPACE_DATA}/spice/RosettaKernels/SPK/LORL_DL_006_01____H__00156.BSP', - -- '${OPENSPACE_DATA}/spice/RosettaKernels/SPK/RORL_DL_006_01____H__00156.BSP', - -- '${OPENSPACE_DATA}/spice/RosettaKernels/SPK/CORL_DL_006_01____H__00156.BSP', + "${OPENSPACE_DATA}/spice/Rosetta/SCLK/ROS_160718_STEP.TSC", + "${OPENSPACE_DATA}/spice/Rosetta/SCLK/ros_triv.tsc", - --Jan 2014 - May 2015 (version match with 00162 ck files) - -- "${OPENSPACE_DATA}/spice/RosettaKernels/SPK/CORB_DV_097_01_______00162.BSP", - -- "${OPENSPACE_DATA}/spice/RosettaKernels/SPK/RORB_DV_097_01_______00162.BSP", - -- "${OPENSPACE_DATA}/spice/RosettaKernels/SPK/LORB_DV_097_01_______00162.BSP", + "${OPENSPACE_DATA}/spice/Rosetta/SPK/CORB_DV_243_01___T19_00325.BSP", + "${OPENSPACE_DATA}/spice/Rosetta/SPK/CORB_DV_223_01___T19_00302.BSP", + "${OPENSPACE_DATA}/spice/Rosetta/SPK/CORB_DV_145_01___T19_00216.BSP", - --IK - "${OPENSPACE_DATA}/spice/RosettaKernels_New/IK/ROS_NAVCAM_V01.TI", - "${OPENSPACE_DATA}/spice/RosettaKernels/IK/ROS_NAVCAM_V00-20130102.TI", + "${OPENSPACE_DATA}/spice/Rosetta/SPK/LORB_DV_236_01___T19_00318.BSP", + "${OPENSPACE_DATA}/spice/Rosetta/SPK/LORB_DV_223_01___T19_00302.BSP", + "${OPENSPACE_DATA}/spice/Rosetta/SPK/LORB_DV_145_01___T19_00216.BSP", + + "${OPENSPACE_DATA}/spice/Rosetta/SPK/RORB_DV_243_01___T19_00325.BSP", + "${OPENSPACE_DATA}/spice/Rosetta/SPK/RORB_DV_223_01___T19_00302.BSP", + "${OPENSPACE_DATA}/spice/Rosetta/SPK/RORB_DV_145_01___T19_00216.BSP", - --SCLK - -- "${OPENSPACE_DATA}/spice/RosettaKernels/SCLK/ROS_150227_STEP.TSC", + "${OPENSPACE_DATA}/spice/Rosetta/CK/ATNR_P040302093352_00127.BC", - -- FK - -- "${OPENSPACE_DATA}/spice/RosettaKernels/FK/ROS_CHURYUMOV_V01.TF", - -- "${OPENSPACE_DATA}/spice/RosettaKernels/FK/ROS_V24.TF", + "${OPENSPACE_DATA}/spice/Rosetta/SPK/ROS_STRUCT_V5.BSP", + + "${OPENSPACE_DATA}/spice/Rosetta/IK/ROS_NAVCAM_V01.TI", + + "${OPENSPACE_DATA}/spice/Rosetta/FK/ROS_CHURYUMOV_V01.TF", + "${OPENSPACE_DATA}/spice/Rosetta/FK/ROS_V26.TF", -- CK - -- '${OPENSPACE_DATA}/spice/RosettaKernels/CK/RATT_DV_097_01_01____00162.BC', - -- "${OPENSPACE_DATA}/spice/RosettaKernels/CK/CATT_DV_097_01_______00162.BC", + -- Rosetta attitude + "${OPENSPACE_DATA}/spice/Rosetta/CK/RATT_DV_243_01_01____00325.BC", + "${OPENSPACE_DATA}/spice/Rosetta/CK/RATT_DV_223_01_01____00302.BC", + "${OPENSPACE_DATA}/spice/Rosetta/CK/RATT_DV_145_01_01____00216.BC", - --SCLK - -- "${OPENSPACE_DATA}/spice/RosettaKernels/SCLK/ROS_150227_STEP.TSC", - "${OPENSPACE_DATA}/spice/RosettaKernels_New/SCLK/ROS_160425_STEP.TSC", + -- Comet attitude + "${OPENSPACE_DATA}/spice/Rosetta/CK/CATT_DV_243_01_______00325.BC", + "${OPENSPACE_DATA}/spice/Rosetta/CK/CATT_DV_223_01_______00302.BC", + "${OPENSPACE_DATA}/spice/Rosetta/CK/CATT_DV_145_01_______00216.BC", - -- FK + -- High gain antenna + "${OPENSPACE_DATA}/spice/Rosetta/CK/ROS_HGA_2016_V0035.BC", + "${OPENSPACE_DATA}/spice/Rosetta/CK/ROS_HGA_2015_V0053.BC", + "${OPENSPACE_DATA}/spice/Rosetta/CK/ROS_HGA_2014_V0044.BC", - "${OPENSPACE_DATA}/spice/RosettaKernels_New/FK/ROS_CHURYUMOV_V01.TF", - "${OPENSPACE_DATA}/spice/RosettaKernels_New/FK/ROS_V26.TF", - -- "${OPENSPACE_DATA}/spice/RosettaKernels/FK/ROS_V24.TF", - -- CK - "${OPENSPACE_DATA}/spice/RosettaKernels_New/CK/RATT_DV_211_01_01____00288.BC", - "${OPENSPACE_DATA}/spice/RosettaKernels_New/CK/CATT_DV_211_01_______00288.BC", - '${OPENSPACE_DATA}/spice/RosettaKernels/CK/RATT_DV_097_01_01____00162.BC', - "${OPENSPACE_DATA}/spice/RosettaKernels/CK/CATT_DV_097_01_______00162.BC", - - -- PCK - "${OPENSPACE_DATA}/spice/RosettaKernels_New/PCK/ROS_CGS_RSOC_V03.TPC", - -- "${OPENSPACE_DATA}/spice/RosettaKernels/PCK/ROS_CGS_RSOC_V03.TPC", + -- Solar arrays + "${OPENSPACE_DATA}/spice/Rosetta/CK/ROS_SA_2016_V0034.BC", + "${OPENSPACE_DATA}/spice/Rosetta/CK/ROS_SA_2015_V0042.BC", + "${OPENSPACE_DATA}/spice/Rosetta/CK/ROS_SA_2014_V0047.BC", - - "${OPENSPACE_DATA}/spice/RosettaKernels_New/CK/ROS_SA_2014_V0047.BC", - "${OPENSPACE_DATA}/spice/RosettaKernels_New/CK/ROS_SA_2015_V0042.BC", - "${OPENSPACE_DATA}/spice/RosettaKernels_New/CK/ROS_SA_2016_V0019.BC", + "${OPENSPACE_DATA}/spice/Rosetta/PCK/ROS_CGS_RSOC_V03.TPC", } +RotationMatrix = { + 0, 1, 0, + 0, 0, 1, + 1, 0, 0 +} + + + return { { Name = "Rosetta", @@ -64,7 +67,7 @@ return { Reference = "GALACTIC", Observer = "SUN", Kernels = RosettaKernels - }, + }, Rotation = { Type = "SpiceRotation", SourceFrame = "ROS_SPACECRAFT", @@ -73,8 +76,19 @@ return { } }, { - Name = "Rosetta_black_foil", + Name = "RosettaModel", Parent = "Rosetta", + Transform = { + Scale = { + Type = "StaticScale", + -- The scale of the model is in cm; OpenSpace is in m + Scale = 0.01 + } + } + }, + { + Name = "Rosetta_black_foil", + Parent = "RosettaModel", Renderable = { Type = "RenderableModel", Body = "ROSETTA", @@ -85,12 +99,13 @@ return { Textures = { Type = "simple", Color = "textures/foil_silver_ramp.png" - } + }, + Rotation = { ModelTransform = RotationMatrix } } }, { Name = "Rosetta_black_parts", - Parent = "Rosetta", + Parent = "RosettaModel", Renderable = { Type = "RenderableModel", Body = "ROSETTA", @@ -101,12 +116,13 @@ return { Textures = { Type = "simple", Color = "textures/foil_silver_ramp.png" - } + }, + Rotation = { ModelTransform = RotationMatrix } } }, { Name = "Rosetta_dish", - Parent = "Rosetta", + Parent = "RosettaModel", Renderable = { Type = "RenderableModel", Body = "ROSETTA", @@ -117,12 +133,21 @@ return { Textures = { Type = "simple", Color = "textures/dish_AO.png" - } - } + }, + Rotation = { ModelTransform = RotationMatrix } + + }, + -- Transform = { + -- Rotation = { + -- Type = "SpiceRotation", + -- SourceFrame = "-226071", -- ROS_HGA + -- DestinationFrame = "ROS_SPACECRAFT", + -- } + -- } }, { Name = "Rosetta_parts", - Parent = "Rosetta", + Parent = "RosettaModel", Renderable = { Type = "RenderableModel", Body = "ROSETTA", @@ -133,12 +158,14 @@ return { Textures = { Type = "simple", Color = "textures/parts2_AO.png" - } + }, + Rotation = { ModelTransform = RotationMatrix } + } }, { Name = "Rosetta_silver_foil", - Parent = "Rosetta", + Parent = "RosettaModel", Renderable = { Type = "RenderableModel", Body = "ROSETTA", @@ -149,12 +176,14 @@ return { Textures = { Type = "simple", Color = "textures/foil_silver_ramp.png" - } + }, + Rotation = { ModelTransform = RotationMatrix } + } }, { Name = "Rosetta_vents", - Parent = "Rosetta", + Parent = "RosettaModel", Renderable = { Type = "RenderableModel", Body = "ROSETTA", @@ -165,12 +194,14 @@ return { Textures = { Type = "simple", Color = "textures/tex_01.png" - } + }, + Rotation = { ModelTransform = RotationMatrix } + } }, { Name = "Rosetta_wing_a", - Parent = "Rosetta", + Parent = "RosettaModel", Renderable = { Type = "RenderableModel", Body = "ROSETTA", @@ -181,12 +212,21 @@ return { Textures = { Type = "simple", Color = "textures/tex_01.png" - } - } + }, + Rotation = { ModelTransform = RotationMatrix } + + }, + -- Transform = { + -- Rotation = { + -- Type = "SpiceRotation", + -- SourceFrame = "-226015", -- ROS_SA + -- DestinationFrame = "ROS_SPACECRAFT", + -- } + -- } }, { Name = "Rosetta_wing_b", - Parent = "Rosetta", + Parent = "RosettaModel", Renderable = { Type = "RenderableModel", Body = "ROSETTA", @@ -197,12 +237,21 @@ return { Textures = { Type = "simple", Color = "textures/tex_01.png" - } - } + }, + Rotation = { ModelTransform = RotationMatrix } + + }, + -- Transform = { + -- Rotation = { + -- Type = "SpiceRotation", + -- SourceFrame = "-226025", -- ROS_SA + -- DestinationFrame = "ROS_SPACECRAFT", + -- } + -- } }, { Name = "Rosetta_yellow_foil", - Parent = "Rosetta", + Parent = "RosettaModel", Renderable = { Type = "RenderableModel", Body = "ROSETTA", @@ -213,14 +262,35 @@ return { Textures = { Type = "simple", Color = "textures/foil_gold_ramp.png" - } + }, + Rotation = { ModelTransform = RotationMatrix } + } }, { Name = "Philae", - Parent = "Rosetta" + Parent = "67PBarycenter", -- This should need a transform, but currently the model is intrinsically -- translated + Transform = { + Translation = { + Type = "SpiceEphemeris", + Body = "PHILAE", + Reference = "GALACTIC", + Observer = "CHURYUMOV-GERASIMENKO", + Kernels = RosettaKernels + }, + Rotation = { + Type = "SpiceRotation", + SourceFrame = "ROS_SPACECRAFT", + DestinationFrame = "GALACTIC", + }, + Scale = { + Type = "StaticScale", + -- The scale of the model is in cm; OpenSpace is in m + Scale = 0.01 + } + } }, { Name = "Philae_foil", @@ -235,7 +305,9 @@ return { Textures = { Type = "simple", Color = "textures/foil_silver_ramp.png" - } + }, + Rotation = { ModelTransform = RotationMatrix } + } }, { @@ -251,7 +323,9 @@ return { Textures = { Type = "simple", Color = "textures/parts2_AO.png" - } + }, + Rotation = { ModelTransform = RotationMatrix } + } }, { @@ -267,7 +341,9 @@ return { Textures = { Type = "simple", Color = "textures/foil_silver_ramp.png" - } + }, + Rotation = { ModelTransform = RotationMatrix } + } }, { @@ -283,55 +359,12 @@ return { Textures = { Type = "simple", Color = "textures/tex_01.png" - } + }, + Rotation = { ModelTransform = RotationMatrix } + } }, - --[[ -- Rosetta Trail Module - { - Name = "RosettaTrail", - Parent = "67P", - Renderable = { - Type = "RenderableTrail", - Body = "ROSETTA", - Frame = "GALACTIC", - Observer = "SUN", - -- 3 Dummy values for compilation: - TropicalOrbitPeriod = 10000.0, - EarthOrbitRatio = 2, - DayLength = 50, - -- End of Dummy values - RGB = { 0.7, 0.7, 0.4 }, - Textures = { - Type = "simple", - Color = "textures/glare.png" - }, - }, - GuiName = "RosettaTrail" - }, --]] - -- Comet Dance trail - --[[{ - Name = "RosettaCometTrail", - Parent = "67P", - Renderable = { - Type = "RenderableTrail", - Body = "ROSETTA", - Frame = "GALACTIC", - Observer = "CHURYUMOV-GERASIMENKO", - TropicalOrbitPeriod = 20000.0, - EarthOrbitRatio = 2, - DayLength = 25, - RGB = { 0.9, 0.2, 0.9 }, - Textures = { - Type = "simple", - Color = "textures/glare.png" - }, - StartTime = "2014 AUG 01 12:00:00", - EndTime = "2016 MAY 26 12:00:00" - }, - GuiName = "RosettaCometTrail" - }, - ]] - { + { Name = "RosettaCometTrail", Parent = "67PBarycenter", Renderable = { @@ -341,7 +374,7 @@ return { Frame = "GALACTIC", Observer = "CHURYUMOV-GERASIMENKO", -- Optional rendering properties - LineColor = { 0.9, 0.2, 0.9 }, + LineColor = { 0.288, 0.375, 0.934 }, PointColor = { 0.9, 0.2, 0.9 }, LineFade = 0.0, -- [0,1] RenderPart = 0.5, -- [0,1] @@ -351,23 +384,43 @@ return { -- Time interval TimeRange = { Start = "2014 AUG 01 12:00:00", - End = "2016 MAY 26 12:00:00" + End = "2016 SEP 30 12:00:00" }, SampleDeltaTime = 3600, -- Seconds between each point SubSamples = 3, }, GuiName = "/Solar/RosettaCometTrail" }, + { + Name = "PhilaeTrail", + Parent = "67PBarycenter", + Renderable = { + Type = "RenderableTrailNew", + -- Spice + Body = "PHILAE", + Frame = "GALACTIC", + Observer = "CHURYUMOV-GERASIMENKO", + -- Optional rendering properties + LineColor = { 1.0, 1.0, 1.0 }, + PointColor = { 0.9, 0.2, 0.9 }, + LineFade = 0.0, -- [0,1] + RenderPart = 0.5, -- [0,1] + LineWidth = 2, + ShowTimeStamps = false, + RenderFullTrail = false, + -- Time interval + TimeRange = { + Start = "2014 NOV 12 08:35:00", + End = "2014 NOV 12 17:00:00" + }, + SampleDeltaTime = 2, -- Seconds between each point + SubSamples = 0, + }, + GuiName = "/Solar/RosettaCometTrail" + }, { Name = "NAVCAM", Parent = "Rosetta", - -- Transform = { - -- Rotation = { - -- Type = "SpiceRotation", - -- SourceFrame = "NAVCAM", - -- DestinationFrame = "ROS_SPACECRAFT", - -- }, - -- }, GuiName = "/Solar/Rosetta_navcam" }, { diff --git a/data/scene/sun/sun.mod b/data/scene/sun/sun.mod index 288335bb39..17caf205be 100644 --- a/data/scene/sun/sun.mod +++ b/data/scene/sun/sun.mod @@ -50,7 +50,8 @@ return { Size = {1.3, 10.5}, Origin = "Center", Billboard = true, - Texture = "textures/sun-glare.png" + Texture = "textures/sun-glare.png", + BlendMode = "Additive" }, Ephemeris = { Type = "Spice", @@ -70,7 +71,8 @@ return { Size = {3.0, 11.0}, Origin = "Center", Billboard = true, - Texture = "textures/marker.png" + Texture = "textures/marker.png", + BlendMode = "Additive" }, Ephemeris = { Type = "Static", diff --git a/ext/ghoul b/ext/ghoul index b08559d8b2..e45911445d 160000 --- a/ext/ghoul +++ b/ext/ghoul @@ -1 +1 @@ -Subproject commit b08559d8b2d4804348bcfb815bfbd620906e3039 +Subproject commit e45911445d85b884baa017e175d6a175ca421d76 diff --git a/ext/sgct b/ext/sgct index 70cde82e33..9ad36dbaa5 160000 --- a/ext/sgct +++ b/ext/sgct @@ -1 +1 @@ -Subproject commit 70cde82e331406d8bfe406d0e606fefa89d873c7 +Subproject commit 9ad36dbaa55d5267556a32338f8776a85c463321 diff --git a/include/openspace/documentation/core_registration.h b/include/openspace/documentation/core_registration.h new file mode 100644 index 0000000000..d29259daf2 --- /dev/null +++ b/include/openspace/documentation/core_registration.h @@ -0,0 +1,38 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __CORE_REGISTRATION_H__ +#define __CORE_REGISTRATION_H__ + +namespace openspace { +namespace documentation { + +class DocumentationEngine; + +void registerCoreClasses(documentation::DocumentationEngine& engine); + +} // namespace documentation +} // namespace openspace + +#endif // __CORE_REGISTRATION_H__ diff --git a/include/openspace/documentation/documentation.h b/include/openspace/documentation/documentation.h new file mode 100644 index 0000000000..a200be79a8 --- /dev/null +++ b/include/openspace/documentation/documentation.h @@ -0,0 +1,288 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __DOCUMENTATION_H__ +#define __DOCUMENTATION_H__ + +#include +#include +#include + +#include +#include +#include + +namespace openspace { +namespace documentation { + +using Optional = ghoul::Boolean; +using Exhaustive = ghoul::Boolean; + +/** + * The TestResult structure returns the information from the #testSpecification method. It + * contains the information whether test specification test was successful + * (TestResult::success) and a list of TestResult::Offense%s (TestResult::offenses). If + * TestResult::success is true, TestResult::offenses is guaranteed to be empty. + */ +struct TestResult { + /** + * An Offense is a violation against a specific verifier. The Offense::offender is the + * key that caused the offense (in the case of nested tables, it will be fully + * qualified identifier) and the Offense::Reason is the reason that caused the + * offense. + */ + struct Offense { + /** + * The Reason for the offense + */ + enum class Reason { + MissingKey, ///< The offending key that was requested was not found + ExtraKey, ///< The exhaustive documentation contained an extra key + WrongType, ///< The key's value was not of the expected type + Verification, ///< The value did not pass a necessary non-type verifier + UnknownIdentifier ///< If the identifier for a ReferencingVerifier did not exist + }; + /// The offending key that caused the Offense. In the case of a nested table, + /// this value will be the fully qualified name of the key + std::string offender; + /// The Reason that caused this offense + Reason reason; + }; + /// Is \c true if the TestResult is positive, \c false otherwise + bool success; + /// Contains a list of offenses that were found in the test. Is empty if + /// TestResult::Success is \c true + std::vector offenses; +}; + +/** + * This exception is thrown by the #testSpecificationAndThrow method if the test detected + * a specification violation. This class contains the TestResult that would have otherwise + * be returned in a call to #testSpecification. + */ +struct SpecificationError : public ghoul::RuntimeError { + /** + * Creates the SpecificationError exception instance. + * \param result The offending TestResult that is passed on + * \param component The component that initiated the specification test + * \pre \p result%'s TestResult::success must be \c false + */ + SpecificationError(TestResult result, std::string component); + + /// The TestResult that caused the SpecificationError to be thrown + TestResult result; +}; + +struct Verifier; + +/** + * A DocumentationEntry provides the specification for a single key, which is tested using + * the provided Verifier. Each DocumentationEntry can contain a textual documentation that + * describes the entry and is printed when the documentation for a Documentation is + * requested. Lastly, each DocumentationEntry can be Optional. If the provided key is the + * DocumentationEntry::Wildcard, any key in the containing Documentation will be tested + * against the provided verifier. The most convenient way of creating DocumentationEntry%s + * is by using an inline initializer list such as: + *\verbatim +DocumentationEntry e = { "key", new IntVerifier, "Documentation text", Optional::Yes }; +\endverbatim + + * Furthermore, these initializer lists can be crated all at once for a Documentation. + * Even if the Verifier%s are specified using the \c new operators, they will not leak + * memory as the DocumentationEntry takes ownership of them in the constructor. + */ +struct DocumentationEntry { + /// The wildcard character that will match against every key in a Documentation + static const std::string Wildcard; + + /** + * The constructor for a DocumentationEntry describing a \p key in a Documentation. + * The value for the key (or each value in the case of the + * DocumentationEntry::Wildcard) is tested using the \p verifier, that specifies the + * conditions that the \p key%'s value has to fulfill. The textual documentation + * \p doc shall describe the usage of the key-value pair and will be printed for human + * consumption for example in the DocumentationEngine. Each DocumentationEntry can + * further be \p optional. + * \param key The key for which this DocumentationEntry is valid. If this valid is + * equal to DocumentationEntry::Wildcard, each entry in the Documentation that + * contains this DocumentationEntry will be matched + * \param verifier The Verifier that is used to test the \p key%'s value to determine + * if it is a valid value + * \param doc The textual documentation that describes the DocumentationEntry in a + * human readable format + * \param optional Determines whether the Documentation containing this + * DocumentationEntry must have a key \p key, or whether it is optional + * \pre \p key must not be empty + * \pre \p verifier must not be nullptr + */ + DocumentationEntry(std::string key, std::shared_ptr verifier, + std::string doc = "", Optional optional = Optional::No); + + /** + * The constructor for a DocumentationEntry describing a \p key in a Documentation. + * The value for the key (or each value in the case of the + * DocumentationEntry::Wildcard) is tested using the \p verifier, that specifies the + * conditions that the \p key%'s value has to fulfill. The textual documentation + * \p doc shall describe the usage of the key-value pair and will be printed for human + * consumption for example in the DocumentationEngine. Each DocumentationEntry can + * further be \p optional. + * \param key The key for which this DocumentationEntry is valid. If this valid is + * equal to DocumentationEntry::Wildcard, each entry in the Documentation that + * contains this DocumentationEntry will be matched + * \param verifier The Verifier that is used to test the \p key%'s value to determine + * if it is a valid value. The DocumentationEntry will take ownership of the passed + * object + * \param doc The textual documentation that describes the DocumentationEntry in a + * human readable format + * \param optional Determines whether the Documentation containing this + * DocumentationEntry must have a key \p key, or whether it is optional + * \pre \p key must not be empty + * \pre \p verifier must not be nullptr + */ + DocumentationEntry(std::string key, Verifier* verifier, std::string doc = "", + Optional optional = Optional::No); + + /// The key that is described by this DocumentationEntry + std::string key; + /// The Verifier that is used to test the key's value + std::shared_ptr verifier; + /// Determines whether the described DocumentationEntry is optional or not + Optional optional; + /// The textual description of this DocumentationEntry + std::string documentation; +}; + + +/** + * This struct contains the documentation and specification for a ghoul::Dictionary. It is + * used to impose restrictions on keys and values and determine whether a given + * ghoul::Dictionary adheres to these specifications (see #testSpecification and + * #testSpecificationAndThrow methods). Each Documentation consists of a human-readable + * \c name, a list of DocumentationEntry%s that each describe a single key value, and a + * flag whether these entries are Exhaustive or not. If a Documentation is Exhaustive, a + * ghoul::Dictionary that contains additional keys will fail the specification, whereas a + * non-exhaustive Documentation allow for other (potentially non used) keys. The most + * convenient way of creating a Documentation is by using nested initializer lists: + *\verbatim +Documentation doc = { + "Documentation for an arbitrary dictionary", + { // A list of DocumentationEntry%s; also specified using initializer lists + { "key1", new IntVerifier, "Documentation key1", Optional::Yes }, + { "key2", new FloatVerifier, "Documentation key2" }, + { "key3", new StringVerifier } + }, + Exhaustive::Yes ++; +\endverbatim + * + * If multiple DocumentationEntries cover the same key, they are all evaluated for that + * specific key. The same holds true if there is a DocumentationEntry with a + * DocumentationEntry::Wildcard and a more specialized DocumentationEntry. In this case, + * both the wildcard and the specialized entry will be evaluated. + */ +struct Documentation { + using DocumentationEntries = std::vector; + + /** + * Creates a Documentation with a human-readable \p name and a list of \p entries. + * \param name The human-readable name of this Documentation + * \param id A unique identifier which can be used by applications (or other + * Documentation%s to reference this entry + * \param entries A list of DocumentationEntry%s that describe the individual keys for + * this entrie Documentation + * \param exhaustive Determines whether the \p entries are an exhaustive specification + * of the object or whether additional, potentially unused, keys are allowed + */ + Documentation(std::string name, std::string id, DocumentationEntries entries = {}, + Exhaustive exhaustive = Exhaustive::No); + + /** + * Creates a Documentation with a human-readable \p name. + * \param name The human-readable name of this Documentation + * \param entries A list of DocumentationEntry%s that describe the individual keys for + * this entrie Documentation + * \param exhaustive Determines whether the \p entries are an exhaustive specification + * of the object or whether additional, potentially unused, keys are allowed + */ + Documentation(std::string name, DocumentationEntries entries = {}, + Exhaustive exhaustive = Exhaustive::No); + + /** + * Creates a Documentation. + * \param entries A list of DocumentationEntry%s that describe the individual keys for + * this entrie Documentation + * \param exhaustive Determines whether the \p entries are an exhaustive specification + * of the object or whether additional, potentially unused, keys are allowed + */ + Documentation(DocumentationEntries entries = {}, Exhaustive exhaustive = Exhaustive::No); + + /// The human-readable name of the Documentation + std::string name; + /// A unique identifier which can be used to reference this Documentation + std::string id; + /// A list of specifications that are describing this Documentation + DocumentationEntries entries; + /// A flag to say wheter the DocumentationEntries are an exhaustive description + Exhaustive exhaustive; +}; + +/** + * This method tests whether a provided ghoul::Dictionary \p dictionary adheres to the + * specification \p documentation and returns its result as a TestResult. The TestResult + * will contain whether the \p dictionary adheres to the \p documentation and, in + * addition, the list of all offending keys together with the reason why they are + * offending. + * \param documentation The Documentation that the \p dictionary is tested against + * \param dictionary The ghoul::Dictionary that is to be tested against the + * \p documentation + * \return A TestResult that contains the results of the specification testing + */ +TestResult testSpecification(const Documentation& documentation, + const ghoul::Dictionary& dictionary); + +/** +* This method tests whether a provided ghoul::Dictionary \p dictionary adheres to the +* specification \p documentation. If the \p dictionary does not adhere to the +* specification a SpecificationError is thrown, and the exception contains the TestResult +* that contains more information about the offending keys. If the \p dictionary adheres to +* the \p documentation, the method returns normally. +* \param documentation The Documentation that the \p dictionary is tested against +* \param dictionary The ghoul::Dictionary that is to be tested against the +* \p documentation +* \param component The component that is using this method; this argument is passed to the +* SpecificationError that is thrown in case of not adhering to the \p documentation +* \throw SpecificationError If the \p dictionary does not adhere to the \p documentation +*/ +void testSpecificationAndThrow(const Documentation& documentation, + const ghoul::Dictionary& dictionary, std::string component); + +} // namespace documentation + +// We want to make it easier for people to use it, so we pull the Documentation class into +// the openspace namespace +using documentation::Documentation; + +} // namespace openspace + +#endif // __DOCUMENTATION_H__ diff --git a/include/openspace/documentation/documentationengine.h b/include/openspace/documentation/documentationengine.h new file mode 100644 index 0000000000..7f8851686b --- /dev/null +++ b/include/openspace/documentation/documentationengine.h @@ -0,0 +1,102 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __DOCUMENTATIONENGINE_H__ +#define __DOCUMENTATIONENGINE_H__ + +#include + +#include +#include + +namespace openspace { +namespace documentation { + +/** + * The DocumentationEngine has the ability to collect all Documentation%s that are + * produced in the application an write them out as a documentation file for human + * consumption. + */ +class DocumentationEngine : public ghoul::Singleton { +public: + /** + * This exception is thrown by the addDocumentation method if a provided Documentation + * has an identifier, but the identifier was registered previously. + */ + struct DuplicateDocumentationException : public ghoul::RuntimeError { + /** + * Constructor of a DuplicateDocumentationException storing the offending + * Documentation for later use. + * \param documentation The Documentation whose identifier was previously + * registered + */ + DuplicateDocumentationException(Documentation documentation); + + /// The offending Documentation whose identifier was previously registered + Documentation documentation; + }; + + /** + * Write the collected Documentation%s to disk at the \p filename in the specified + * \p type. A new file is created and silently overwritten in the location that + * \p filename is pointed to. + * \param filename The file that is to be created containing all the Documentation + * information. + * \param type The type of documentation that is written. Currently allowed values are + * \c text and \c html + */ + void writeDocumentation(const std::string& filename, const std::string& type); + + /** + * Adds the \p documentation to the list of Documentation%s that are written to a + * documentation file with the writeDocumentation method. + * \param documentation The Documentation object that is to be stored for later use + * \throws DuplicateDocumentationException If the \p documentation has a non-empty + * identifier and it was not unique + */ + void addDocumentation(Documentation documentation); + + /** + * Returns a list of all registered Documentation%s + * \return A list of all registered Documentation%s + */ + std::vector documentations() const; + + /** + * Returns a static reference to the main singleton DocumentationEngine + * \return A static reference to the main singleton DocumentationEngine + */ + static DocumentationEngine& ref(); + +private: + /// The list of all Documentation%s that are stored by the DocumentationEngine + std::vector _documentations; +}; + +} // namespace documentation +} // namespace openspace + +#define DocEng (openspace::documentation::DocumentationEngine::ref()) + +#endif // __DOCUMENTATIONENGINE_H__ diff --git a/include/openspace/documentation/verifier.h b/include/openspace/documentation/verifier.h new file mode 100644 index 0000000000..15275a8539 --- /dev/null +++ b/include/openspace/documentation/verifier.h @@ -0,0 +1,858 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __VERIFIER_H__ +#define __VERIFIER_H__ + +#include + +#include + +namespace openspace { +namespace documentation { + +/** + * The base class of all Verifier%s. Each object must have an Verifier::operator() + * overload, that performs the actual testing of the key inside the passed + * ghoul::Dictionary and return a TestResult. The Verifier::type method returns a + * human-readable representation of the type that is expected by the concret subclass of + * Verifier. Furthermore, the Verifier::documentation method returns a human-readable + * description of the Verifier subclass and what it tests for. + */ +struct Verifier { + /** + * This method tests whether the \p key contained in the \p dictionary adheres to + * whatever the concrete Verifer needs to test. The actual testing depends on the + * concrete subclass and can range from type testing (for example IntVerifier or + * StringVerifier) to more complex testing (for example DoubleInRangeVerifier or + * TableVerifier). + * \param dictionary The dictionary that contains the \p key which is to be tested by + * this Verifier + * \param key The key inside the \p dictionary that is to be tested + * \return A TestResult struct that contains information about whether the key adheres + * to the demands of the specific Verifier. If it does not, TestResult::offenders will + * either contain \p key or, in the case of a TableVerifier, a list of all offending + * subkeys as fully qualified names. + * \post If the return values' TestResult::success is \c true, its + * TestResult::offenders is empty + */ + virtual TestResult operator()(const ghoul::Dictionary& dictionary, + const std::string& key) const = 0; + + /** + * This method returns a human-readable string describing the type of object that is + * handled by the Verifier subclass. This is only used for generating a human-readable + * documentation and description of a Documenation object. + * \return A human-readable string describing the type of object for the Verifier + * \post The return value is not empty + */ + virtual std::string type() const = 0; + + /** + * This method returns a human-readable string describing the tests that the concrete + * Verifier subclass implements. This is only used for generating a human-readable + * documentation and description of a Documentation object. + * \return A human-readable string describing the tests that are performed by the + * Verifier + * \post The return value is not empty + */ + virtual std::string documentation() const = 0; +}; + +//---------------------------------------------------------------------------------------- +// General verifiers +//---------------------------------------------------------------------------------------- + +/** + * The base class Verifier for all Verifier%s that have to test against a specific value + * type. This Verifier tests whether a given key exists and whether it has the same type + * as the template parameter \c T. + * \tparam T The type against which the key's value is tested + */ +template +struct TemplateVerifier : public Verifier { + using Type = T; + + /** + * Tests whether the \p key contained in the ghoul::Dictionary \p dictionary exists + * and has the same type as \c T. + * \param dictionary The ghoul::Dictionary that contains the \p key to be tested + * \param key The key inside the \p dictinoary that is to be tested + * \return A TestResult that contains the information whether the \p key exists in the + * \p dictionary and whether the key's value's type agrees with \c T. + * \post The return values' TestResult::success is either \c true and + * TestResult::offenders is empty, or it is \c false and TestResult::offenders + * contains \p key + */ + TestResult operator()(const ghoul::Dictionary& dictionary, + const std::string& key) const override; + + std::string documentation() const override; +}; + +/** + * A Verifier that checks whether a given key inside a ghoul::Dictionary is of type + * \c bool. No implicit conversion is considered in this testing. + */ +struct BoolVerifier : public TemplateVerifier { + std::string type() const override; +}; + +/** +* A Verifier that checks whether a given key inside a ghoul::Dictionary is of type +* \c double. No implicit conversion is considered in this testing. +*/ +struct DoubleVerifier : public TemplateVerifier { + std::string type() const override; +}; + +/** +* A Verifier that checks whether a given key inside a ghoul::Dictionary is of type +* \c int. It will also return \c true if the key's value is of type \c double, but is a +* integer value (for example, 0.0, 12.0, but not +* 0.5). +*/ +struct IntVerifier : public TemplateVerifier { + TestResult operator()(const ghoul::Dictionary& dict, + const std::string& key) const override; + + std::string type() const override; +}; + +/** +* A Verifier that checks whether a given key inside a ghoul::Dictionary is of type +* std::string. No implicit conversion is considered in this testing. +*/ +struct StringVerifier : public TemplateVerifier { + std::string type() const override; +}; + +/** +* A Verifier that checks whether a given key inside a ghoul::Dictionary is another +* ghoul::Dictionary. The constructor takes a list of DocumentationEntry%s, which are used +* recursively to check the contained table. If this list is empty, a simple type testing +* is performed instead. If the testing finds any offending keys, it will return those keys +* with fully qualified names, that is, the name of the table will be prepended to the +* offending keys. Example: If the key \c Table is tested and a passed DocumentationEntry +* checks for a nested key \c a and this does not comply, this Verifier will return +* Table.a as an offender. +*/ +struct TableVerifier : public TemplateVerifier { + /** + * This constructor takes a list of DocumentationEntry%s that are used recursively to + * check the table (= ghoul::Dictionary) contained in the key's value. Similar to the + * Documentation, these DocumentationEntry%s can be Exhaustive or not. + * \param documentationEntries The DocumentationEntry%s that are used to recursively + * test the ghoul::Dictionary that is contained inside. If this list is empty, only a + * type check is performed + * \param exhaustive Whether the DocumentationEntry%s contained in + * \p documentationEntries completely describe the contained table or whether + * additional keys are allowed + */ + TableVerifier(std::vector documentationEntries = {}, + Exhaustive exhaustive = Exhaustive::No); + + /** + * Checks whether the \p key%'s value is a table (= ghoul::Dictionary) and (if + * provided) recursively checks whether the table adheres to the DocumentationEntry%s + * provided in the constructor. If the testing finds any offending keys, it will + * return those keys with fully qualified names, that is, the name of the table will + * be prepended to the offending keys. + * \param dictionary The ghoul::Dictionary that is to be tested for the \p key + * \param key The key for which the \p dictionary is tested + * \return A TestResult containing the results of the testing. If DocumentationEntry%s + * were specified in the constructor and one of those values find an offending key + * inside the table, it's name will be returned with a fully qualified name by + * prepending the name (= \key) of the table. + */ + TestResult operator()(const ghoul::Dictionary& dictionary, + const std::string& key) const override; + + std::string type() const override; + + /// The documentations passed in the constructor + std::vector documentations; + /// Flag that specifies whether the TableVerifier::documentation exhaustively + /// describes the table or whether additional keys are allowed + Exhaustive exhaustive; +}; + +//---------------------------------------------------------------------------------------- +// Vector verifiers +//---------------------------------------------------------------------------------------- + +/** + * This struct is the base class for all Verifier%s that check for \c glm vector types. + * The template parameter for the subclasses is the containing type, not the full vector + * type. For example to check for glm::dvec3, one would create a + * Vector3Verifier. + */ +struct VectorVerifier {}; + +/** + * This Verifier checks whether the value is of type glm::tvec2 + */ +template +struct Vector2Verifier : public TemplateVerifier>, public VectorVerifier { + std::string type() const override; +}; + +/** +* This Verifier checks whether the value is of type glm::tvec3 +*/ +template +struct Vector3Verifier : public TemplateVerifier>, public VectorVerifier { + std::string type() const override; +}; + +/** +* This Verifier checks whether the value is of type glm::tvec4 +*/ +template +struct Vector4Verifier : public TemplateVerifier>, public VectorVerifier { + std::string type() const override; +}; + +//---------------------------------------------------------------------------------------- +// Operator verifiers +//---------------------------------------------------------------------------------------- + +/** + * This is the abstract base class of all binary operator-based verifiers. This class + * takes two template parameters. The first is the Verifier that one would use to only + * check for the type of the object, for example IntVerifier. The second argument is a + * function object that has its operator() function overloaded and returns a + * boolean value. In these cases, the \c std function objects std::less, + * std::equal_to, etc are used. + * + * This verifier will apply the \c Operator to the stored value and the incoming value + * (after type checking) and will check if the \c Operator returns \c true or \c false. + * The incoming value is used as the first argument and the stored value as the second + * argument to the \c Operator. If the type checking fails, the offense reason + * TestResult::Offense::Reason::WrongType is returned. If the \c Operator fails, the + * reason TestResult::Offense::Verification is returned instead. + */ +template +struct OperatorVerifier : public T { + /** + * Constructor for an OperatorVerifier. As all operators need to compare the incoming + * value to a stored value, we require the comparison \p value to be passed in here. + * \param value The value against which the tested value is compared using the + * \c Operator + */ + OperatorVerifier(typename T::Type value); + + /** + * First checks whether the \p dictionary contains the passed \p key and whether the + * \p key%'s value is correct using the template paramater \c T as a verifier. Then, + * the \p key%'s value is checked against the stored OperatorVerifier::value using the + * \c Operator. + * \param dictionary The ghoul::Dictionary that contains the \p key to be tested + * \param key The key inside the \p dictinoary that is to be tested + * \return A TestResult containing the results of the specification testing. If the + * \p key%'s value has the wrong type, it will be added to the TestResult's offense + * list with the reason TestResult::Offense::Reason::WrongType; if the \c Operator + * returns false, it will be added with the reason TestResult::Offense::Verification + * instead. + */ + TestResult operator()(const ghoul::Dictionary& dictionary, + const std::string& key) const override; + + /// The stored value which is passed to the \c Operator as a second argument + typename T::Type value; +}; + +/** + * This Verifier checks whether the incoming value is strictly smaller than the stored + * value. Due to the operator type restrictions, \c T cannot be a subclass of (or the same + * as) BoolVerifier, StringVerifier, TableVerifier, or VectorVerifier. + */ +template +struct LessVerifier : public OperatorVerifier> { + static_assert(!std::is_base_of_v, "T cannot be BoolVerifier"); + static_assert(!std::is_base_of_v, "T cannot be StringVerifier"); + static_assert(!std::is_base_of_v, "T cannot be TableVerifier"); + static_assert(!std::is_base_of_v, "T cannot be VectorVerifier"); + + using OperatorVerifier::OperatorVerifier; + + std::string documentation() const; +}; + +/** +* This Verifier checks whether the incoming value is smaller than or equal to the stored +* value. Due to the operator type restrictions, \c T cannot be a subclass of (or the same +* as) BoolVerifier, StringVerifier, TableVerifier, or VectorVerifier. +*/ +template +struct LessEqualVerifier : public OperatorVerifier> { + static_assert(!std::is_base_of_v, "T cannot be BoolVerifier"); + static_assert(!std::is_base_of_v, "T cannot be StringVerifier"); + static_assert(!std::is_base_of_v, "T cannot be TableVerifier"); + static_assert(!std::is_base_of_v, "T cannot be VectorVerifier"); + + using OperatorVerifier::OperatorVerifier; + + std::string documentation() const override; +}; + +/** +* This Verifier checks whether the incoming value is strictly greater than the stored +* value. Due to the operator type restrictions, \c T cannot be a subclass of (or the same +* as) BoolVerifier, StringVerifier, TableVerifier, or VectorVerifier. +*/ +template +struct GreaterVerifier : public OperatorVerifier> { + static_assert(!std::is_base_of_v, "T cannot be BoolVerifier"); + static_assert(!std::is_base_of_v, "T cannot be StringVerifier"); + static_assert(!std::is_base_of_v, "T cannot be TableVerifier"); + static_assert(!std::is_base_of_v, "T cannot be VectorVerifier"); + + using OperatorVerifier::OperatorVerifier; + + std::string documentation() const override; +}; + +/** +* This Verifier checks whether the incoming value is greater than or equal to the stored +* value. Due to the operator type restrictions, \c T cannot be a subclass of (or the same +* as) BoolVerifier, StringVerifier, TableVerifier, or VectorVerifier. +*/ +template +struct GreaterEqualVerifier : public OperatorVerifier> { + static_assert(!std::is_base_of_v, "T cannot be BoolVerifier"); + static_assert(!std::is_base_of_v, "T cannot be StringVerifier"); + static_assert(!std::is_base_of_v, "T cannot be TableVerifier"); + static_assert(!std::is_base_of_v, "T cannot be VectorVerifier"); + + using OperatorVerifier::OperatorVerifier; + + std::string documentation() const override; +}; + +/** +* This Verifier checks whether the incoming value is equal to the stored value. Due to the +* operator type restrictions, \c T cannot be a subclass of (or the same as) TableVerifier. +*/ +template +struct EqualVerifier : public OperatorVerifier> { + static_assert(!std::is_base_of_v, "T cannot be TableVerifier"); + + using OperatorVerifier::OperatorVerifier; + + std::string documentation() const override; +}; + +/** +* This Verifier checks whether the incoming value is unequal to the store value. Due to +* the operator type restrictions, \c T cannot be a subclass of (or the same as) +* TableVerifier. +*/ +template +struct UnequalVerifier : public OperatorVerifier> { + static_assert(!std::is_base_of_v, "T cannot be TableVerifier"); + + using OperatorVerifier::OperatorVerifier; + + std::string documentation() const override; +}; + +//---------------------------------------------------------------------------------------- +// List verifiers +//---------------------------------------------------------------------------------------- + +/** + * This Verifier checks whether the incoming value is of the correct type, using the + * Verifier passed as a template parameter \c T and then checks whether it is part of a + * list that is passed to the constructor. To the missing equality operator, \c T cannot + * be a subclass of (or the same as) TableVerifier. + */ +template +struct InListVerifier : public T { + static_assert(!std::is_base_of_v, "T cannot be TableVerifier"); + + /** + * Constructs an InListVerifier that checks whether the incoming value is of the + * correct type and whether the value is part of the list passed as \p values. + * \param values The list of values against which the incoming value is tested + */ + InListVerifier(std::vector values); + + /** + * Tests whether the \p key exists in the \p dictionary, whether it has the correct + * type by invoking the template parameter \c T, and then tests if the \p key's value + * is part of the list passed to the constructor. + * \param dictionary The ghoul::Dictionary that contains the \p key + * \param key The key that is contained in the \p dictionary and whose value is tested + * \return A TestResult containing the results of the specification testing. If the + * \p key%'s value has the wrong type, it will be added to the TestResult's offense + * list with the reason TestResult::Offense::Reason::WrongType; if the value is not + * in the list, it will be added with the reason TestResult::Offense::Verification + * instead. + */ + TestResult operator()(const ghoul::Dictionary& dictionary, + const std::string& key) const override; + + std::string documentation() const override; + + /// The list of values against which the incoming value is tested + std::vector values; +}; + +/** +* This Verifier checks whether the incoming value is of the correct type, using the +* Verifier passed as a template parameter \c T and then checks whether it is not part of a +* list that is passed to the constructor. To the missing equality operator, \c T cannot +* be a subclass of (or the same as) TableVerifier. +*/ +template +struct NotInListVerifier : public T { + static_assert(!std::is_base_of_v, "T cannot be TableVerifier"); + + /** + * Constructs a NotInListVerifier that checks whether the incoming value is of the + * correct type and whether the value is not part of the list passed as \p values. + * \param values The list of values against which the incoming value is tested + */ + NotInListVerifier(std::vector values); + + /** + * Tests whether the \p key exists in the \p dictionary, whether it has the correct + * type by invoking the template parameter \c T, and then tests if the \p key's value + * is not part of the list passed to the constructor. + * \param dictionary The ghoul::Dictionary that contains the \p key + * \param key The key that is contained in the \p dictionary and whose value is tested + * \return A TestResult containing the results of the specification testing. If the + * \p key%'s value has the wrong type, it will be added to the TestResult's offense + * list with the reason TestResult::Offense::Reason::WrongType; if the value is in the + * list, it will be added with the reason TestResult::Offense::Verification instead. + */ + TestResult operator()(const ghoul::Dictionary& dictionary, + const std::string& key) const override; + + std::string documentation() const override; + + std::vector values; +}; + +//---------------------------------------------------------------------------------------- +// Range verifiers +//---------------------------------------------------------------------------------------- + +/** +* This Verifier checks whether the incoming value is of the correct type, using the +* Verifier passed as a template parameter \c T and then checks whether it is greater or +* equal to a lower limit and less or equal to a higher limit. To the missing comparison +* operators, \c T cannot be a subclass of (or the same as) BoolVerifier, StringVerifier, +* TableVerifier, or VectorVerifier. Both the lower and the higher limit are inclusive). +*/ +template +struct InRangeVerifier : public T { + static_assert(!std::is_base_of_v, "T cannot be BoolVerifier"); + static_assert(!std::is_base_of_v, "T cannot be StringVerifier"); + static_assert(!std::is_base_of_v, "T cannot be TableVerifier"); + static_assert(!std::is_base_of_v, "T cannot be VectorVerifier"); + + /** + * Constructs a InRangeVerifier that checks whether the incoming value is of the + * correct type and whether the value is greater or equal to \p lower and less or equal + * to \upper. + * \param lower The (inclusive) lower limit of the range + * \param upper The (inclusive) upper limit of the range + * \pre \p lower must be smaller or equal to \p upper + */ + InRangeVerifier(typename T::Type lower, typename T::Type upper); + + /** + * Tests whether the \p key exists in the \p dictionary, whether it has the correct + * type by invoking the template parameter \c T, and then tests if the \p key's value + * is between the lower and upper limits (both inclusive) that were passed to the + * constructor. + * \param dictionary The ghoul::Dictionary that contains the \p key + * \param key The key that is contained in the \p dictionary and whose value is tested + * \return A TestResult containing the results of the specification testing. If the + * \p key%'s value has the wrong type, it will be added to the TestResult's offense + * list with the reason TestResult::Offense::Reason::WrongType; if the value is outside + * the range defined by the lower and upper limits passed to the constructor, it will + * be added with the reason TestResult::Offense::Verification instead. + */ + TestResult operator()(const ghoul::Dictionary& dictionary, + const std::string& key) const override; + + std::string documentation() const override; + + typename T::Type lower; + typename T::Type upper; +}; + +/** +* This Verifier checks whether the incoming value is of the correct type, using the +* Verifier passed as a template parameter \c T and then checks whether it is outside the +* (exclusive) range defined by a lower and upper limit. To the missing comparison +* operators, \c T cannot be a subclass of (or the same as) BoolVerifier, StringVerifier, +* TableVerifier, or VectorVerifier. Both the lower and the higher limit are exclusive). +*/ +template +struct NotInRangeVerifier : public T { + static_assert(!std::is_base_of_v, "T cannot be BoolVerifier"); + static_assert(!std::is_base_of_v, "T cannot be StringVerifier"); + static_assert(!std::is_base_of_v, "T cannot be TableVerifier"); + static_assert(!std::is_base_of_v, "T cannot be VectorVerifier"); + + /** + * Constructs a InRangeVerifier that checks whether the incoming value is of the + * correct type and whether the value is less then \p lower and greater than \upper. + * \param lower The (exclusive) lower limit of the range + * \param upper The (exclusive) upper limit of the range + * \pre \p lower must be smaller or equal to \p upper + */ + NotInRangeVerifier(typename T::Type lower, typename T::Type upper); + + /** + * Tests whether the \p key exists in the \p dictionary, whether it has the correct + * type by invoking the template parameter \c T, and then tests if the \p key's value + * is outside the lower and upper limits (both exclusive) that were passed to the + * constructor. + * \param dictionary The ghoul::Dictionary that contains the \p key + * \param key The key that is contained in the \p dictionary and whose value is tested + * \return A TestResult containing the results of the specification testing. If the + * \p key%'s value has the wrong type, it will be added to the TestResult's offense + * list with the reason TestResult::Offense::Reason::WrongType; if the value is greater + * or equal to the lower limit and less or equal to the upper limit, it will be added + * with the reason TestResult::Offense::Verification instead. + */ + TestResult operator()(const ghoul::Dictionary& dictionary, + const std::string& key) const override; + + std::string documentation() const override; + + typename T::Type lower; + typename T::Type upper; +}; + + +//---------------------------------------------------------------------------------------- +// Misc verifiers +//---------------------------------------------------------------------------------------- + +/** + * This Verifier only checks for the correct type of the incoming value. If the + * documentation is requested, it will return an additional string that is the annotation. + * This can be used to specify further conditions that are hard (or impossible) to codify, + * but the user should be notified about. This, for example, can be that used to notify + * the user that the parameter should be a file of a specific type. + */ +template +struct AnnotationVerifier : public T { + /** + * Constructs an AnnotationVerifier that contains the passed \p annotation which is + * passed to the user when a documentation is requested. + * \param annotation The annotation that is stored and returned to the user when it + * is requested. + * \pre annotation must not be empty + */ + AnnotationVerifier(std::string annotation); + + std::string documentation() const override; + + /// The annotation that is returned to the user in the documentation + std::string annotation; +}; + +/** + * This Verifier can reference and apply other Documentation%s that have been registered + * with a DocumentationEngine. The dependency is only resolved when the operator() is + * called, at which the referencing Documentation must have been registered, or the + * TestResult will contain an offense of TestResult::Offense::Reason::UnknownIdentifier. + * If the referenced Documentation exists, the stored Table will be checked against that + * Documentation. + */ +struct ReferencingVerifier : public TableVerifier { + /** + * Creates a ReferencingVerifier that references a documentation with the provided + * \p identifier. The ReferencingVerifier will use the static DocumentationEngine to + * retrieve Documentation%s and find the \p identifier among them. + * \param identifier The identifier of the Documentation that this Verifier references + */ + ReferencingVerifier(std::string identifier); + + /** + * Checks whether the \p key in the \p dictionary exists and is of type Table (similar + * to the TableVerifier). If it exists and is a Table, the Documentation referenced by + * the identifier provided in the constructor is used to validate the Table. If the + * identifier does not name a registered Documentation, the TestResult::offenses + * will contain the \p key and TestResult::Offense::Reason::UnknownIdentifier will be + * signaled. If the identifier exists and the \p key%'s value does not comply with the + * Documentation, the offending keys will be returned in the TestResult with their + * fully qualified names. + * \param dictionary The ghoul::Dictionary whose \p key should be tested + * \param key The key contained in the \p dictionary that should be tested + * \return A TestResult struct that contains the results of the testing + */ + TestResult operator()(const ghoul::Dictionary& dictionary, + const std::string& key) const override; + + std::string documentation() const override; + + /// The identifier that references another Documentation registered with the + /// DocumentationEngine + std::string identifier; +}; + +//---------------------------------------------------------------------------------------- +// Misc verifiers +//---------------------------------------------------------------------------------------- + +/** + * This Verifier takes two Verifiers and performs a boolean \c and operation on their + * results. In essence, a value only passes this Verifier if it passes both Verifier%s + * that are passed in the constructor. Opposed to the C++ && + * operator, the AndVerifier does not perform any short-circut evaluation. + */ +struct AndVerifier : public Verifier { + /** + * Constructs an AndVerifier with two Verifiers which must be cleared by incoming + * values in order to pass this Verifier. + * \param lhs The first Verifier that is to be tested + * \param rhs The second Verifier that is to be tested + * \pre lhs must not be nullptr + * \pre rhs must not be nullptr + */ + AndVerifier(Verifier* lhs, Verifier* rhs); + + /** + * Checks whether the \p dictionary contains the \p key and whether this key passes + * both Verifier%'s that were passed in the constructor. If the value fails either + * of the two Verifiers, it is only added once to the TestResult::offenses list with + * a reason of TestResult::Offense::Reason::Verification. + * \param dictionary The ghoul::Dictionary that is to be tested + * \param key The key contained in \p dictionary that is to be tested + * \return A TestResult object that contains the test results. If the value fails + * either of the two Verifiers, TestResult::success is \c false and the + * TestResult::offenses list contains \p with a reason of + * TestResult::Offense::Reason::Verification. If \p key%'s value passes both + * Verifier%s, the result's TestResult::success is \c true and the + * TestResult::offenses is empty. + */ + TestResult operator()(const ghoul::Dictionary& dictionary, + const std::string& key) const override; + + std::string type() const override; + std::string documentation() const override; + + /// The first Verifier that incoming values are tested against + std::shared_ptr lhs; + /// The second Verifier that incoming values are tested against + std::shared_ptr rhs; +}; + +/** +* This Verifier takes two Verifiers and performs a boolean \c or operation on their +* results. In essence, a value only passes this Verifier if it passes either of the two +* Verifier%s that are passed in the constructor. Opposed to the C++ +* || operator, the OrVerifier does not perform any short-circut evaluation. +*/ +struct OrVerifier : public Verifier { + /** + * Constructs an OrVerifier with two Verifiers, either of which must be cleared by + * incoming values in order to pass this Verifier. + * \param lhs The first Verifier that is to be tested + * \param rhs The second Verifier that is to be tested + * \pre lhs must not be nullptr + * \pre rhs must not be nullptr + */ + OrVerifier(Verifier* lhs, Verifier* rhs); + + /** + * Checks whether the \p dictionary contains the \p key and whether this key passes + * either of the two Verifier%'s that were passed in the constructor. If the value + * fails both Verifiers, it is added to the TestResult::offenses list with a reason of + * TestResult::Offense::Reason::Verification. + * \param dictionary The ghoul::Dictionary that is to be tested + * \param key The key contained in \p dictionary that is to be tested + * \return A TestResult object that contains the test results. If the value fails + * both Verifiers, TestResult::success is \c false and the TestResult::offenses list + * contains \p with a reason of TestResult::Offense::Reason::Verification. If \p key%'s + * value passes either of the two Verifier%s, the result's TestResult::success is + * \c true and the TestResult::offenses is empty. + */ + TestResult operator()(const ghoul::Dictionary& dict, + const std::string& key) const override; + + std::string type() const override; + std::string documentation() const override; + + /// The first Verifier that incoming values are tested against + std::shared_ptr lhs; + /// The second Verifier that incoming values are tested against + std::shared_ptr rhs; +}; + +/// A short-hand definition for a Verifier checking for glm::bvec2 +using BoolVector2Verifier = Vector2Verifier; +/// A short-hand definition for a Verifier checking for glm::ivec2 +using IntVector2Verifier = Vector2Verifier; +/// A short-hand definition for a Verifier checking for glm::dvec2 +using DoubleVector2Verifier = Vector2Verifier; +/// A short-hand definition for a Verifier checking for glm::bvec3 +using BoolVector3Verifier = Vector3Verifier; +/// A short-hand definition for a Verifier checking for glm::ivec3 +using IntVector3Verifier = Vector3Verifier; +/// A short-hand definition for a Verifier checking for glm::dvec3 +using DoubleVector3Verifier = Vector3Verifier; +/// A short-hand definition for a Verifier checking for glm::bvec4 +using BoolVector4Verifier = Vector4Verifier; +/// A short-hand definition for a Verifier checking for glm::ivec4 +using IntVector4Verifier = Vector4Verifier; +/// A short-hand definition for a Verifier checking for glm::dvec4 +using DoubleVector4Verifier = Vector4Verifier; + +/// A short-hand definition for a LessVerifier with a type check for \c int +using IntLessVerifier = LessVerifier; +/// A short-hand definition for a LessVerifier with a type check for \c double +using DoubleLessVerifier = LessVerifier; +/// A short-hand definition for a LessEqualVerifier with a type check for \c int +using IntLessEqualVerifier = LessEqualVerifier; +/// A short-hand definition for a LessEqualVerifier with a type check for \c double +using DoubleLessEqualVerifier = LessEqualVerifier; +/// A short-hand definition for a GreaterVerifier with a type check for \c int +using IntGreaterVerifier = GreaterVerifier; +/// A short-hand definition for a GreaterVerifier with a type check for \c double +using DoubleGreaterVerifier = GreaterVerifier; +/// A short-hand definition for a GreaterEqualVerifier with a type check for \c int +using IntGreaterEqualVerifier = GreaterEqualVerifier; +/// A short-hand definition for a GreaterEqualVerifier with a type check for \c double +using DoubleGreaterEqualVerifier = GreaterEqualVerifier; +/// A short-hand definition for a EqualVerifier with a type check for \c bool +using BoolEqualVerifier = EqualVerifier; +/// A short-hand definition for a EqualVerifier with a type check for \c int +using IntEqualVerifier = EqualVerifier; +/// A short-hand definition for a EqualVerifier with a type check for \c double +using DoubleEqualVerifier = EqualVerifier; +/// A short-hand definition for a EqualVerifier with a type check for \c string +using StringEqualVerifier = EqualVerifier; +/// A short-hand definition for a UnequalVerifier with a type check for \c bool +using BoolUnequalVerifier = UnequalVerifier; +/// A short-hand definition for a UnequalVerifier with a type check for \c int +using IntUnequalVerifier = UnequalVerifier; +/// A short-hand definition for a UnequalVerifier with a type check for \c double +using DoubleUnequalVerifier = UnequalVerifier; +/// A short-hand definition for a UnequalVerifier with a type check for \c string +using StringUnequalVerifier = UnequalVerifier; + +/// A short-hand definition for a InListVerifier with a type check for \c bool +using BoolInListVerifier = InListVerifier; +/// A short-hand definition for a InListVerifier with a type check for \c int +using IntInListVerifier = InListVerifier; +/// A short-hand definition for a InListVerifier with a type check for \c double +using DoubleInListVerifier = InListVerifier; +/// A short-hand definition for a InListVerifier with a type check for \c string +using StringInListVerifier = InListVerifier; +/// A short-hand definition for a NotInListVerifier with a type check for \c bool +using BoolNotInListVerifier = NotInListVerifier; +/// A short-hand definition for a NotInListVerifier with a type check for \c int +using IntNotInListVerifier = NotInListVerifier; +/// A short-hand definition for a NotInListVerifier with a type check for \c double +using DoubleNotInListVerifier = NotInListVerifier; +/// A short-hand definition for a NotInListVerifier with a type check for \c string +using StringNotInListVerifier = NotInListVerifier; + +/// A short-hand definition for a InRangeVerifier with a type check for \c int +using IntInRangeVerifier = InRangeVerifier; +/// A short-hand definition for a InRangeVerifier with a type check for \c double +using DoubleInRangeVerifier = InRangeVerifier; +/// A short-hand definition for a NotInRangeVerifier with a type check for \c int +using IntNotInRangeVerifier = NotInRangeVerifier; +/// A short-hand definition for a NotInRangeVerifier with a type check for \c double +using DoubleNotInRangeVerifier = NotInRangeVerifier; + +/// A short-hand definition for a AnnotationVerifier with a type check for \c bool +using BoolAnnotationVerifier = AnnotationVerifier; +/// A short-hand definition for a AnnotationVerifier with a type check for \c int +using IntAnnotationVerifier = AnnotationVerifier; +/// A short-hand definition for a AnnotationVerifier with a type check for \c double +using DoubleAnnotationVerifier = AnnotationVerifier; +/// A short-hand definition for a AnnotationVerifier with a type check for \c string +using StringAnnotationVerifier = AnnotationVerifier; +/// A short-hand definition for a AnnotationVerifier with a type check for +/// ghoul::Dictionary +using TableAnnotationVerifier = AnnotationVerifier; + +// Definitions of external templates that are instantiated in the cpp file +// This cuts down the compilation times as almost all of the possible template types do +// not need to be instantiated multiple times +extern template struct Vector2Verifier; +extern template struct Vector2Verifier; +extern template struct Vector2Verifier; +extern template struct Vector3Verifier; +extern template struct Vector3Verifier; +extern template struct Vector3Verifier; +extern template struct Vector4Verifier; +extern template struct Vector4Verifier; +extern template struct Vector4Verifier; + +extern template struct LessVerifier; +extern template struct LessVerifier; +extern template struct LessEqualVerifier; +extern template struct LessEqualVerifier; +extern template struct GreaterVerifier; +extern template struct GreaterVerifier; +extern template struct GreaterEqualVerifier; +extern template struct GreaterEqualVerifier; +extern template struct EqualVerifier; +extern template struct EqualVerifier; +extern template struct EqualVerifier; +extern template struct EqualVerifier; +extern template struct UnequalVerifier; +extern template struct UnequalVerifier; +extern template struct UnequalVerifier; +extern template struct UnequalVerifier; + +extern template struct InListVerifier; +extern template struct InListVerifier; +extern template struct InListVerifier; +extern template struct InListVerifier; +extern template struct NotInListVerifier; +extern template struct NotInListVerifier; +extern template struct NotInListVerifier; +extern template struct NotInListVerifier; + +extern template struct InRangeVerifier; +extern template struct InRangeVerifier; +extern template struct NotInRangeVerifier; +extern template struct NotInRangeVerifier; + +extern template struct AnnotationVerifier; +extern template struct AnnotationVerifier; +extern template struct AnnotationVerifier; +extern template struct AnnotationVerifier; +extern template struct AnnotationVerifier; + + +} // namespace documentation +} // namespace openspace + +#include "verifier.inl" + +#endif // __VERIFIER_H__ diff --git a/include/openspace/documentation/verifier.inl b/include/openspace/documentation/verifier.inl new file mode 100644 index 0000000000..5c4fad2389 --- /dev/null +++ b/include/openspace/documentation/verifier.inl @@ -0,0 +1,303 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +namespace std { + std::string to_string(std::string value); +} + +namespace openspace { +namespace documentation { + +template +TestResult TemplateVerifier::operator()(const ghoul::Dictionary& dict, + const std::string& key) const +{ + if (dict.hasKeyAndValue(key)) { + return { true, {} }; + } + else { + if (dict.hasKey(key)) { + return { false, { { key, TestResult::Offense::Reason::WrongType } } }; + } + else { + return { false, { { key, TestResult::Offense::Reason::MissingKey } } }; + } + } +} + +template +std::string TemplateVerifier::documentation() const { + return "Type testing of '" + type() + "'"; +} + +template +std::string Vector2Verifier::type() const { + using namespace std::string_literals; + + return "Vector2<"s + typeid(T).name() + ">"; +} + +template +std::string Vector3Verifier::type() const { + using namespace std::string_literals; + + return "Vector3<"s + typeid(T).name() + ">"; +} + +template +std::string Vector4Verifier::type() const { + using namespace std::string_literals; + + return "Vector4<"s + typeid(T).name() + ">"; +} + +template +OperatorVerifier::OperatorVerifier(typename T::Type value) + : value(std::move(value)) +{} + +template +TestResult OperatorVerifier::operator()(const ghoul::Dictionary& dict, + const std::string& key) const +{ + TestResult res = T::operator()(dict, key); + if (res.success) { + if (Operator()(dict.value(key), value)) { + return { true, {} }; + } + else { + return { false, { { key, TestResult::Offense::Reason::Verification }}}; + } + } + else { + return res; + } +} + +template +std::string LessVerifier::documentation() const { + return "Less than: " + std::to_string(value); +} + +template +std::string LessEqualVerifier::documentation() const { + return "Less or equal to: " + std::to_string(value); +} + +template +std::string GreaterVerifier::documentation() const { + return "Greater than: " + std::to_string(value); +} + +template +std::string GreaterEqualVerifier::documentation() const { + return "Greater or equal to: " + std::to_string(value); +} + +template +std::string EqualVerifier::documentation() const { + return "Equal to: " + std::to_string(value); +} + +template +std::string UnequalVerifier::documentation() const { + return "Unequal to: " + std::to_string(value); +} + +template +InListVerifier::InListVerifier(std::vector values) + : values(std::move(values)) +{} + +template +TestResult InListVerifier::operator()(const ghoul::Dictionary& dict, + const std::string& key) const +{ + TestResult res = T::operator()(dict, key); + if (res.success) { + typename T::Type value = dict.value(key); + + auto it = std::find(values.begin(), values.end(), value); + + if (it != values.end()) { + return { true, {} }; + } + else { + return { false, { { key, TestResult::Offense::Reason::Verification } } }; + } + } + else { + return res; + } +} + +template +std::string InListVerifier::documentation() const { + std::string result = "In list { "; + + std::stringstream s; + std::copy( + values.begin(), + values.end(), + std::ostream_iterator(s, ",") + ); + + std::string joined = s.str(); + // We need to remove a trailing ',' at the end of the string + result += joined.substr(0, joined.size() - 1); + + result += " }"; + return result; +} + +template +NotInListVerifier::NotInListVerifier(std::vector values) + : values(std::move(values)) +{} + +template +TestResult NotInListVerifier::operator()(const ghoul::Dictionary& dict, + const std::string& key) const +{ + TestResult res = T::operator()(dict, key); + if (res.success) { + typename T::Type value = dict.value(key); + + auto it = std::find(values.begin(), values.end(), value); + + if (it == values.end()) { + return { true, {} }; + } + else { + return { false, { { key, TestResult::Offense::Reason::Verification } } }; + } + } + else { + return res; + } +} + +template +std::string NotInListVerifier::documentation() const { + std::string result = "Not in list { "; + + std::stringstream s; + std::copy( + values.begin(), + values.end(), + std::ostream_iterator(s, ",") + ); + + std::string joined = s.str(); + // We need to remove a trailing ',' at the end of the string + result += joined.substr(0, joined.size() - 1); + + result += " }"; + return result; +} + +template +InRangeVerifier::InRangeVerifier(typename T::Type lower, typename T::Type upper) + : lower(std::move(lower)) + , upper(std::move(upper)) +{ + ghoul_assert(lower <= upper, "lower must be smaller or equal to upper"); +} + +template +TestResult InRangeVerifier::operator()(const ghoul::Dictionary& dict, + const std::string& key) const +{ + TestResult res = T::operator()(dict, key); + if (res.success) { + typename T::Type val = dict.value(key); + + if (val >= lower && val <= upper) { + return { true, {} }; + } + else { + return { false, { { key, TestResult::Offense::Reason::Verification } } }; + } + } + else { + return res; + } +} + +template +std::string InRangeVerifier::documentation() const { + return "In range: ( " + std::to_string(lower) + "," + + std::to_string(upper) + " )"; +} + +template +NotInRangeVerifier::NotInRangeVerifier(typename T::Type lower, typename T::Type upper) + : lower(std::move(lower)) + , upper(std::move(upper)) +{ + ghoul_assert(lower <= upper, "lower must be smaller or equal to upper"); +} + +template +TestResult NotInRangeVerifier::operator()(const ghoul::Dictionary& dict, + const std::string& key) const { + TestResult res = T::operator()(dict, key); + if (res.success) { + typename T::Type val = dict.value(key); + + if (val >= lower && val <= upper) { + return { false, { { key, TestResult::Offense::Reason::Verification } } }; + } + else { + return { true, {} }; + } + } + else { + return res; + } +} + +template +std::string NotInRangeVerifier::documentation() const { + return "Not in range: ( " + std::to_string(lower) + "," + + std::to_string(upper) + " )"; +} + + +template +AnnotationVerifier::AnnotationVerifier(std::string annotation) + : annotation(std::move(annotation)) +{ + ghoul_assert(!this->annotation.empty(), "Annotation must not be empty"); +} + +template +std::string AnnotationVerifier::documentation() const { + return annotation; +} + +} // namespace documentation +} // namespace openspace diff --git a/include/openspace/engine/configurationmanager.h b/include/openspace/engine/configurationmanager.h index 23a5109043..f242c6440f 100644 --- a/include/openspace/engine/configurationmanager.h +++ b/include/openspace/engine/configurationmanager.h @@ -25,6 +25,8 @@ #ifndef __CONFIGURATIONMANAGER_H__ #define __CONFIGURATIONMANAGER_H__ +#include + #include namespace openspace { @@ -49,22 +51,22 @@ public: /// The key that stores the location of the SGCT configuration file that is used on /// application launch static const std::string KeyConfigSgct; - /// The key that stores the type of Lua documentation that should be stored - static const std::string KeyLuaDocumentationType; - /// The key that stores the save location of the Lua documentation - static const std::string KeyLuaDocumentationFile; - /// The key that stores the type of scripting log that should be stored - static const std::string KeyScriptLogType; - /// The key that stores the save location of the scripting log - static const std::string KeyScriptLogFile; - /// The key that stores the type of Property documentation that should be stored - static const std::string KeyPropertyDocumentationType; - /// The key that stores the save location of the Property documentation - static const std::string KeyPropertyDocumentationFile; - /// The key that stores the type of keyboard bindings that should be stored - static const std::string KeyKeyboardShortcutsType; - /// The key that stores the save location of the keyboard bindings file - static const std::string KeyKeyboardShortcutsFile; + /// The part of the key that defines the type + static const std::string PartType; + /// The part of the key that defines the file + static const std::string PartFile; + /// The key that stores the Lua documentation + static const std::string KeyLuaDocumentation; + /// The key that stores the scripting log + static const std::string KeyScriptLog; + /// The key that stores the Property documentation + static const std::string KeyPropertyDocumentation; + /// The key that stores the keyboard bindings that should be stored + static const std::string KeyKeyboardShortcuts; + /// The key that stores the main documentation + static const std::string KeyDocumentation; + /// The key that stores the factory documentation values + static const std::string KeyFactoryDocumentation; /// The key that stores the location of the scene file that is initially loaded static const std::string KeyConfigScene; /// The key that stores the subdirectory containing a list of all startup scripts to @@ -73,18 +75,24 @@ public: /// The key that stores the subdirectory containing a list of all settings scripts to /// be executed on application start and after the scene file is loaded static const std::string KeySettingsScript; + /// The key that stores the settings for determining log-related settings + static const std::string KeyLogging; /// The key that stores the desired LogLevel for the whole application /// \sa ghoul::logging::LogManager - static const std::string KeyLogLevel; + static const std::string PartLogLevel; /// The key that stores whether the log should be immediately flushed after a n /// \sa ghoul::logging::LogManager - static const std::string KeyLogImmediateFlush; + static const std::string PartImmediateFlush; /// The key that stores a subdirectory with a description for additional /// ghoul::logging::Log%s to be created /// \sa LogFactory - static const std::string KeyLogs; + static const std::string PartLogs; + /// The key that stores whether a log should be appended to or should be overwritten + static const std::string PartAppend; /// The key that stores the verbosity (None, Minimal, Default, Full) of the system /// capabilities components + static const std::string PartCapabilitiesVerbosity; + /// The full key that stores the verbosity of the system capabilities component static const std::string KeyCapabilitiesVerbosity; /// The key that stores the time (in seconds) that the application will wait before /// shutting down after the shutdown call is made @@ -95,6 +103,9 @@ public: /// The key that sets the request URL that is used to request additional data to be /// downloaded static const std::string KeyDownloadRequestURL; + /// The key that stores the switch for enabling/disabling the rendering on a master + /// computer + static const std::string KeyRenderingMethod; /** * Iteratively walks the directory structure starting with \p filename to find the @@ -123,6 +134,8 @@ public: */ void loadFromFile(const std::string& filename); + static Documentation Documentation(); + private: /** * Checks whether the loaded configuration file is complete, that is specifying the diff --git a/include/openspace/interaction/interactionmode.h b/include/openspace/interaction/interactionmode.h index 1a53683c8f..22c495266c 100644 --- a/include/openspace/interaction/interactionmode.h +++ b/include/openspace/interaction/interactionmode.h @@ -179,9 +179,9 @@ public: class MouseStates { public: - /*! + /** \param sensitivity - \param velocityScalefactor can be set to 60 to remove the inertia of the + \param velocityScaleFactor can be set to 60 to remove the inertia of the interaction. Lower value will make it harder to move the camera. */ MouseStates(double sensitivity, double velocityScaleFactor); diff --git a/include/openspace/properties/numericalproperty.h b/include/openspace/properties/numericalproperty.h index 33a179b7fd..ffa257a16c 100644 --- a/include/openspace/properties/numericalproperty.h +++ b/include/openspace/properties/numericalproperty.h @@ -48,7 +48,10 @@ public: bool setStringValue(std::string value) override; T minValue() const; + void setMinValue(T value); + T maxValue() const; + void setMaxValue(T value); virtual std::string className() const override; diff --git a/include/openspace/properties/numericalproperty.inl b/include/openspace/properties/numericalproperty.inl index 964c29190d..86f04e8f62 100644 --- a/include/openspace/properties/numericalproperty.inl +++ b/include/openspace/properties/numericalproperty.inl @@ -329,11 +329,21 @@ T NumericalProperty::minValue() const { return _minimumValue; } +template +void NumericalProperty::setMinValue(T value) { + _minimumValue = std::move(value); +} + template T NumericalProperty::maxValue() const { return _maximumValue; } +template +void NumericalProperty::setMaxValue(T value) { + _maximumValue = std::move(value); +} + template std::string NumericalProperty::generateAdditionalDescription() const { std::string result; diff --git a/include/openspace/properties/optionproperty.h b/include/openspace/properties/optionproperty.h index 42bca29af6..39466779cf 100644 --- a/include/openspace/properties/optionproperty.h +++ b/include/openspace/properties/optionproperty.h @@ -60,9 +60,16 @@ public: * to its super class. * \param identifier A unique identifier for this property * \param guiName The GUI name that should be used to represent this property - * \param displayType Optional DisplayType for GUI (default RADIO) */ OptionProperty(std::string identifier, std::string guiName); + + /** + * The constructor delegating the identifier and the guiName + * to its super class. + * \param identifier A unique identifier for this property + * \param guiName The GUI name that should be used to represent this property + * \param displayType Optional DisplayType for GUI (default RADIO) + */ OptionProperty(std::string identifier, std::string guiName, DisplayType displayType); /** diff --git a/include/openspace/properties/property.h b/include/openspace/properties/property.h index 3ec235e341..c3cdb71572 100644 --- a/include/openspace/properties/property.h +++ b/include/openspace/properties/property.h @@ -71,6 +71,8 @@ public: * \param identifier A unique identifier for this property. It has to be unique to the * PropertyOwner and cannot contain any .s * \param guiName The human-readable GUI name for this Property + * \pre \p identifier must not be empty + * \pre \p guiName must not be empty */ Property(std::string identifier, std::string guiName); diff --git a/include/openspace/properties/scalarproperty.h b/include/openspace/properties/scalarproperty.h index 29cdb9100c..7576edd37d 100644 --- a/include/openspace/properties/scalarproperty.h +++ b/include/openspace/properties/scalarproperty.h @@ -25,6 +25,77 @@ #ifndef __SCALARPROPERTY_H__ #define __SCALARPROPERTY_H__ + /** + * \file scalarproperty.h + * + * \addtogroup openspace + * @{ + * \addtogroup properties + * @{ + + * \class BoolProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type bool. + + * \class CharProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type char. + + * \class SignedCharProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type signed char. + + * \class UCharProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type unsigned char. + + * \class ShortProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type short. + + * \class UShortProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type unsigned short. + + * \class IntProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type int. + + * \class UIntProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type unsigned int. + + * \class LongProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type long. + + * \class ULongProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type unsigned long. + + * \class LongLongProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type long long. + + * \class ULongLongProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type unsigned long long. + + * \class FloatProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type float. + + * \class DoubleProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type double. + + * \class LongDoubleProperty + * This class is a concrete implementation of openspace::properties::TemplateProperty with + * the type long double. + + * @} @} + */ + #include "openspace/properties/numericalproperty.h" namespace openspace { diff --git a/include/openspace/properties/templateproperty.inl b/include/openspace/properties/templateproperty.inl index 3567784124..ec8f8509a6 100644 --- a/include/openspace/properties/templateproperty.inl +++ b/include/openspace/properties/templateproperty.inl @@ -38,7 +38,7 @@ namespace properties { // C++ class name for which a typedef will be created // TYPE = The template parameter T for which the TemplateProperty is specialized #define REGISTER_TEMPLATEPROPERTY_HEADER(CLASS_NAME, TYPE) \ - typedef TemplateProperty CLASS_NAME; \ + using CLASS_NAME = TemplateProperty; \ \ template <> \ std::string PropertyDelegate>::className(); \ diff --git a/include/openspace/rendering/abufferrenderer.h b/include/openspace/rendering/abufferrenderer.h index 4a9689fa9f..8a8dd5dba6 100644 --- a/include/openspace/rendering/abufferrenderer.h +++ b/include/openspace/rendering/abufferrenderer.h @@ -105,7 +105,7 @@ private: /** * When a volume is attached or detached from the scene graph, * the resolve program needs to be recompiled. - * The #_volumes map keeps track of which volumes that can + * The _volumes map keeps track of which volumes that can * be rendered using the current resolve program, along with their raycast data * (id, namespace, etc) */ diff --git a/include/openspace/rendering/renderable.h b/include/openspace/rendering/renderable.h index 790c01419b..77904bd646 100644 --- a/include/openspace/rendering/renderable.h +++ b/include/openspace/rendering/renderable.h @@ -1,4 +1,3 @@ - /***************************************************************************************** * * * OpenSpace * @@ -33,6 +32,8 @@ #include +#include + // Forward declare to minimize dependencies namespace ghoul { @@ -51,6 +52,13 @@ class PowerScaledCoordinate; class Renderable : public properties::PropertyOwner { public: + enum class RenderBin : int { + Background = 1, + Opaque = 2, + Transparent = 4, + Overlay = 8 + }; + static Renderable* createFromDictionary(const ghoul::Dictionary& dictionary); // constructors & destructor @@ -64,14 +72,18 @@ public: virtual bool isReady() const = 0; bool isEnabled() const; - void setBoundingSphere(const PowerScaledScalar& boundingSphere); - const PowerScaledScalar& getBoundingSphere(); + void setBoundingSphere(PowerScaledScalar boundingSphere); + PowerScaledScalar getBoundingSphere(); virtual void render(const RenderData& data); virtual void render(const RenderData& data, RendererTasks& rendererTask); virtual void postRender(const RenderData& data); virtual void update(const UpdateData& data); + RenderBin renderBin() const; + void setRenderBin(RenderBin bin); + bool matchesRenderBinMask(int binMask); + bool isVisible() const; bool hasTimeInterval(); @@ -81,10 +93,13 @@ public: static void setPscUniforms(ghoul::opengl::ProgramObject& program, const Camera& camera, const PowerScaledCoordinate& position); + static Documentation Documentation(); + protected: properties::BoolProperty _enabled; private: + RenderBin _renderBin; PowerScaledScalar boundingSphere_; std::string _startTime; std::string _endTime; diff --git a/include/openspace/rendering/screenspacerenderable.h b/include/openspace/rendering/screenspacerenderable.h index 9fa4c175c8..46cea14e32 100644 --- a/include/openspace/rendering/screenspacerenderable.h +++ b/include/openspace/rendering/screenspacerenderable.h @@ -35,6 +35,8 @@ #include #include +#include + namespace openspace { /** @@ -63,6 +65,8 @@ public: glm::vec3 sphericalPosition() const; float depth() const; + static openspace::Documentation Documentation(); + protected: void createPlane(); void useEuclideanCoordinates(bool b); diff --git a/include/openspace/scene/ephemeris.h b/include/openspace/scene/ephemeris.h index 026e024b24..ff7ae0a441 100644 --- a/include/openspace/scene/ephemeris.h +++ b/include/openspace/scene/ephemeris.h @@ -29,6 +29,8 @@ #include #include +#include + namespace openspace { class Ephemeris { @@ -41,6 +43,8 @@ public: virtual const glm::dvec3& position() const = 0; virtual void update(const UpdateData& data); + static openspace::Documentation Documentation(); + protected: Ephemeris(); }; diff --git a/include/openspace/scene/rotation.h b/include/openspace/scene/rotation.h index f5f3620382..42e35bd3bd 100644 --- a/include/openspace/scene/rotation.h +++ b/include/openspace/scene/rotation.h @@ -28,6 +28,8 @@ #include #include +#include + namespace openspace { class Rotation { @@ -40,6 +42,8 @@ public: virtual const glm::dmat3& matrix() const = 0; virtual void update(const UpdateData& data); + static openspace::Documentation Documentation(); + protected: Rotation(); }; diff --git a/include/openspace/scene/scale.h b/include/openspace/scene/scale.h index d95cec819c..86e4fcaac8 100644 --- a/include/openspace/scene/scale.h +++ b/include/openspace/scene/scale.h @@ -29,6 +29,8 @@ #include #include +#include + namespace openspace { class Scale : public properties::PropertyOwner { @@ -41,6 +43,8 @@ public: virtual double scaleValue() const = 0; virtual void update(const UpdateData& data); + static openspace::Documentation Documentation(); + protected: Scale(); }; diff --git a/include/openspace/scene/scene.h b/include/openspace/scene/scene.h index 4844d3ac7b..0e748a2f65 100644 --- a/include/openspace/scene/scene.h +++ b/include/openspace/scene/scene.h @@ -31,6 +31,8 @@ #include #include +#include + #include #include #include @@ -117,6 +119,8 @@ public: */ static scripting::LuaLibrary luaLibrary(); + static documentation::Documentation Documentation(); + private: bool loadSceneInternal(const std::string& sceneDescriptionFilePath); diff --git a/include/openspace/scene/scenegraphnode.h b/include/openspace/scene/scenegraphnode.h index 4979eedcf8..a7a3c4cac7 100644 --- a/include/openspace/scene/scenegraphnode.h +++ b/include/openspace/scene/scenegraphnode.h @@ -26,6 +26,8 @@ #define __SCENEGRAPHNODE_H__ // open space includes +#include + #include #include #include @@ -106,6 +108,8 @@ public: _ephemeris = eph; } + static documentation::Documentation Documentation(); + private: bool sphereInsideFrustum(const psc& s_pos, const PowerScaledScalar& s_rad, const Camera* camera); diff --git a/include/openspace/scripting/scriptengine.h b/include/openspace/scripting/scriptengine.h index 656d201224..89edffff5d 100644 --- a/include/openspace/scripting/scriptengine.h +++ b/include/openspace/scripting/scriptengine.h @@ -70,7 +70,7 @@ public: bool runScript(const std::string& script); bool runScriptFile(const std::string& filename); - bool writeDocumentation(const std::string& filename, const std::string& type) const; + void writeDocumentation(const std::string& filename, const std::string& type) const; bool writeLog(const std::string& script); diff --git a/include/openspace/util/factorymanager.h b/include/openspace/util/factorymanager.h index 04903eb82b..acee69b919 100644 --- a/include/openspace/util/factorymanager.h +++ b/include/openspace/util/factorymanager.h @@ -86,9 +86,11 @@ public: /** * Adds the passed \p factory to the FactoryManager. Factories may only be added once. * \param factory The ghoul::TemplateFactory to add to this FactoryManager + * \param name A user-readable name for the registered factory. * \pre \p factory must not be nullptr */ - void addFactory(std::unique_ptr factory); + void addFactory(std::unique_ptr factory, + std::string name = ""); /** * This method provides access to all registered ghoul::TemplateFactory%s through @@ -102,11 +104,25 @@ public: template ghoul::TemplateFactory* factory() const; + /** + * Writes a documentation for the FactoryMananger that contains all of the registered + * factories and for each factory all registered class names. + * \param file The file to which the documentation will be written + * \param type The type of documentation that will be written + * \pre \p file must not be empty + * \pre \p type must not be empty + */ + void writeDocumentation(const std::string& file, const std::string& type); + private: /// Singleton member for the Factory Manager static FactoryManager* _manager; - std::vector> _factories; + struct FactoryInfo { + std::unique_ptr factory; + std::string name; + }; + std::vector _factories; }; } // namespace openspace diff --git a/include/openspace/util/factorymanager.inl b/include/openspace/util/factorymanager.inl index 7f9989f619..dde01bcef5 100644 --- a/include/openspace/util/factorymanager.inl +++ b/include/openspace/util/factorymanager.inl @@ -27,8 +27,8 @@ namespace openspace { template ghoul::TemplateFactory* FactoryManager::factory() const { for (auto& factory : _factories) { - if (factory->baseClassType() == typeid(T)) - return dynamic_cast*>(factory.get()); + if (factory.factory->baseClassType() == typeid(T)) + return dynamic_cast*>(factory.factory.get()); } throw FactoryNotFoundError(typeid(T).name()); diff --git a/include/openspace/util/openspacemodule.h b/include/openspace/util/openspacemodule.h index 350c853f65..2cda76686a 100644 --- a/include/openspace/util/openspacemodule.h +++ b/include/openspace/util/openspacemodule.h @@ -27,6 +27,8 @@ #include +#include + #include #include @@ -64,6 +66,8 @@ public: */ void deinitialize(); + virtual std::vector documentations() const; + protected: /** * Customization point for each derived class. The internalInitialize method is called diff --git a/include/openspace/util/updatestructures.h b/include/openspace/util/updatestructures.h index 44e8ff25e3..22ed70bef7 100644 --- a/include/openspace/util/updatestructures.h +++ b/include/openspace/util/updatestructures.h @@ -50,12 +50,14 @@ struct UpdateData { bool doPerformanceMeasurement; }; + struct RenderData { const Camera& camera; // psc position to be removed in favor of the double precision position defined in // the translation in transform. psc position; bool doPerformanceMeasurement; + int renderBinMask; TransformData modelTransform; }; diff --git a/modules/base/basemodule.cpp b/modules/base/basemodule.cpp index 196913f848..8694b42b35 100644 --- a/modules/base/basemodule.cpp +++ b/modules/base/basemodule.cpp @@ -65,12 +65,27 @@ BaseModule::BaseModule() {} void BaseModule::internalInitialize() { - FactoryManager::ref().addFactory(std::make_unique>()); - FactoryManager::ref().addFactory(std::make_unique>()); - FactoryManager::ref().addFactory(std::make_unique>()); + FactoryManager::ref().addFactory( + std::make_unique>(), + "PlanetGeometry" + ); + FactoryManager::ref().addFactory( + std::make_unique>(), + "ModelGeometry" + ); + FactoryManager::ref().addFactory( + std::make_unique>(), + "ScreenSpaceRenderable" + ); - FactoryManager::ref().addFactory(std::make_unique>()); - FactoryManager::ref().addFactory(std::make_unique>()); + FactoryManager::ref().addFactory( + std::make_unique>(), + "Rotation" + ); + FactoryManager::ref().addFactory( + std::make_unique>(), + "Scale" + ); auto fScreenSpaceRenderable = FactoryManager::ref().factory(); ghoul_assert(fScreenSpaceRenderable, "ScreenSpaceRenderable factory was not created"); @@ -119,4 +134,10 @@ void BaseModule::internalInitialize() { fModelGeometry->registerClass("MultiModelGeometry"); } +std::vector BaseModule::documentations() const { + return { + StaticScale::Documentation() + }; +} + } // namespace openspace diff --git a/modules/base/basemodule.h b/modules/base/basemodule.h index ffad43acf1..f9838dbe31 100644 --- a/modules/base/basemodule.h +++ b/modules/base/basemodule.h @@ -33,6 +33,8 @@ class BaseModule : public OpenSpaceModule { public: BaseModule(); + std::vector documentations() const override; + protected: void internalInitialize() override; }; diff --git a/modules/base/ephemeris/spiceephemeris.cpp b/modules/base/ephemeris/spiceephemeris.cpp index 36c81ac26a..85df8a21c9 100644 --- a/modules/base/ephemeris/spiceephemeris.cpp +++ b/modules/base/ephemeris/spiceephemeris.cpp @@ -27,6 +27,8 @@ #include #include +#include + namespace { const std::string _loggerCat = "SpiceEphemeris"; //const std::string keyGhosting = "EphmerisGhosting"; @@ -59,8 +61,15 @@ SpiceEphemeris::SpiceEphemeris(const ghoul::Dictionary& dictionary) for (size_t i = 1; i <= kernels.size(); ++i) { std::string kernel; bool success = kernels.getValue(std::to_string(i), kernel); - if (!success) + if (!success) { LERROR("'" << KeyKernels << "' has to be an array-style table"); + break; + } + + if (!FileSys.fileExists(kernel)) { + LERROR("Kernel '" << kernel << "' does not exist"); + continue; + } try { SpiceManager::ref().loadKernel(kernel); diff --git a/modules/base/rendering/modelgeometry.cpp b/modules/base/rendering/modelgeometry.cpp index 507e38ce81..655c883a2e 100644 --- a/modules/base/rendering/modelgeometry.cpp +++ b/modules/base/rendering/modelgeometry.cpp @@ -125,9 +125,9 @@ bool ModelGeometry::initialize(Renderable* parent) { for (auto v: _vertices) { maximumDistanceSquared = glm::max( - glm::pow(v.location[0], 2) + - glm::pow(v.location[1], 2) + - glm::pow(v.location[2], 2), maximumDistanceSquared); + glm::pow(v.location[0], 2.f) + + glm::pow(v.location[1], 2.f) + + glm::pow(v.location[2], 2.f), maximumDistanceSquared); } _parent->setBoundingSphere(PowerScaledScalar(glm::sqrt(maximumDistanceSquared), 0.0)); diff --git a/modules/base/rendering/renderablemodel.cpp b/modules/base/rendering/renderablemodel.cpp index 5366b3a3bc..c98e2480c5 100644 --- a/modules/base/rendering/renderablemodel.cpp +++ b/modules/base/rendering/renderablemodel.cpp @@ -211,7 +211,7 @@ void RenderableModel::render(const RenderData& data) { glm::dmat4 modelTransform = glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation glm::dmat4(data.modelTransform.rotation) * // Spice rotation - glm::dmat4(glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale))); + glm::dmat4(glm::scale(glm::dmat4(_modelTransform), glm::dvec3(data.modelTransform.scale))); debugModelRotation; // debug model rotation controlled from GUI glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * modelTransform; diff --git a/modules/base/rendering/renderableplane.cpp b/modules/base/rendering/renderableplane.cpp index a2e7ff9b08..3d43ee6c66 100644 --- a/modules/base/rendering/renderableplane.cpp +++ b/modules/base/rendering/renderableplane.cpp @@ -60,6 +60,7 @@ RenderablePlane::RenderablePlane(const ghoul::Dictionary& dictionary) , _shader(nullptr) , _textureIsDirty(false) , _texture(nullptr) + , _blendMode(BlendMode::Normal) , _quad(0) , _vertexPositionBuffer(0) { @@ -67,7 +68,7 @@ RenderablePlane::RenderablePlane(const ghoul::Dictionary& dictionary) dictionary.getValue("Size", size); _size = size; - if (dictionary.hasKey("Name")){ + if (dictionary.hasKey("Name")) { dictionary.getValue("Name", _nodeName); } @@ -102,6 +103,13 @@ RenderablePlane::RenderablePlane(const ghoul::Dictionary& dictionary) } } + std::string blendMode; + if (dictionary.getValue("BlendMode", blendMode)) { + if (blendMode == "Additive") { + _blendMode = BlendMode::Additive; + setRenderBin(Renderable::RenderBin::Transparent); + } + } std::string texturePath = ""; bool success = dictionary.getValue("Texture", texturePath); @@ -228,9 +236,30 @@ void RenderablePlane::render(const RenderData& data) { _texture->bind(); _shader->setUniform("texture1", unit); + bool usingFramebufferRenderer = + OsEng.renderEngine().rendererImplementation() == RenderEngine::RendererImplementation::Framebuffer; + + bool usingABufferRenderer = + OsEng.renderEngine().rendererImplementation() == RenderEngine::RendererImplementation::ABuffer; + + if (usingABufferRenderer) { + _shader->setUniform("additiveBlending", _blendMode == BlendMode::Additive); + } + + bool additiveBlending = _blendMode == BlendMode::Additive && usingFramebufferRenderer; + if (additiveBlending) { + glDepthMask(false); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + } + glBindVertexArray(_quad); glDrawArrays(GL_TRIANGLES, 0, 6); + if (additiveBlending) { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(true); + } + _shader->deactivate(); } diff --git a/modules/base/rendering/renderableplane.h b/modules/base/rendering/renderableplane.h index eaa5cf4445..fc4e8f795c 100644 --- a/modules/base/rendering/renderableplane.h +++ b/modules/base/rendering/renderableplane.h @@ -51,6 +51,11 @@ class RenderablePlane : public Renderable { }; public: + enum class BlendMode : int { + Normal = 0, + Additive + }; + RenderablePlane(const ghoul::Dictionary& dictionary); ~RenderablePlane(); @@ -79,6 +84,7 @@ private: std::unique_ptr _shader; bool _textureIsDirty; std::unique_ptr _texture; + BlendMode _blendMode; ghoul::filesystem::File* _textureFile; GLuint _quad; GLuint _vertexPositionBuffer; diff --git a/modules/base/rendering/renderabletrail.cpp b/modules/base/rendering/renderabletrail.cpp index 3ca3e60ce4..a95cb3054d 100644 --- a/modules/base/rendering/renderabletrail.cpp +++ b/modules/base/rendering/renderabletrail.cpp @@ -124,6 +124,7 @@ bool RenderableTrail::initialize() { "${MODULE_BASE}/shaders/ephemeris_vs.glsl", "${MODULE_BASE}/shaders/ephemeris_fs.glsl"); + setRenderBin(Renderable::RenderBin::Overlay); if (!_programObject) return false; @@ -192,6 +193,14 @@ void RenderableTrail::render(const RenderData& data) { // _programObject->setUniform("forceFade", _distanceFade); //} + bool usingFramebufferRenderer = + OsEng.renderEngine().rendererImplementation() == RenderEngine::RendererImplementation::Framebuffer; + + if (usingFramebufferRenderer) { + glDepthMask(false); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + } + glLineWidth(_lineWidth); glBindVertexArray(_vaoID); @@ -207,6 +216,12 @@ void RenderableTrail::render(const RenderData& data) { glBindVertexArray(0); } + + if (usingFramebufferRenderer) { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(true); + } + _programObject->deactivate(); } diff --git a/modules/base/scale/staticscale.cpp b/modules/base/scale/staticscale.cpp index 99d961c50c..214847d290 100644 --- a/modules/base/scale/staticscale.cpp +++ b/modules/base/scale/staticscale.cpp @@ -24,6 +24,8 @@ #include +#include + namespace { const std::string _loggerCat = "StaticScale"; const std::string KeyValue = "Scale"; @@ -31,24 +33,35 @@ namespace { namespace openspace { -StaticScale::StaticScale(const ghoul::Dictionary& dictionary) +Documentation StaticScale::Documentation() { + using namespace openspace::documentation; + return { + "Static Scaling", + {{ + KeyValue, + new DoubleVerifier, + "The scaling factor by which the scenegraph node is scaled." + }} + }; +} + +StaticScale::StaticScale() : _scaleValue("scale", "Scale", 1.0, 1.0, 1000.0) { - const bool hasValue = dictionary.hasKeyAndValue(KeyValue); - if (hasValue) { - float value; - dictionary.getValue(KeyValue, value); - _scaleValue.setValue(value); - } - Scale::addProperty(_scaleValue); + addProperty(_scaleValue); } -StaticScale::~StaticScale() {} + +StaticScale::StaticScale(const ghoul::Dictionary& dictionary) + : StaticScale() +{ + documentation::testSpecificationAndThrow(Documentation(), dictionary, "StaticScale"); + + _scaleValue = dictionary.value(KeyValue); +} double StaticScale::scaleValue() const { - return _scaleValue.value(); + return _scaleValue; } -void StaticScale::update(const UpdateData&) {} - -} // namespace openspace \ No newline at end of file +} // namespace openspace diff --git a/modules/base/scale/staticscale.h b/modules/base/scale/staticscale.h index 196956d9ab..e99e07fd60 100644 --- a/modules/base/scale/staticscale.h +++ b/modules/base/scale/staticscale.h @@ -27,17 +27,20 @@ #include +#include + namespace openspace { -class StaticScale: public Scale { +class StaticScale : public Scale { public: - StaticScale(const ghoul::Dictionary& dictionary = ghoul::Dictionary()); - virtual ~StaticScale(); - virtual double scaleValue() const; - virtual void update(const UpdateData& data) override; + StaticScale(); + StaticScale(const ghoul::Dictionary& dictionary); + double scaleValue() const; + + static openspace::Documentation Documentation(); + private: properties::FloatProperty _scaleValue; - //double _scaleValue; }; } // namespace openspace diff --git a/modules/base/shaders/ephemeris_fs.glsl b/modules/base/shaders/ephemeris_fs.glsl index 46e46fa916..a77149b371 100644 --- a/modules/base/shaders/ephemeris_fs.glsl +++ b/modules/base/shaders/ephemeris_fs.glsl @@ -32,10 +32,11 @@ in float fade; #include "fragment.glsl" Fragment getFragment() { - vec4 c = vec4(color, fade*forceFade); - + vec4 c = vec4(color * fade * forceFade, 1.0); Fragment frag; frag.color = c; frag.depth = vs_positionScreenSpace.w; + frag.blend = BLEND_MODE_ADDITIVE; + return frag; -} \ No newline at end of file +} diff --git a/modules/base/shaders/plane_fs.glsl b/modules/base/shaders/plane_fs.glsl index a8d20ee5b2..bfabe1ccb9 100644 --- a/modules/base/shaders/plane_fs.glsl +++ b/modules/base/shaders/plane_fs.glsl @@ -24,6 +24,7 @@ uniform float time; uniform sampler2D texture1; +uniform bool additiveBlending; in vec2 vs_st; in vec4 vs_positionScreenSpace; @@ -50,6 +51,10 @@ Fragment getFragment() { Fragment frag; frag.color = diffuse; frag.depth = vs_positionScreenSpace.w; + + if (additiveBlending) { + frag.blend = BLEND_MODE_ADDITIVE; + } return frag; } diff --git a/modules/debugging/rendering/renderabledebugplane.cpp b/modules/debugging/rendering/renderabledebugplane.cpp index da2378fee1..98077c7a7b 100644 --- a/modules/debugging/rendering/renderabledebugplane.cpp +++ b/modules/debugging/rendering/renderabledebugplane.cpp @@ -26,8 +26,6 @@ #include - -#include #include #include diff --git a/modules/globebrowsing/chunk/chunkedlodglobe.cpp b/modules/globebrowsing/chunk/chunkedlodglobe.cpp index fd32fa4e77..83378d7d6b 100644 --- a/modules/globebrowsing/chunk/chunkedlodglobe.cpp +++ b/modules/globebrowsing/chunk/chunkedlodglobe.cpp @@ -214,7 +214,7 @@ namespace openspace { const vec4& clippingSpaceCorner = mvp * modelSpaceCorners[i]; clippingSpaceCorners[i] = clippingSpaceCorner; - vec3 screenSpaceCorner = (1.0f / clippingSpaceCorner.w) * clippingSpaceCorner.xyz(); + vec3 screenSpaceCorner = (1.0f / clippingSpaceCorner.w) * clippingSpaceCorner; screenSpaceBounds.expand(screenSpaceCorner); } diff --git a/modules/globebrowsing/chunk/chunkrenderer.cpp b/modules/globebrowsing/chunk/chunkrenderer.cpp index 3ebca99530..7d7cc560fe 100644 --- a/modules/globebrowsing/chunk/chunkrenderer.cpp +++ b/modules/globebrowsing/chunk/chunkrenderer.cpp @@ -372,7 +372,7 @@ namespace openspace { glm::vec3 directionToSunWorldSpace = glm::normalize(-data.modelTransform.translation); glm::vec3 directionToSunCameraSpace = - (viewTransform * glm::dvec4(directionToSunWorldSpace, 0)).xyz(); + (viewTransform * glm::dvec4(directionToSunWorldSpace, 0)); data.modelTransform.translation; programObject->setUniform("modelViewTransform", modelViewTransform); programObject->setUniform("lightDirectionCameraSpace", -directionToSunCameraSpace); @@ -451,7 +451,7 @@ namespace openspace { glm::vec3 directionToSunWorldSpace = glm::normalize(-data.modelTransform.translation); glm::vec3 directionToSunCameraSpace = - (viewTransform * glm::dvec4(directionToSunWorldSpace, 0)).xyz(); + (viewTransform * glm::dvec4(directionToSunWorldSpace, 0)); data.modelTransform.translation; programObject->setUniform("lightDirectionCameraSpace", -directionToSunCameraSpace); } diff --git a/modules/globebrowsing/chunk/culling.cpp b/modules/globebrowsing/chunk/culling.cpp index 88a3d802dd..c281f6d7cb 100644 --- a/modules/globebrowsing/chunk/culling.cpp +++ b/modules/globebrowsing/chunk/culling.cpp @@ -72,7 +72,7 @@ namespace openspace { dvec4 cornerClippingSpace = modelViewProjectionTransform * corners[i]; clippingSpaceCorners[i] = cornerClippingSpace; - dvec3 cornerScreenSpace = (1.0f / glm::abs(cornerClippingSpace.w)) * cornerClippingSpace.xyz(); + dvec3 cornerScreenSpace = (1.0f / glm::abs(cornerClippingSpace.w)) * cornerClippingSpace; bounds.expand(cornerScreenSpace); } diff --git a/modules/globebrowsing/geometry/ellipsoid.cpp b/modules/globebrowsing/geometry/ellipsoid.cpp index 6bd4c5a056..b77bd1c9aa 100644 --- a/modules/globebrowsing/geometry/ellipsoid.cpp +++ b/modules/globebrowsing/geometry/ellipsoid.cpp @@ -145,7 +145,7 @@ namespace openspace { } Scalar Ellipsoid::longitudalDistance(Scalar lat, Scalar lon1, Scalar lon2) const { - Vec2 ellipseRadii = glm::cos(lat) * _radii.xy(); + Vec2 ellipseRadii = glm::cos(lat) * Vec2(_radii); // Approximating with the ellipse mean radius Scalar meanRadius = 0.5 * (ellipseRadii.x + ellipseRadii.y); return meanRadius * std::abs(lon2 - lon1); diff --git a/modules/globebrowsing/globes/renderableglobe.cpp b/modules/globebrowsing/globes/renderableglobe.cpp index 11d268b641..52b5f012b0 100644 --- a/modules/globebrowsing/globes/renderableglobe.cpp +++ b/modules/globebrowsing/globes/renderableglobe.cpp @@ -239,14 +239,14 @@ namespace openspace { // Sample and do linear interpolation (could possibly be moved as a function in ghoul texture) glm::uvec3 dimensions = tile.texture->dimensions(); - glm::vec2 samplePos = transformedUv * glm::vec2(dimensions.xy()); + glm::vec2 samplePos = transformedUv * glm::vec2(dimensions); glm::uvec2 samplePos00 = samplePos; - samplePos00 = glm::clamp(samplePos00, glm::uvec2(0, 0), dimensions.xy() - glm::uvec2(1)); + samplePos00 = glm::clamp(samplePos00, glm::uvec2(0, 0), glm::uvec2(dimensions) - glm::uvec2(1)); glm::vec2 samplePosFract = samplePos - glm::vec2(samplePos00); - glm::uvec2 samplePos10 = glm::min(samplePos00 + glm::uvec2(1, 0), dimensions.xy() - glm::uvec2(1)); - glm::uvec2 samplePos01 = glm::min(samplePos00 + glm::uvec2(0, 1), dimensions.xy() - glm::uvec2(1)); - glm::uvec2 samplePos11 = glm::min(samplePos00 + glm::uvec2(1, 1), dimensions.xy() - glm::uvec2(1)); + glm::uvec2 samplePos10 = glm::min(samplePos00 + glm::uvec2(1, 0), glm::uvec2(dimensions) - glm::uvec2(1)); + glm::uvec2 samplePos01 = glm::min(samplePos00 + glm::uvec2(0, 1), glm::uvec2(dimensions) - glm::uvec2(1)); + glm::uvec2 samplePos11 = glm::min(samplePos00 + glm::uvec2(1, 1), glm::uvec2(dimensions) - glm::uvec2(1)); float sample00 = tile.texture->texelAsFloat(samplePos00).x; float sample10 = tile.texture->texelAsFloat(samplePos10).x; diff --git a/modules/globebrowsing/meshes/trianglesoup.cpp b/modules/globebrowsing/meshes/trianglesoup.cpp index 83aa715a83..f50e88601b 100644 --- a/modules/globebrowsing/meshes/trianglesoup.cpp +++ b/modules/globebrowsing/meshes/trianglesoup.cpp @@ -36,9 +36,9 @@ TriangleSoup::TriangleSoup(std::vector elements, : _vaoID(0) ,_vertexBufferID(0) ,_elementBufferID(0) - ,_useVertexPositions(usePositions == Positions::Yes) - ,_useTextureCoordinates(useTextures == TextureCoordinates::Yes) - ,_useVertexNormals(useNormals == Normals::Yes) + ,_useVertexPositions(usePositions) + ,_useTextureCoordinates(useTextures) + ,_useVertexNormals(useNormals) { setElements(elements); } diff --git a/modules/globebrowsing/meshes/trianglesoup.h b/modules/globebrowsing/meshes/trianglesoup.h index c8aeeb7bda..c80988c29a 100644 --- a/modules/globebrowsing/meshes/trianglesoup.h +++ b/modules/globebrowsing/meshes/trianglesoup.h @@ -25,6 +25,7 @@ #ifndef __TRIANGLESOUP_H__ #define __TRIANGLESOUP_H__ +#include #include #include @@ -46,9 +47,9 @@ namespace openspace { class TriangleSoup { public: - enum class Positions { Yes, No }; - enum class TextureCoordinates { Yes, No }; - enum class Normals { Yes, No }; + using Positions = ghoul::Boolean; + using TextureCoordinates = ghoul::Boolean; + using Normals = ghoul::Boolean; TriangleSoup( std::vector elements, // At least elements are required diff --git a/modules/globebrowsing/other/statscollector.h b/modules/globebrowsing/other/statscollector.h index e5b07d8920..3bb73728fa 100644 --- a/modules/globebrowsing/other/statscollector.h +++ b/modules/globebrowsing/other/statscollector.h @@ -24,6 +24,7 @@ #ifndef __STATS_TRACKER_H__ #define __STATS_TRACKER_H__ +#include #include #include @@ -143,13 +144,13 @@ namespace openspace { StatsCollector() = delete; - enum class Enabled { Yes, No }; + using Enabled = ghoul::Boolean; StatsCollector(const std::string& filename, int dumpEveryXRecord, Enabled enabled = Enabled::Yes, const std::string& delimiter = ",") : _filename(filename) , _dumpEveryXRecord(dumpEveryXRecord) , _recordsSinceLastDump(0) - , _enabled(enabled == Enabled::Yes) + , _enabled(enabled) , _delimiter(delimiter) , _hasWrittenHeader(false) , i(TemplatedStatsCollector(_enabled, delimiter)) diff --git a/modules/globebrowsing/tile/tiledataset.cpp b/modules/globebrowsing/tile/tiledataset.cpp index 66b8d897ff..4c2d0bc781 100644 --- a/modules/globebrowsing/tile/tiledataset.cpp +++ b/modules/globebrowsing/tile/tiledataset.cpp @@ -473,7 +473,7 @@ namespace openspace { io.write.region.roundDownToQuadratic(); io.write.region.roundUpNumPixelToNearestMultipleOf(2); if (preRound != io.write.region.numPixels) { - //LDEBUG(chunkIndex << " | " << preRound.x << ", " << preRound.y << " --> " << io.write.region.numPixels.x << ", " << io.write.region.numPixels.y); + LDEBUG(chunkIndex << " | " << preRound.x << ", " << preRound.y << " --> " << io.write.region.numPixels.x << ", " << io.write.region.numPixels.y); } @@ -611,10 +611,10 @@ namespace openspace { } } - if (depth == 0) { + //if (depth == 0) { //LDEBUG(indentation << "main rasterIO read: " << io.read.region); //LDEBUG(indentation << "main rasterIO write: " << io.write.region); - } + //} else if (worstError > CPLErr::CE_None) { LDEBUG(indentation << "Error reading padding: " << worstError); diff --git a/modules/newhorizons/newhorizonsmodule.cpp b/modules/newhorizons/newhorizonsmodule.cpp index 479afd7e5b..72caa9b844 100644 --- a/modules/newhorizons/newhorizonsmodule.cpp +++ b/modules/newhorizons/newhorizonsmodule.cpp @@ -51,7 +51,10 @@ NewHorizonsModule::NewHorizonsModule() void NewHorizonsModule::internalInitialize() { ImageSequencer::initialize(); - FactoryManager::ref().addFactory(std::make_unique>()); + FactoryManager::ref().addFactory( + std::make_unique>(), + "Decoder" + ); auto fRenderable = FactoryManager::ref().factory(); ghoul_assert(fRenderable, "No renderable factory existed"); diff --git a/modules/newhorizons/rendering/renderablecrawlingline.cpp b/modules/newhorizons/rendering/renderablecrawlingline.cpp index 34f07bd365..4c538c180a 100644 --- a/modules/newhorizons/rendering/renderablecrawlingline.cpp +++ b/modules/newhorizons/rendering/renderablecrawlingline.cpp @@ -24,12 +24,10 @@ #include -#include #include #include #include #include -//#include namespace { const std::string _loggerCat = "RenderableCrawlingLine"; diff --git a/modules/newhorizons/rendering/renderablefov.cpp b/modules/newhorizons/rendering/renderablefov.cpp index a31acd90b7..878d833186 100644 --- a/modules/newhorizons/rendering/renderablefov.cpp +++ b/modules/newhorizons/rendering/renderablefov.cpp @@ -528,7 +528,7 @@ void RenderableFov::computeIntercepts(const RenderData& data) { _interceptTag[_bounds.size()] = _interceptTag[0]; fovSurfaceIntercept(_interceptTag, _bounds); - glm::vec3 aim = (_spacecraftRotation * glm::vec4(_boresight, 1)).xyz(); + glm::vec3 aim = (_spacecraftRotation * glm::vec4(_boresight, 1)); double lt; glm::dvec3 position = SpiceManager::ref().targetPosition( diff --git a/modules/newhorizons/rendering/renderablemodelprojection.cpp b/modules/newhorizons/rendering/renderablemodelprojection.cpp index 1df9adec24..3daadde566 100644 --- a/modules/newhorizons/rendering/renderablemodelprojection.cpp +++ b/modules/newhorizons/rendering/renderablemodelprojection.cpp @@ -40,6 +40,7 @@ namespace { const std::string keyDestination = "Rotation.Destination"; const std::string keyBody = "Body"; const std::string keyGeometry = "Geometry"; + const std::string keyBoundingSphereRadius = "BoundingSphereRadius"; const std::string keyTextureColor = "Textures.Color"; const std::string keyTextureProject = "Textures.Project"; @@ -96,10 +97,10 @@ RenderableModelProjection::RenderableModelProjection(const ghoul::Dictionary& di completeSuccess &= _projectionComponent.initializeProjectionSettings(dictionary); openspace::SpiceManager::ref().addFrame(_target, _source); - - double boundingRadius = _geometry->boundingRadius(); - setBoundingSphere(PowerScaledScalar::CreatePSS(boundingRadius)); + float boundingSphereRadius = 1.0e9; + dictionary.getValue(keyBoundingSphereRadius, boundingSphereRadius); + setBoundingSphere(PowerScaledScalar::CreatePSS(boundingSphereRadius)); Renderable::addProperty(_performShading); Renderable::addProperty(_rotation); @@ -132,9 +133,18 @@ bool RenderableModelProjection::initialize() { ghoul::opengl::ProgramObject::IgnoreError::Yes ); + _depthFboProgramObject = ghoul::opengl::ProgramObject::Build("DepthPass", + "${MODULE_NEWHORIZONS}/shaders/renderableModelDepth_vs.glsl", + "${MODULE_NEWHORIZONS}/shaders/renderableModelDepth_fs.glsl"); + + completeSuccess &= loadTextures(); completeSuccess &= _projectionComponent.initialize(); + + auto bs = getBoundingSphere(); completeSuccess &= _geometry->initialize(this); + setBoundingSphere(bs); // ignore bounding sphere set by geometry. + completeSuccess &= !_source.empty(); completeSuccess &= !_destination.empty(); @@ -164,7 +174,6 @@ void RenderableModelProjection::render(const RenderData& data) { if (_projectionComponent.needsClearProjection()) _projectionComponent.clearAllProjections(); - _camScaling = data.camera.scaling(); _up = data.camera.lookUpVectorCameraSpace(); if (_capture && _projectionComponent.doesPerformProjection()) @@ -221,6 +230,9 @@ void RenderableModelProjection::update(const UpdateData& data) { _projectionComponent.update(); + if (_depthFboProgramObject->isDirty()) + _depthFboProgramObject->rebuildFromFile(); + _time = data.time; if (openspace::ImageSequencer::ref().isReady()) { @@ -252,8 +264,20 @@ void RenderableModelProjection::update(const UpdateData& data) { void RenderableModelProjection::imageProjectGPU( std::shared_ptr projectionTexture) { - _projectionComponent.imageProjectBegin(); + if (_projectionComponent.needsShadowMap()) { + _projectionComponent.depthMapRenderBegin(); + _depthFboProgramObject->activate(); + _depthFboProgramObject->setUniform("ProjectorMatrix", _projectorMatrix); + _depthFboProgramObject->setUniform("ModelTransform", _transform); + _geometry->setUniforms(*_fboProgramObject); + _geometry->render(); + + _depthFboProgramObject->deactivate(); + _projectionComponent.depthMapRenderEnd(); + } + + _projectionComponent.imageProjectBegin(); _fboProgramObject->activate(); ghoul::opengl::TextureUnit unitFbo; @@ -261,9 +285,17 @@ void RenderableModelProjection::imageProjectGPU( projectionTexture->bind(); _fboProgramObject->setUniform("projectionTexture", unitFbo); + _fboProgramObject->setUniform("needShadowMap", _projectionComponent.needsShadowMap()); + + ghoul::opengl::TextureUnit unitDepthFbo; + if (_projectionComponent.needsShadowMap()) { + unitDepthFbo.activate(); + _projectionComponent.depthTexture().bind(); + _fboProgramObject->setUniform("depthTexture", unitDepthFbo); + } + _fboProgramObject->setUniform("ProjectorMatrix", _projectorMatrix); _fboProgramObject->setUniform("ModelTransform", _transform); - _fboProgramObject->setUniform("_scaling", _camScaling); _fboProgramObject->setUniform("boresight", _boresight); _geometry->setUniforms(*_fboProgramObject); @@ -325,19 +357,24 @@ void RenderableModelProjection::attitudeParameters(double time) { time, lightTime); psc position = PowerScaledCoordinate::CreatePowerScaledCoordinate(p.x, p.y, p.z); - position[3] += (3 + _camScaling[1]) + 1; + position[3] += 4; glm::vec3 cpos = position.vec3(); + float distance = glm::length(cpos); + float radius = getBoundingSphere().lengthf(); + _projectorMatrix = _projectionComponent.computeProjectorMatrix( cpos, boresight, _up, _instrumentMatrix, _projectionComponent.fieldOfViewY(), _projectionComponent.aspectRatio(), - _projectionComponent.nearPlane(), - _projectionComponent.farPlane(), + distance - radius, + distance + radius, _boresight ); } + + void RenderableModelProjection::project() { for (auto img : _imageTimes) { attitudeParameters(img.timeRange.start); diff --git a/modules/newhorizons/rendering/renderablemodelprojection.h b/modules/newhorizons/rendering/renderablemodelprojection.h index 9f9d0227ab..1a0ef2c259 100644 --- a/modules/newhorizons/rendering/renderablemodelprojection.h +++ b/modules/newhorizons/rendering/renderablemodelprojection.h @@ -74,6 +74,7 @@ private: std::unique_ptr _programObject; std::unique_ptr _fboProgramObject; + std::unique_ptr _depthFboProgramObject; std::unique_ptr _baseTexture; @@ -100,7 +101,6 @@ private: bool _capture; psc _sunPosition; - properties::BoolProperty _performShading; }; diff --git a/modules/newhorizons/rendering/renderableplanetprojection.cpp b/modules/newhorizons/rendering/renderableplanetprojection.cpp index e883877546..3f892aecb0 100644 --- a/modules/newhorizons/rendering/renderableplanetprojection.cpp +++ b/modules/newhorizons/rendering/renderableplanetprojection.cpp @@ -47,6 +47,7 @@ namespace { const std::string keyFrame = "Frame"; const std::string keyGeometry = "Geometry"; + const std::string keyRadius = "Geometry.Radius"; const std::string keyShading = "PerformShading"; const std::string keyBody = "Body"; const std::string _mainFrame = "GALACTIC"; @@ -101,6 +102,10 @@ RenderablePlanetProjection::RenderablePlanetProjection(const ghoul::Dictionary& if (success) _heightMapTexturePath = absPath(heightMapPath); + glm::vec2 radius = glm::vec2(1.0, 9.0); + dictionary.getValue(keyRadius, radius); + setBoundingSphere(pss(radius)); + addPropertySubOwner(_geometry.get()); addPropertySubOwner(_projectionComponent); @@ -134,7 +139,6 @@ bool RenderablePlanetProjection::initialize() { completeSuccess &= loadTextures(); completeSuccess &= _projectionComponent.initialize(); - completeSuccess &= _geometry->initialize(this); if (completeSuccess) { @@ -285,6 +289,9 @@ void RenderablePlanetProjection::attitudeParameters(double time) { //position[3] += 3; glm::vec3 cpos = position.vec3(); + float distance = glm::length(cpos); + float radius = getBoundingSphere().lengthf(); + _projectorMatrix = _projectionComponent.computeProjectorMatrix( cpos, bs, @@ -292,8 +299,8 @@ void RenderablePlanetProjection::attitudeParameters(double time) { _instrumentMatrix, _projectionComponent.fieldOfViewY(), _projectionComponent.aspectRatio(), - _projectionComponent.nearPlane(), - _projectionComponent.farPlane(), + distance - radius, + distance + radius, _boresight ); } diff --git a/modules/newhorizons/shaders/renderableModelDepth_fs.glsl b/modules/newhorizons/shaders/renderableModelDepth_fs.glsl new file mode 100644 index 0000000000..2b7bcfdaec --- /dev/null +++ b/modules/newhorizons/shaders/renderableModelDepth_fs.glsl @@ -0,0 +1,30 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#version __CONTEXT__ + +void main() { + gl_FragColor = vec4(1.0); + //gl_FragDepth = gl_FragCoord.z; +} diff --git a/modules/newhorizons/shaders/renderableModelDepth_vs.glsl b/modules/newhorizons/shaders/renderableModelDepth_vs.glsl new file mode 100644 index 0000000000..574a349ad4 --- /dev/null +++ b/modules/newhorizons/shaders/renderableModelDepth_vs.glsl @@ -0,0 +1,36 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#version __CONTEXT__ + +#include "PowerScaling/powerScaling_vs.hglsl" + +layout(location = 0) in vec4 in_position; + +uniform mat4 ProjectorMatrix; +uniform mat4 ModelTransform; + +void main() { + gl_Position = ProjectorMatrix * ModelTransform * psc_to_meter(in_position, vec2(1.0, 0.0)); +} diff --git a/modules/newhorizons/shaders/renderableModelProjection_fs.glsl b/modules/newhorizons/shaders/renderableModelProjection_fs.glsl index 881e20e4b7..7c69bc65bf 100644 --- a/modules/newhorizons/shaders/renderableModelProjection_fs.glsl +++ b/modules/newhorizons/shaders/renderableModelProjection_fs.glsl @@ -25,9 +25,8 @@ #version __CONTEXT__ in vec4 vs_position; +in vec4 vs_ndc; in vec4 vs_normal; -in vec2 vs_uv; -in vec4 ProjTexCoord; layout (location = 0) out vec4 color; // Even though the stencel texture is only a single channel, we still need to @@ -35,34 +34,53 @@ layout (location = 0) out vec4 color; layout (location = 1) out vec4 stencil; uniform sampler2D projectionTexture; +uniform sampler2D depthTexture; + +uniform bool needShadowMap; uniform mat4 ModelTransform; -uniform vec2 _scaling; uniform vec3 boresight; +uniform vec4 debugColor; bool inRange(float x, float a, float b) { return (x >= a && x <= b); } void main() { - vec2 uv = vec2(0.5,0.5)*vs_uv+vec2(0.5,0.5); - vec3 n = normalize(vs_normal.xyz); - vec4 projected = ProjTexCoord; + vec4 projected = vs_ndc; + vec2 uv = vec2(0.5) * projected.xy + vec2(0.5); - // normalize - projected.x /= projected.w; - projected.y /= projected.w; - // invert gl coordinates - projected.x = 1 - projected.x; + if (needShadowMap) { + float thisDepth = projected.z * 0.5 + 0.5; + float closestDepth = texture(depthTexture, uv).r; + float epsilon = 0.001; - if ((inRange(projected.x, 0, 1) && inRange(projected.y, 0, 1)) && (dot(n, boresight) < 0)) { - color = texture(projectionTexture, projected.xy); - //color.a = 1.0; - stencil = vec4(1.0); + if (inRange(uv.x, 0.0, 1.0) && inRange(uv.y, 0.0, 1.0) && + dot(n, boresight) < 0 && thisDepth <= closestDepth + epsilon) + { + // color = texture(projectionTexture, projected.xy); + color = texture(projectionTexture, vec2(1.0) - uv); + color.a = 1.0; + stencil = vec4(1.0); + } + else { + color = vec4(vec3(0.0), 0.0); + stencil = vec4(0.0); + } } else { - color = vec4(0.0);//vec4(vec3(0.0), 1.0); - stencil = vec4(0.0); + if (inRange(uv.x, 0.0, 1.0) && inRange(uv.y, 0.0, 1.0) && + dot(n, boresight) < 0) + { + // color = texture(projectionTexture, projected.xy); + color = texture(projectionTexture, vec2(1.0) - uv); + color.a = 1.0; + stencil = vec4(1.0); + } + else { + color = vec4(vec3(0.0), 0.0); + stencil = vec4(0.0); + } } } diff --git a/modules/newhorizons/shaders/renderableModelProjection_vs.glsl b/modules/newhorizons/shaders/renderableModelProjection_vs.glsl index 20c64bd812..2be32b0119 100644 --- a/modules/newhorizons/shaders/renderableModelProjection_vs.glsl +++ b/modules/newhorizons/shaders/renderableModelProjection_vs.glsl @@ -33,26 +33,20 @@ layout(location = 2) in vec3 in_normal; out vec4 vs_position; out vec4 vs_normal; out vec2 vs_uv; -out vec4 ProjTexCoord; +out vec4 vs_ndc; uniform mat4 ProjectorMatrix; uniform mat4 ModelTransform; -uniform vec2 _scaling; uniform vec3 boresight; void main() { - vs_position = in_position; - - vec4 tmp = in_position; - vec4 position = pscTransform(tmp, ModelTransform); - vs_position = position; - - vec4 raw_pos = psc_to_meter(in_position, _scaling); - ProjTexCoord = ProjectorMatrix * ModelTransform * raw_pos; + vec4 raw_pos = psc_to_meter(in_position, vec2(1.0, 0.0)); + vs_position = ProjectorMatrix * ModelTransform * raw_pos; vs_normal = normalize(ModelTransform * vec4(in_normal,0)); - + vs_ndc = vs_position / vs_position.w; + //match clipping plane vec2 texco = (in_st * 2) - 1; vs_uv = texco; diff --git a/modules/newhorizons/shaders/renderableModel_fs.glsl b/modules/newhorizons/shaders/renderableModel_fs.glsl index 5f3b5f285a..efe16cf13c 100644 --- a/modules/newhorizons/shaders/renderableModel_fs.glsl +++ b/modules/newhorizons/shaders/renderableModel_fs.glsl @@ -84,12 +84,12 @@ Fragment getFragment() { color = diffuseAlbedo; } - float transparency = 1.0; - float alpha = _projectionFading * transparency; + // float transparency = 1.0; + // float alpha = _projectionFading * transparency; Fragment frag; - frag.color = vec4(color, alpha); + frag.color = vec4(color, 1.0); frag.depth = vs_positionScreenSpace.w; return frag; } diff --git a/modules/newhorizons/shaders/renderablePlanetProjection_fs.glsl b/modules/newhorizons/shaders/renderablePlanetProjection_fs.glsl index 7213c6c22f..ff8698c866 100644 --- a/modules/newhorizons/shaders/renderablePlanetProjection_fs.glsl +++ b/modules/newhorizons/shaders/renderablePlanetProjection_fs.glsl @@ -73,6 +73,8 @@ void main() { projected.x /= projected.w; projected.y /= projected.w; + projected = projected * 0.5 + vec4(0.5); + vec3 normal = normalize((ModelTransform*vec4(vertex.xyz,0)).xyz); vec3 v_b = normalize(boresight); @@ -83,7 +85,7 @@ void main() { { // The 1-x is in this texture call because of flipped textures // to be fixed soon ---abock - color = texture(projectionTexture, vec2(projected.x, 1-projected.y)); + color = texture(projectionTexture, vec2(projected.x, projected.y)); stencil = vec4(1.0); } else { diff --git a/modules/newhorizons/util/decoder.cpp b/modules/newhorizons/util/decoder.cpp index 865815035a..74e1c9b87a 100644 --- a/modules/newhorizons/util/decoder.cpp +++ b/modules/newhorizons/util/decoder.cpp @@ -23,8 +23,11 @@ ****************************************************************************************/ #include + #include + #include +#include namespace { const std::string _loggerCat = "Decoder"; @@ -32,29 +35,22 @@ const std::string _loggerCat = "Decoder"; namespace openspace { -Decoder* Decoder::createFromDictionary(const ghoul::Dictionary& dictionary, const std::string& type) +std::unique_ptr Decoder::createFromDictionary( + const ghoul::Dictionary& dictionary, const std::string& type) { ghoul::TemplateFactory* factory = FactoryManager::ref().factory(); Decoder* result = factory->create(type, dictionary); if (result == nullptr) { - LERROR("Failed creating Payload object of type '" << type << "'"); - return nullptr; + throw ghoul::RuntimeError( + "Failed creating payload object of type '" + type + '"', + "Decoder" + ); } - return result; + return std::unique_ptr(result); } -Decoder::Decoder() -{ -} - -Decoder::Decoder(const ghoul::Dictionary& dictionary) -{ -} - -Decoder::~Decoder() -{ -} +Decoder::~Decoder() {} } // namespace openspace \ No newline at end of file diff --git a/modules/newhorizons/util/decoder.h b/modules/newhorizons/util/decoder.h index c4e2c9eb56..700cf06452 100644 --- a/modules/newhorizons/util/decoder.h +++ b/modules/newhorizons/util/decoder.h @@ -26,20 +26,23 @@ #define __DECODER_H__ #include -#include + +#include namespace openspace { class Decoder { public: - static Decoder* createFromDictionary(const ghoul::Dictionary& dictionary, const std::string& type); + static std::unique_ptr createFromDictionary( + const ghoul::Dictionary& dictionary, const std::string& type); - Decoder(const ghoul::Dictionary& dictionary); virtual ~Decoder(); + virtual std::string getDecoderType() = 0; virtual std::vector getTranslation() = 0; + protected: - Decoder(); + Decoder() = default; }; } // namespace openspace diff --git a/modules/newhorizons/util/hongkangparser.cpp b/modules/newhorizons/util/hongkangparser.cpp index 57034403d8..bd8a20b7ac 100644 --- a/modules/newhorizons/util/hongkangparser.cpp +++ b/modules/newhorizons/util/hongkangparser.cpp @@ -75,10 +75,10 @@ HongKangParser::HongKangParser(std::string name, std::string fileName, ghoul::Dictionary decoderDictionary; translationDictionary.getValue(currentKey, decoderDictionary); - Decoder* decoder = Decoder::createFromDictionary(decoderDictionary, decoders[i]); + auto decoder = Decoder::createFromDictionary(decoderDictionary, decoders[i]); //insert decoder to map - this will be used in the parser to determine //behavioral characteristics of each instrument - _fileTranslation[keys[j]] = decoder; + _fileTranslation[keys[j]] = std::move(decoder); } } //Hong's playbook needs _only_ instrument translation though. @@ -201,7 +201,7 @@ bool HongKangParser::create() { if (it->second->getDecoderType() == "SCANNER"){ // SCANNER START scan_start = time; - InstrumentDecoder* scanner = static_cast(it->second); + InstrumentDecoder* scanner = static_cast(it->second.get()); std::string endNominal = scanner->getStopCommand(); // store current position in file diff --git a/modules/newhorizons/util/hongkangparser.h b/modules/newhorizons/util/hongkangparser.h index ef1db0023d..a1959c923e 100644 --- a/modules/newhorizons/util/hongkangparser.h +++ b/modules/newhorizons/util/hongkangparser.h @@ -46,7 +46,6 @@ public: bool create() override; void findPlaybookSpecifiedTarget(std::string line, std::string& target); - virtual std::map getTranslation(){ return _fileTranslation; }; private: double getMetFromET(double et); @@ -70,7 +69,7 @@ private: std::string _name; std::string _fileName; std::string _spacecraft; - std::map _fileTranslation; + std::map> _fileTranslation; std::vector _potentialTargets; }; diff --git a/modules/newhorizons/util/imagesequencer.cpp b/modules/newhorizons/util/imagesequencer.cpp index 1a7eec7fdf..e5e1f5233e 100644 --- a/modules/newhorizons/util/imagesequencer.cpp +++ b/modules/newhorizons/util/imagesequencer.cpp @@ -338,9 +338,9 @@ void ImageSequencer::runSequenceParser(SequenceParser* parser){ bool parserComplete = parser->create(); if (parserComplete){ // get new data - std::map translations = parser->getTranslation(); // in1 + std::map>& translations = parser->getTranslation(); // in1 std::map imageData = parser->getSubsetMap(); // in2 - std::vector> instrumentTimes = parser->getIstrumentTimes(); //in3 + std::vector> instrumentTimes = parser->getInstrumentTimes(); //in3 std::vector> targetTimes = parser->getTargetTimes(); //in4 std::vector captureProgression = parser->getCaptureProgression(); //in5 @@ -349,11 +349,14 @@ void ImageSequencer::runSequenceParser(SequenceParser* parser){ LERROR("Missing sequence data"); return; } - + // append data - _fileTranslation.insert(translations.begin(), translations.end()); - for (auto it : imageData){ + for (auto& it : translations) { + _fileTranslation[it.first] = std::move(it.second); + } + + for (auto& it : imageData){ if (_subsetMap.find(it.first) == _subsetMap.end()) { // if key not exist yet - add sequence data for key (target) _subsetMap.insert(it); @@ -404,7 +407,7 @@ void ImageSequencer::runSequenceParser(SequenceParser* parser){ sortData(); // extract payload from _fileTranslation - for (auto t : _fileTranslation){ + for (auto& t : _fileTranslation){ if (t.second->getDecoderType() == "CAMERA" || t.second->getDecoderType() == "SCANNER"){ std::vector spiceIDs = t.second->getTranslation(); diff --git a/modules/newhorizons/util/imagesequencer.h b/modules/newhorizons/util/imagesequencer.h index 07f981cc9c..4729dab641 100644 --- a/modules/newhorizons/util/imagesequencer.h +++ b/modules/newhorizons/util/imagesequencer.h @@ -152,7 +152,7 @@ private: * \see Decoder * \see (projection mod files) */ - std::map _fileTranslation; + std::map> _fileTranslation; /* * This is the main container of image data. The key is the target name, diff --git a/modules/newhorizons/util/instrumenttimesparser.cpp b/modules/newhorizons/util/instrumenttimesparser.cpp index 4b0b679a12..92765eb190 100644 --- a/modules/newhorizons/util/instrumenttimesparser.cpp +++ b/modules/newhorizons/util/instrumenttimesparser.cpp @@ -65,7 +65,7 @@ InstrumentTimesParser::InstrumentTimesParser( for (const auto& instrumentKey : instruments.keys()) { ghoul::Dictionary instrument = instruments.value(instrumentKey); ghoul::Dictionary files = instrument.value(KeyInstrumentFiles); - _fileTranslation[instrumentKey] = Decoder::createFromDictionary(instrument, KeyInstrument); + _fileTranslation[instrumentKey] = std::move(Decoder::createFromDictionary(instrument, KeyInstrument)); for (int i = 0; i < files.size(); i++) { std::string filename = files.value(std::to_string(i + 1)); _instrumentFiles[instrumentKey].push_back(filename); diff --git a/modules/newhorizons/util/instrumenttimesparser.h b/modules/newhorizons/util/instrumenttimesparser.h index 4674cc73c3..49fc2c3a4d 100644 --- a/modules/newhorizons/util/instrumenttimesparser.h +++ b/modules/newhorizons/util/instrumenttimesparser.h @@ -45,13 +45,7 @@ public: bool create() override; - //virtual std::map getTranslation() override; - - // temporary need to figure this out - std::map getTranslation(){ return _fileTranslation; }; - private: - std::regex _pattern; std::map> _instrumentFiles; @@ -59,7 +53,7 @@ private: std::string _name; std::string _fileName; std::string _spacecraft; - std::map _fileTranslation; + std::map> _fileTranslation; std::vector _specsOfInterest; std::string _target; diff --git a/modules/newhorizons/util/labelparser.cpp b/modules/newhorizons/util/labelparser.cpp index 3ce4c5271b..bccdb49ff6 100644 --- a/modules/newhorizons/util/labelparser.cpp +++ b/modules/newhorizons/util/labelparser.cpp @@ -22,18 +22,23 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include -#include -#include -#include -#include +#include + #include + +#include +#include + +#include +#include +#include +#include + #include #include #include #include -#include namespace { const std::string _loggerCat = "LabelParser"; @@ -54,24 +59,24 @@ LabelParser::LabelParser(std::string name, std::string fileName, //get the different instrument types const std::vector& decoders = translationDictionary.keys(); //for each decoder (assuming might have more if hong makes changes) - for (int i = 0; i < decoders.size(); i++){ + for (int i = 0; i < decoders.size(); ++i) { ghoul::Dictionary typeDictionary; translationDictionary.getValue(decoders[i], typeDictionary); //create dictionary containing all {playbookKeys , spice IDs} - if (decoders[i] == "Instrument"){ + if (decoders[i] == "Instrument") { //for each playbook call -> create a Decoder object - const std::vector& keys = typeDictionary.keys(); - for (int j = 0; j < keys.size(); j++){ + std::vector keys = typeDictionary.keys(); + for (int j = 0; j < keys.size(); ++j){ std::string currentKey = decoders[i] + "." + keys[j]; - ghoul::Dictionary decoderDictionary; - translationDictionary.getValue(currentKey, decoderDictionary); + ghoul::Dictionary decoderDictionary = + translationDictionary.value(currentKey); - Decoder *decoder = Decoder::createFromDictionary(decoderDictionary, decoders[i]); + auto decoder = Decoder::createFromDictionary(decoderDictionary, decoders[i]); //insert decoder to map - this will be used in the parser to determine //behavioral characteristics of each instrument - _fileTranslation[keys[j]] = decoder; + _fileTranslation[keys[j]] = std::move(decoder); } } if (decoders[i] == "Target"){ @@ -91,17 +96,17 @@ LabelParser::LabelParser(std::string name, std::string fileName, for (int j = 0; j < keys.size(); j++){ ghoul::Dictionary itemDictionary; convertDictionary.getValue(keys[j], itemDictionary); - Decoder *decoder = Decoder::createFromDictionary(itemDictionary, decoders[i]); + auto decoder = Decoder::createFromDictionary(itemDictionary, decoders[i]); //insert decoder to map - this will be used in the parser to determine //behavioral characteristics of each instrument - _fileTranslation[keys[j]] = decoder; + _fileTranslation[keys[j]] = std::move(decoder); }; } } } std::string LabelParser::decode(std::string line){ - for (auto key : _fileTranslation){ + for (auto& key : _fileTranslation){ std::size_t value = line.find(key.first); if (value != std::string::npos){ std::string toTranslate = line.substr(value); @@ -121,7 +126,7 @@ std::string LabelParser::decode(std::string line){ } std::string LabelParser::encode(std::string line) { - for (auto key : _fileTranslation) { + for (auto& key : _fileTranslation) { std::size_t value = line.find(key.first); if (value != std::string::npos) { return line.substr(value); @@ -232,29 +237,31 @@ bool LabelParser::create() { LINFO("Please make sure input data adheres to format https://pds.jpl.nasa.gov/documents/qs/labels.html"); } } - if (count == _specsOfInterest.size()){ - count = 0; - std::string ext = "jpg"; - path.replace(path.begin() + position, path.end(), ext); - bool fileExists = FileSys.fileExists(path); - if (!fileExists) { - ext = "JPG"; - path.replace(path.begin() + position, path.end(), ext); - fileExists = FileSys.fileExists(path); - } - if (fileExists) { - Image image; - std::vector spiceInstrument; - spiceInstrument.push_back(_instrumentID); - createImage(image, startTime, stopTime, spiceInstrument, _target, path); - - _subsetMap[image.target]._subset.push_back(image); - _subsetMap[image.target]._range.include(startTime); + if (count == _specsOfInterest.size()) { + using ghoul::io::TextureReader; + auto extensions = TextureReader::ref().supportedExtensions(); - _captureProgression.push_back(startTime); - std::stable_sort(_captureProgression.begin(), _captureProgression.end()); + count = 0; + + using namespace std::literals; + std::string p = path.substr(0, path.size() - ("lbl"s).size()); + for (const std::string& ext : extensions) { + path = p + ext; + if (FileSys.fileExists(path)) { + Image image; + std::vector spiceInstrument; + spiceInstrument.push_back(_instrumentID); + createImage(image, startTime, stopTime, spiceInstrument, _target, path); + + _subsetMap[image.target]._subset.push_back(image); + _subsetMap[image.target]._range.include(startTime); + + _captureProgression.push_back(startTime); + std::stable_sort(_captureProgression.begin(), _captureProgression.end()); + + break; + } } - } } while (!file.eof()); } diff --git a/modules/newhorizons/util/labelparser.h b/modules/newhorizons/util/labelparser.h index c0cf393ce6..a65eaf1543 100644 --- a/modules/newhorizons/util/labelparser.h +++ b/modules/newhorizons/util/labelparser.h @@ -43,7 +43,7 @@ public: bool create() override; // temporary need to figure this out - std::map getTranslation(){ return _fileTranslation; }; + //std::map getTranslation() { return _fileTranslation; }; private: void createImage(Image& image, @@ -64,7 +64,6 @@ private: std::string _name; std::string _fileName; std::string _spacecraft; - std::map _fileTranslation; std::vector _specsOfInterest; std::string _target; diff --git a/modules/newhorizons/util/projectioncomponent.cpp b/modules/newhorizons/util/projectioncomponent.cpp index 6b92aa5fe1..21a36cbd61 100644 --- a/modules/newhorizons/util/projectioncomponent.cpp +++ b/modules/newhorizons/util/projectioncomponent.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include @@ -42,8 +43,6 @@ namespace { const std::string keyInstrument = "Instrument.Name"; const std::string keyInstrumentFovy = "Instrument.Fovy"; const std::string keyInstrumentAspect = "Instrument.Aspect"; - const std::string keyInstrumentNear = "Instrument.Near"; - const std::string keyInstrumentFar = "Instrument.Far"; const std::string keyProjObserver = "Projection.Observer"; const std::string keyProjTarget = "Projection.Target"; @@ -54,6 +53,8 @@ namespace { const std::string keyTranslation = "DataInputTranslation"; const std::string keyNeedsTextureMapDilation = "Projection.TextureMap"; + const std::string keyNeedsShadowing = "Projection.ShadowMap"; + const std::string keyTextureMapAspectRatio = "Projection.AspectRatio"; const std::string sequenceTypeImage = "image-sequence"; const std::string sequenceTypePlaybook = "playbook"; @@ -69,25 +70,55 @@ namespace { namespace openspace { using ghoul::Dictionary; +using glm::ivec2; ProjectionComponent::ProjectionComponent() : properties::PropertyOwner() , _performProjection("performProjection", "Perform Projections", true) , _clearAllProjections("clearAllProjections", "Clear Projections", false) , _projectionFading("projectionFading", "Projection Fading", 1.f, 0.f, 1.f) + , _textureSize("textureSize", "Texture Size", ivec2(16), ivec2(16), ivec2(32768)) + , _applyTextureSize("applyTextureSize", "Apply Texture Size") + , _textureSizeDirty(false) , _projectionTexture(nullptr) - , _needsTextureMapDilation(false) { setName("ProjectionComponent"); + _shadowing.isEnabled = false; + _dilation.isEnabled = false; + addProperty(_performProjection); addProperty(_clearAllProjections); addProperty(_projectionFading); + + addProperty(_textureSize); + addProperty(_applyTextureSize); + _applyTextureSize.onChange([this]() { _textureSizeDirty = true; }); } bool ProjectionComponent::initialize() { - bool a = generateProjectionLayerTexture(); - bool b = auxiliaryRendertarget(); + int maxSize = OpenGLCap.max2DTextureSize(); + glm::ivec2 size; + + if (_projectionTextureAspectRatio > 1.f) { + size.x = maxSize; + size.y = static_cast(maxSize / _projectionTextureAspectRatio); + } + else { + size.x = static_cast(maxSize * _projectionTextureAspectRatio); + size.y = maxSize; + } + + _textureSize.setMaxValue(size); + _textureSize = size / 2; + + // We only want to use half the resolution per default: + size /= 2; + + bool success = generateProjectionLayerTexture(size); + success &= generateDepthTexture(size); + success &= auxiliaryRendertarget(); + success &= depthRendertarget(); using std::unique_ptr; using ghoul::opengl::Texture; @@ -103,8 +134,7 @@ bool ProjectionComponent::initialize() { } _placeholderTexture = std::move(texture); - - if (_needsTextureMapDilation) { + if (_dilation.isEnabled) { _dilation.program = ghoul::opengl::ProgramObject::Build( "Dilation", "${MODULE_NEWHORIZONS}/shaders/dilation_vs.glsl", @@ -139,7 +169,7 @@ bool ProjectionComponent::initialize() { glBindVertexArray(0); } - return a && b; + return success; } bool ProjectionComponent::deinitialize() { @@ -147,7 +177,7 @@ bool ProjectionComponent::deinitialize() { glDeleteFramebuffers(1, &_fboID); - if (_needsTextureMapDilation) { + if (_dilation.isEnabled) { glDeleteFramebuffers(1, &_dilation.fbo); glDeleteVertexArrays(1, &_dilation.vao); glDeleteBuffers(1, &_dilation.vbo); @@ -170,8 +200,7 @@ bool ProjectionComponent::initializeProjectionSettings(const Dictionary& diction completeSuccess &= dictionary.getValue(keyProjTarget, _projecteeID); completeSuccess &= dictionary.getValue(keyInstrumentFovy, _fovy); completeSuccess &= dictionary.getValue(keyInstrumentAspect, _aspectRatio); - completeSuccess &= dictionary.getValue(keyInstrumentNear, _nearPlane); - completeSuccess &= dictionary.getValue(keyInstrumentFar, _farPlane); + ghoul_assert(completeSuccess, "All neccessary attributes not found in modfile"); std::string a = "NONE"; @@ -195,7 +224,17 @@ bool ProjectionComponent::initializeProjectionSettings(const Dictionary& diction } if (dictionary.hasKeyAndValue(keyNeedsTextureMapDilation)) { - _needsTextureMapDilation = dictionary.value(keyNeedsTextureMapDilation); + _dilation.isEnabled = dictionary.value(keyNeedsTextureMapDilation); + } + + if (dictionary.hasKeyAndValue(keyNeedsShadowing)) { + _shadowing.isEnabled = dictionary.value(keyNeedsShadowing); + } + + _projectionTextureAspectRatio = 1.f; + if (dictionary.hasKeyAndValue(keyTextureMapAspectRatio)) { + _projectionTextureAspectRatio = + static_cast(dictionary.value(keyTextureMapAspectRatio)); } return completeSuccess; @@ -283,6 +322,190 @@ void ProjectionComponent::imageProjectBegin() { // keep handle to the current bound FBO glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_defaultFBO); + if (_textureSizeDirty) { + LDEBUG("Changing texture size to " << std::to_string(_textureSize)); + + // If the texture size has changed, we have to allocate new memory and copy + // the image texture to the new target + + using ghoul::opengl::Texture; + using ghoul::opengl::FramebufferObject; + + // Make a copy of the old textures + std::unique_ptr oldProjectionTexture = std::move(_projectionTexture); + std::unique_ptr oldDilationStencil = std::move(_dilation.stencilTexture); + std::unique_ptr oldDilationTexture = std::move(_dilation.texture); + std::unique_ptr oldDepthTexture = std::move(_shadowing.texture); + + // Generate the new textures + generateProjectionLayerTexture(_textureSize); + + if (_shadowing.isEnabled) { + generateDepthTexture(_textureSize); + } + + auto copyFramebuffers = [](Texture* src, Texture* dst, const std::string& msg) { + glFramebufferTexture( + GL_READ_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + *src, + 0 + ); + + GLenum status = glCheckFramebufferStatus(GL_READ_FRAMEBUFFER); + if (!FramebufferObject::errorChecking(status).empty()) { + LERROR( + "Read Buffer (" << msg << "): " << + FramebufferObject::errorChecking(status) + ); + } + + glFramebufferTexture( + GL_DRAW_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + *dst, + 0 + ); + + status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); + if (!FramebufferObject::errorChecking(status).empty()) { + LERROR( + "Draw Buffer (" << msg << "): " << + FramebufferObject::errorChecking(status) + ); + } + + glBlitFramebuffer( + 0, 0, + src->dimensions().x, src->dimensions().y, + 0, 0, + dst->dimensions().x, dst->dimensions().y, + GL_COLOR_BUFFER_BIT, + GL_LINEAR + ); + }; + + auto copyDepthBuffer = [](Texture* src, Texture* dst, const std::string& msg) { + glFramebufferTexture( + GL_READ_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + *src, + 0 + ); + + GLenum status = glCheckFramebufferStatus(GL_READ_FRAMEBUFFER); + if (!FramebufferObject::errorChecking(status).empty()) { + LERROR( + "Read Buffer (" << msg << "): " << + FramebufferObject::errorChecking(status) + ); + } + + glFramebufferTexture( + GL_DRAW_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + *dst, + 0 + ); + + status = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); + if (!FramebufferObject::errorChecking(status).empty()) { + LERROR( + "Draw Buffer (" << msg << "): " << + FramebufferObject::errorChecking(status) + ); + } + + glBlitFramebuffer( + 0, 0, + src->dimensions().x, src->dimensions().y, + 0, 0, + dst->dimensions().x, dst->dimensions().y, + GL_DEPTH_BUFFER_BIT, + GL_NEAREST + ); + }; + + GLuint fbos[2]; + glGenFramebuffers(2, fbos); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[0]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[1]); + + copyFramebuffers( + oldProjectionTexture.get(), + _projectionTexture.get(), + "Projection" + ); + + if (_dilation.isEnabled) { + copyFramebuffers( + oldDilationStencil.get(), + _dilation.stencilTexture.get(), + "Dilation Stencil" + ); + + copyFramebuffers( + oldDilationTexture.get(), + _dilation.texture.get(), + "Dilation Texture" + ); + } + + if (_shadowing.isEnabled) { + copyDepthBuffer( + oldDepthTexture.get(), + _shadowing.texture.get(), + "Shadowing" + ); + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glDeleteFramebuffers(2, fbos); + + glBindFramebuffer(GL_FRAMEBUFFER, _fboID); + glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + *_projectionTexture, + 0 + ); + + if (_dilation.isEnabled) { + // We only need the stencil texture if we need to dilate + glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT1, + GL_TEXTURE_2D, + *_dilation.stencilTexture, + 0 + ); + + glBindFramebuffer(GL_FRAMEBUFFER, _dilation.fbo); + glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + *_dilation.texture, + 0 + ); + } + + if (_shadowing.isEnabled) { + glBindFramebuffer(GL_FRAMEBUFFER, _depthFboID); + glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + GL_TEXTURE_2D, + *_shadowing.texture, + 0 + ); + } + + _textureSizeDirty = false; + } + glGetIntegerv(GL_VIEWPORT, _viewport); glBindFramebuffer(GL_FRAMEBUFFER, _fboID); @@ -292,14 +515,46 @@ void ProjectionComponent::imageProjectBegin() { static_cast(_projectionTexture->height()) ); - if (_needsTextureMapDilation) { + if (_dilation.isEnabled) { GLenum buffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; glDrawBuffers(2, buffers); } } +bool ProjectionComponent::needsShadowMap() const { + return _shadowing.isEnabled; +} + +ghoul::opengl::Texture& ProjectionComponent::depthTexture() { + return *_shadowing.texture; +} + +void ProjectionComponent::depthMapRenderBegin() { + ghoul_assert(_shadowing.isEnabled, "Shadowing is not enabled"); + + // keep handle to the current bound FBO + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_defaultFBO); + glGetIntegerv(GL_VIEWPORT, _viewport); + + glBindFramebuffer(GL_FRAMEBUFFER, _depthFboID); + glEnable(GL_DEPTH_TEST); + + glViewport( + 0, 0, + static_cast(_shadowing.texture->width()), + static_cast(_shadowing.texture->height()) + ); + + glClear(GL_DEPTH_BUFFER_BIT); +} + +void ProjectionComponent::depthMapRenderEnd() { + glBindFramebuffer(GL_FRAMEBUFFER, _defaultFBO); + glViewport(_viewport[0], _viewport[1], _viewport[2], _viewport[3]); +} + void ProjectionComponent::imageProjectEnd() { - if (_needsTextureMapDilation) { + if (_dilation.isEnabled) { glBindFramebuffer(GL_FRAMEBUFFER, _dilation.fbo); glDisable(GL_BLEND); @@ -328,13 +583,34 @@ void ProjectionComponent::imageProjectEnd() { } void ProjectionComponent::update() { - if (_needsTextureMapDilation) { - if (_dilation.program->isDirty()) { - _dilation.program->rebuildFromFile(); - } + if (_dilation.isEnabled && _dilation.program->isDirty()) { + _dilation.program->rebuildFromFile(); } } +bool ProjectionComponent::depthRendertarget() { + GLint defaultFBO; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO); + // setup FBO + glGenFramebuffers(1, &_depthFboID); + glBindFramebuffer(GL_FRAMEBUFFER, _depthFboID); + glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + GL_TEXTURE_2D, + *_shadowing.texture, + 0); + + glDrawBuffer(GL_NONE); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) + return false; + + glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); + return true; +} + bool ProjectionComponent::auxiliaryRendertarget() { bool completeSuccess = true; @@ -359,7 +635,7 @@ bool ProjectionComponent::auxiliaryRendertarget() { } - if (_needsTextureMapDilation) { + if (_dilation.isEnabled) { // We only need the stencil texture if we need to dilate glFramebufferTexture2D( GL_FRAMEBUFFER, @@ -408,32 +684,24 @@ glm::mat4 ProjectionComponent::computeProjectorMatrix(const glm::vec3 loc, glm:: float nearPlane, float farPlane, glm::vec3& boreSight) { + //rotate boresight into correct alignment boreSight = instrumentMatrix*aim; glm::vec3 uptmp(instrumentMatrix*glm::dvec3(up)); // create view matrix - glm::vec3 e3 = glm::normalize(boreSight); + glm::vec3 e3 = glm::normalize(-boreSight); glm::vec3 e1 = glm::normalize(glm::cross(uptmp, e3)); glm::vec3 e2 = glm::normalize(glm::cross(e3, e1)); - glm::mat4 projViewMatrix = glm::mat4( - e1.x, e2.x, e3.x, 0.f, - e1.y, e2.y, e3.y, 0.f, - e1.z, e2.z, e3.z, 0.f, - -glm::dot(e1, loc), -glm::dot(e2, loc), -glm::dot(e3, loc), 1.f - ); + + glm::mat4 projViewMatrix = glm::mat4(e1.x, e2.x, e3.x, 0.f, + e1.y, e2.y, e3.y, 0.f, + e1.z, e2.z, e3.z, 0.f, + glm::dot(e1, -loc), glm::dot(e2, -loc), glm::dot(e3, -loc), 1.f); // create perspective projection matrix - glm::mat4 projProjectionMatrix = glm::perspective( - glm::radians(fieldOfViewY), aspectRatio, nearPlane, farPlane - ); - // bias matrix - glm::mat4 projNormalizationMatrix = glm::mat4( - 0.5f, 0.f, 0.f, 0.f, - 0.f, 0.5f, 0.f, 0.f, - 0.f, 0.f, 0.5f, 0.f, - 0.5f, 0.5f, 0.5f, 1.f - ); - return projNormalizationMatrix * projProjectionMatrix * projViewMatrix; + glm::mat4 projProjectionMatrix = glm::perspective(glm::radians(fieldOfViewY), aspectRatio, nearPlane, farPlane); + + return projProjectionMatrix*projViewMatrix; } bool ProjectionComponent::doesPerformProjection() const { @@ -449,7 +717,7 @@ float ProjectionComponent::projectionFading() const { } ghoul::opengl::Texture& ProjectionComponent::projectionTexture() const { - if (_needsTextureMapDilation) { + if (_dilation.isEnabled) { return *_dilation.texture; } else { @@ -481,14 +749,6 @@ float ProjectionComponent::aspectRatio() const { return _aspectRatio; } -float ProjectionComponent::nearPlane() const { - return _nearPlane; -} - -float ProjectionComponent::farPlane() const { - return _farPlane; -} - void ProjectionComponent::clearAllProjections() { // keep handle to the current bound FBO GLint defaultFBO; @@ -504,7 +764,7 @@ void ProjectionComponent::clearAllProjections() { glClearColor(0.f, 0.f, 0.f, 0.f); glClear(GL_COLOR_BUFFER_BIT); - if (_needsTextureMapDilation) { + if (_dilation.isEnabled) { glBindFramebuffer(GL_FRAMEBUFFER, _dilation.fbo); glClear(GL_COLOR_BUFFER_BIT); } @@ -543,14 +803,12 @@ std::shared_ptr ProjectionComponent::loadProjectionTextu return std::move(texture); } -bool ProjectionComponent::generateProjectionLayerTexture() { - int maxSize = OpenGLCap.max2DTextureSize() / 2; - +bool ProjectionComponent::generateProjectionLayerTexture(const ivec2& size) { LINFO( - "Creating projection texture of size '" << maxSize << ", " << maxSize / 2 << "'" + "Creating projection texture of size '" << size.x << ", " << size.y << "'" ); _projectionTexture = std::make_unique ( - glm::uvec3(maxSize, maxSize / 2, 1), + glm::uvec3(size, 1), ghoul::opengl::Texture::Format::RGBA ); if (_projectionTexture) { @@ -558,9 +816,9 @@ bool ProjectionComponent::generateProjectionLayerTexture() { //_projectionTexture->setFilter(ghoul::opengl::Texture::FilterMode::AnisotropicMipMap); } - if (_needsTextureMapDilation) { + if (_dilation.isEnabled) { _dilation.texture = std::make_unique( - glm::uvec3(maxSize, maxSize / 2, 1), + glm::uvec3(size, 1), ghoul::opengl::Texture::Format::RGBA ); @@ -570,7 +828,7 @@ bool ProjectionComponent::generateProjectionLayerTexture() { } _dilation.stencilTexture = std::make_unique( - glm::uvec3(maxSize, maxSize / 2, 1), + glm::uvec3(size, 1), ghoul::opengl::Texture::Format::Red, ghoul::opengl::Texture::Format::Red ); @@ -586,4 +844,23 @@ bool ProjectionComponent::generateProjectionLayerTexture() { } +bool ProjectionComponent::generateDepthTexture(const ivec2& size) { + LINFO( + "Creating depth texture of size '" << size.x << ", " << size.y << "'" + ); + + _shadowing.texture = std::make_unique( + glm::uvec3(size, 1), + ghoul::opengl::Texture::Format::DepthComponent, + GL_DEPTH_COMPONENT32F + ); + + if (_shadowing.texture) { + _shadowing.texture->uploadTexture(); + } + + return _shadowing.texture != nullptr; + +} + } // namespace openspace diff --git a/modules/newhorizons/util/projectioncomponent.h b/modules/newhorizons/util/projectioncomponent.h index 3386b63a1d..6bae9afa6f 100644 --- a/modules/newhorizons/util/projectioncomponent.h +++ b/modules/newhorizons/util/projectioncomponent.h @@ -27,6 +27,8 @@ #include #include +#include +#include #include #include @@ -54,13 +56,17 @@ public: bool initializeProjectionSettings(const ghoul::Dictionary& dictionary); bool initializeParser(const ghoul::Dictionary& dictionary); + ghoul::opengl::Texture& depthTexture(); void imageProjectBegin(); void imageProjectEnd(); + void depthMapRenderBegin(); + void depthMapRenderEnd(); + void update(); - bool generateProjectionLayerTexture(); bool auxiliaryRendertarget(); + bool depthRendertarget(); std::shared_ptr loadProjectionTexture( const std::string& texturePath, @@ -81,6 +87,8 @@ public: bool needsClearProjection() const; float projectionFading() const; + bool needsShadowMap() const; + void clearAllProjections(); ghoul::opengl::Texture& projectionTexture() const; @@ -92,18 +100,25 @@ public: float fieldOfViewY() const; float aspectRatio() const; - float nearPlane() const; - float farPlane() const; + +private: + bool generateProjectionLayerTexture(const glm::ivec2& size); + bool generateDepthTexture(const glm::ivec2& size); protected: properties::BoolProperty _performProjection; properties::BoolProperty _clearAllProjections; properties::FloatProperty _projectionFading; - std::unique_ptr _projectionTexture; + properties::IVec2Property _textureSize; + properties::TriggerProperty _applyTextureSize; + bool _textureSizeDirty; + std::unique_ptr _projectionTexture; std::shared_ptr _placeholderTexture; + float _projectionTextureAspectRatio; + std::string _instrumentID; std::string _projectorID; std::string _projecteeID; @@ -111,16 +126,20 @@ protected: std::vector _potentialTargets; float _fovy; float _aspectRatio; - float _nearPlane; - float _farPlane; GLuint _fboID; + GLuint _depthFboID; GLint _defaultFBO; GLint _viewport[4]; - bool _needsTextureMapDilation; struct { + bool isEnabled; + std::unique_ptr texture; + } _shadowing; + + struct { + bool isEnabled; GLuint fbo; GLuint vao; GLuint vbo; diff --git a/modules/newhorizons/util/sequenceparser.cpp b/modules/newhorizons/util/sequenceparser.cpp index 340c3bdb62..6e36361fa6 100644 --- a/modules/newhorizons/util/sequenceparser.cpp +++ b/modules/newhorizons/util/sequenceparser.cpp @@ -42,7 +42,7 @@ namespace openspace { std::map SequenceParser::getSubsetMap(){ return _subsetMap; } -std::vector> SequenceParser::getIstrumentTimes(){ +std::vector> SequenceParser::getInstrumentTimes(){ return _instrumentTimes; } std::vector> SequenceParser::getTargetTimes(){ @@ -52,6 +52,9 @@ std::vector SequenceParser::getCaptureProgression(){ return _captureProgression; }; +std::map>& SequenceParser::getTranslation() { + return _fileTranslation; +} template void writeToBuffer(std::vector& buffer, size_t& currentWriteLocation, T value) { diff --git a/modules/newhorizons/util/sequenceparser.h b/modules/newhorizons/util/sequenceparser.h index 18dd755b2c..258a2b566a 100644 --- a/modules/newhorizons/util/sequenceparser.h +++ b/modules/newhorizons/util/sequenceparser.h @@ -28,14 +28,15 @@ #include #include +#include + #include +#include #include #include namespace openspace { -class Decoder; - struct Image { TimeRange timeRange; std::string path; @@ -54,9 +55,9 @@ class SequenceParser { public: virtual bool create() = 0; virtual std::map getSubsetMap() final; - virtual std::vector> getIstrumentTimes() final; + virtual std::vector> getInstrumentTimes() final; virtual std::vector> getTargetTimes() final; - virtual std::map getTranslation() = 0; + std::map>& getTranslation(); virtual std::vector getCaptureProgression() final; protected: @@ -67,6 +68,8 @@ protected: std::vector> _targetTimes; std::vector _captureProgression; + std::map> _fileTranslation; + NetworkEngine::MessageIdentifier _messageIdentifier; }; diff --git a/modules/toyvolume/toyvolumemodule.cpp b/modules/toyvolume/toyvolumemodule.cpp index 77b65bf4ed..f700830085 100644 --- a/modules/toyvolume/toyvolumemodule.cpp +++ b/modules/toyvolume/toyvolumemodule.cpp @@ -24,7 +24,6 @@ #include -#include #include #include diff --git a/openspace.cfg b/openspace.cfg index 5a77f84544..26fb6f447d 100644 --- a/openspace.cfg +++ b/openspace.cfg @@ -7,9 +7,9 @@ return { -- Sets the scene that is to be loaded by OpenSpace. A scene file is a description -- of all entities that will be visible during an instance of OpenSpace - --Scene = "${SCENE}/default.scene", + Scene = "${SCENE}/default.scene", - Scene = "${SCENE}/globebrowsing.scene", + -- Scene = "${SCENE}/globebrowsing.scene", -- Scene = "${SCENE}/rosetta.scene", -- Scene = "${SCENE}/dawn.scene", -- Scene = "${SCENE}/newhorizons.scene", @@ -27,6 +27,7 @@ return { CONFIG = "${BASE_PATH}/config", CACHE = "${BASE_PATH}/cache", FONTS = "${OPENSPACE_DATA}/fonts", + DOCUMENTATION = "${BASE_PATH}/documentation" }, Fonts = { Mono = "${FONTS}/Droid_Sans_Mono/DroidSansMono.ttf", @@ -36,28 +37,36 @@ return { LogLevel = "Debug", ImmediateFlush = false, Logs = { - { Type = "HTML", FileName = "${BASE_PATH}/log.html", Append = false } + { Type = "html", File = "${BASE_PATH}/log.html", Append = false } }, CapabilitiesVerbosity = "Full" }, - LuaDocumentationFile = { - Type = "text", - File = "${BASE_PATH}/LuaScripting.txt" + LuaDocumentation = { + Type = "html", + File = "${DOCUMENTATION}/LuaScripting.html" }, - PropertyDocumentationFile = { - Type = "text", - File = "${BASE_PATH}/Properties.txt" + PropertyDocumentation = { + Type = "html", + File = "${DOCUMENTATION}/Properties.html" }, - ScriptLogFile = { + ScriptLog = { Type = "text", File = "${BASE_PATH}/ScriptLog.txt" }, KeyboardShortcuts = { - Type = "text", - File = "${BASE_PATH}/KeyboardMapping.txt" + Type = "html", + File = "${DOCUMENTATION}/KeyboardMapping.html" + }, + Documentation = { + Type = "html", + File = "${DOCUMENTATION}/Documentation.html" + }, + FactoryDocumentation = { + Type = "html", + File = "${DOCUMENTATION}/FactoryDocumentation.html" }, ShutdownCountdown = 3, - DownloadRequestURL = "http://130.236.132.168/request.cgi", + DownloadRequestURL = "http://data.openspaceproject.com/request.cgi", RenderingMethod = "Framebuffer" --RenderingMethod = "ABuffer" -- alternative: "Framebuffer" diff --git a/scripts/bind_keys_rosetta.lua b/scripts/bind_keys_rosetta.lua index 47bb50541c..0837db0c7c 100644 --- a/scripts/bind_keys_rosetta.lua +++ b/scripts/bind_keys_rosetta.lua @@ -21,6 +21,7 @@ openspace.bindKey("F8", "openspace.setPropertyValue('67P.renderable.ProjectionCo openspace.bindKey("i", helper.renderable.toggle('ImagePlaneRosetta')) openspace.bindKey("q", helper.renderable.toggle('SunMarker')) -openspace.bindKey("e", helper.renderable.toggle('EarthMarker')) +openspace.bindKey("e", helper.renderable.toggle('JupiterTrail') .. helper.renderable.toggle('SaturnTrail') .. helper.renderable.toggle('UranusTrail') .. helper.renderable.toggle('NeptuneTrail')) +openspace.bindKey("f", helper.renderable.toggle('PhilaeTrail')) openspace.bindKey("c", "openspace.parallel.setAddress('130.236.142.51');openspace.parallel.setPassword('newhorizons-20150714');openspace.parallel.connect();") diff --git a/shaders/abuffer/renderabuffer.frag b/shaders/abuffer/renderabuffer.frag index d3abc97ddf..0dfd046983 100644 --- a/shaders/abuffer/renderabuffer.frag +++ b/shaders/abuffer/renderabuffer.frag @@ -36,10 +36,19 @@ void main() { if (frag.depth < 0) { // discard; } - - //frag.forceFboRendering = true; - // todo: calculate full sample mask from nAaSamples instead of hardcoded 255. - if (!frag.forceFboRendering && (frag.color.a < 1.0 || sampleMask != 255)) { + + bool storeInAbuffer = false; + + if (frag.forceFboRendering) { + storeInAbuffer = false; + } else { + storeInAbuffer = frag.color.a < 1.0 || + sampleMask != 255 || + frag.blend != BLEND_MODE_NORMAL; + // todo: calculate full sample mask from nAaSamples instead of hardcoded 255. + } + + if (storeInAbuffer) { uint newHead = atomicCounterIncrement(atomicCounterBuffer); if (newHead >= #{rendererData.maxTotalFragments}) { discard; // ABuffer is full! diff --git a/shaders/abuffer/resolveabuffer.frag b/shaders/abuffer/resolveabuffer.frag index 38cb3269a5..a722c4f02d 100644 --- a/shaders/abuffer/resolveabuffer.frag +++ b/shaders/abuffer/resolveabuffer.frag @@ -113,7 +113,7 @@ void main() { //normalBlend(finalColor, color); } else if (blend == BLEND_MODE_ADDITIVE) { - accumulatedColor += (1 - accumulatedAlpha) * color.rgb; + accumulatedColor += (1 - accumulatedAlpha) * color.rgb * color.a; //additiveBlend(finalColor, color); } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 23b5075a98..f23b24e3bf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,7 +24,12 @@ set(OPENSPACE_SOURCE ${OPENSPACE_BASE_DIR}/src/openspace.cpp + ${OPENSPACE_BASE_DIR}/src/documentation/core_registration.cpp + ${OPENSPACE_BASE_DIR}/src/documentation/documentation.cpp + ${OPENSPACE_BASE_DIR}/src/documentation/documentationengine.cpp + ${OPENSPACE_BASE_DIR}/src/documentation/verifier.cpp ${OPENSPACE_BASE_DIR}/src/engine/configurationmanager.cpp + ${OPENSPACE_BASE_DIR}/src/engine/configurationmanager_doc.inl ${OPENSPACE_BASE_DIR}/src/engine/downloadmanager.cpp ${OPENSPACE_BASE_DIR}/src/engine/logfactory.cpp ${OPENSPACE_BASE_DIR}/src/engine/moduleengine.cpp @@ -78,9 +83,11 @@ set(OPENSPACE_SOURCE ${OPENSPACE_BASE_DIR}/src/scene/rotation.cpp ${OPENSPACE_BASE_DIR}/src/scene/scale.cpp ${OPENSPACE_BASE_DIR}/src/scene/scene.cpp + ${OPENSPACE_BASE_DIR}/src/scene/scene_doc.inl ${OPENSPACE_BASE_DIR}/src/scene/scene_lua.inl ${OPENSPACE_BASE_DIR}/src/scene/scenegraph.cpp ${OPENSPACE_BASE_DIR}/src/scene/scenegraphnode.cpp + ${OPENSPACE_BASE_DIR}/src/scene/scenegraphnode_doc.inl ${OPENSPACE_BASE_DIR}/src/scripting/lualibrary.cpp ${OPENSPACE_BASE_DIR}/src/scripting/scriptengine.cpp ${OPENSPACE_BASE_DIR}/src/scripting/scriptscheduler.cpp @@ -108,6 +115,11 @@ set(OPENSPACE_SOURCE set(OPENSPACE_HEADER ${OPENSPACE_BASE_DIR}/include/openspace/openspace.h + ${OPENSPACE_BASE_DIR}/include/openspace/documentation/core_registration.h + ${OPENSPACE_BASE_DIR}/include/openspace/documentation/documentation.h + ${OPENSPACE_BASE_DIR}/include/openspace/documentation/documentationengine.h + ${OPENSPACE_BASE_DIR}/include/openspace/documentation/verifier.h + ${OPENSPACE_BASE_DIR}/include/openspace/documentation/verifier.inl ${OPENSPACE_BASE_DIR}/include/openspace/engine/configurationmanager.h ${OPENSPACE_BASE_DIR}/include/openspace/engine/downloadmanager.h ${OPENSPACE_BASE_DIR}/include/openspace/engine/logfactory.h diff --git a/src/documentation/core_registration.cpp b/src/documentation/core_registration.cpp new file mode 100644 index 0000000000..9f938c477e --- /dev/null +++ b/src/documentation/core_registration.cpp @@ -0,0 +1,53 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace openspace { +namespace documentation { + +void registerCoreClasses(documentation::DocumentationEngine& engine) { + engine.addDocumentation(ConfigurationManager::Documentation()); + engine.addDocumentation(Ephemeris::Documentation()); + engine.addDocumentation(Renderable::Documentation()); + engine.addDocumentation(Rotation::Documentation()); + engine.addDocumentation(Scale::Documentation()); + engine.addDocumentation(Scene::Documentation()); + engine.addDocumentation(SceneGraphNode::Documentation()); + engine.addDocumentation(ScreenSpaceRenderable::Documentation()); +} + +} // namespace documentation +} // namespace openspace diff --git a/src/documentation/documentation.cpp b/src/documentation/documentation.cpp new file mode 100644 index 0000000000..5cda43ce74 --- /dev/null +++ b/src/documentation/documentation.cpp @@ -0,0 +1,183 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include +#include + +#include + +namespace { + // Structure used to make offenses unique + struct OffenseCompare { + using Offense = openspace::documentation::TestResult::Offense; + bool operator()(const Offense& lhs, const Offense& rhs) const + { + if (lhs.offender != rhs.offender) { + return lhs.offender < rhs.offender; + } + else { + return std::underlying_type_t(lhs.reason) < + std::underlying_type_t(rhs.reason); + } + } + + }; +} // namespace + +// Unfortunately, the standard library does not contain a no-op for the to_string method +// so we have to include one ourselves +namespace std { +std::string to_string(std::string value) { + return value; +} +} // namespace std + +namespace openspace { +namespace documentation { + +const std::string DocumentationEntry::Wildcard = "*"; + +SpecificationError::SpecificationError(TestResult res, std::string component) + : ghoul::RuntimeError("Error in specification", std::move(component)) + , result(std::move(res)) +{ + ghoul_assert(!result.success, "Result's success must be false"); +} + +DocumentationEntry::DocumentationEntry(std::string k, std::shared_ptr v, + std::string doc, Optional opt) + : key(std::move(k)) + , verifier(std::move(v)) + , documentation(std::move(doc)) + , optional(opt) +{ + ghoul_assert(!key.empty(), "Key must not be empty"); + ghoul_assert(verifier, "Verifier must not be nullptr"); +} + +DocumentationEntry::DocumentationEntry(std::string key, Verifier* v, std::string doc, + Optional optional) + : DocumentationEntry(std::move(key), std::shared_ptr(v), std::move(doc), + optional) +{} + +Documentation::Documentation(std::string n, std::string id, DocumentationEntries entries, + Exhaustive exh) + : name(std::move(n)) + , id(std::move(id)) + , entries(std::move(entries)) + , exhaustive(std::move(exh)) +{} + +Documentation::Documentation(std::string n, DocumentationEntries entries, Exhaustive exh) + : Documentation(n, "", entries, exh) +{} + +Documentation::Documentation(DocumentationEntries entries, Exhaustive exh) + : Documentation("", "", entries, exh) +{} + +TestResult testSpecification(const Documentation& d, const ghoul::Dictionary& dict) { + TestResult result; + result.success = true; + + auto applyVerifier = [dict, &result](Verifier& verifier, const std::string& key) { + TestResult res = verifier(dict, key); + if (!res.success) { + result.success = false; + result.offenses.insert( + result.offenses.end(), + res.offenses.begin(), + res.offenses.end() + ); + } + }; + + for (const auto& p : d.entries) { + if (p.key == DocumentationEntry::Wildcard) { + for (const std::string& key : dict.keys()) { + applyVerifier(*(p.verifier), key); + } + } + else { + if (p.optional && !dict.hasKey(p.key)) { + // If the key is optional and it doesn't exist, we don't need to check it + // if the key exists, it has to be correct, however + continue; + } + applyVerifier(*(p.verifier), p.key); + } + } + + if (d.exhaustive) { + // If the documentation is exhaustive, we have to check if there are extra values + // in the table that are not covered by the Documentation + + for (const std::string& key : dict.keys()) { + auto it = std::find_if( + d.entries.begin(), + d.entries.end(), + [&key](const DocumentationEntry& entry) { + if (entry.key == DocumentationEntry::Wildcard) { + return true; + } + else { + return entry.key == key; + } + } + ); + + if (it == d.entries.end()) { + result.success = false; + result.offenses.push_back( + { key, TestResult::Offense::Reason::ExtraKey } + ); + } + } + } + + // Remove duplicate offenders that might occur if multiple rules apply to a single + // key and more than one of these rules are broken + std::set uniqueOffenders( + result.offenses.begin(), result.offenses.end() + ); + result.offenses = std::vector( + uniqueOffenders.begin(), uniqueOffenders.end() + ); + + return result; +} + +void testSpecificationAndThrow(const Documentation& doc, const ghoul::Dictionary& dict, + std::string component) +{ + // Perform testing against the documentation/specification + TestResult testResult = testSpecification(doc, dict); + if (!testResult.success) { + throw SpecificationError(std::move(testResult), std::move(component)); + } +} + +} // namespace documentation +} // namespace openspace diff --git a/src/documentation/documentationengine.cpp b/src/documentation/documentationengine.cpp new file mode 100644 index 0000000000..d1a0e41ca6 --- /dev/null +++ b/src/documentation/documentationengine.cpp @@ -0,0 +1,281 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include + +#include + +#include + +#include + +namespace openspace { +namespace documentation { + +DocumentationEngine::DuplicateDocumentationException::DuplicateDocumentationException( + Documentation documentation) + : ghoul::RuntimeError(fmt::format( + "Duplicate Documentation with name '{}' and id '{}'", + documentation.name, + documentation.id + )) + , documentation(std::move(documentation)) +{} + +DocumentationEngine& DocumentationEngine::ref() { + static DocumentationEngine engine; + return engine; +} + +std::string generateTextDocumentation(const Documentation& d, int& indentLevel) { + using namespace std::string_literals; + + auto indentMessage = [&indentLevel](std::string prefix, std::string msg) { + if (msg.empty()) { + return ""s; + } + else { + return std::string(indentLevel, '\t') + prefix + ": " + msg + '\n'; + } + }; + std::string result; + + result += indentMessage("Name", d.name); + if (!d.name.empty()) { + ++indentLevel; + } + for (const auto& p : d.entries) { + result += indentMessage("Key", (p.key == "*") ? p.key : "\"" + p.key + "\""); + result += indentMessage("Optional", (p.optional ? "true" : "false")); + result += indentMessage("Type", p.verifier->type()); + TableVerifier* tv = dynamic_cast(p.verifier.get()); + if (tv) { + // We have a TableVerifier, so we need to recurse + ++indentLevel; + result += generateTextDocumentation({ "", "", tv->documentations }, indentLevel); + result = result.substr(0, result.size() - 2); + --indentLevel; + } + else { + result += indentMessage("Restrictions", p.verifier->documentation()); + } + result += indentMessage("Documentation", p.documentation); + result += "\n\n"; + } + if (!d.name.empty()) { + --indentLevel; + } + + return result; +} + +std::string generateJsonDocumentation(const Documentation& d) { + std::stringstream result; + result << "{"; + + result << "\"name\": \"" << d.name << "\","; + result << "\"entries\": ["; + for (const auto& p : d.entries) { + result << "{"; + result << "\"key\": \"" << p.key << "\","; + result << "\"optional\": \"" << (p.optional ? "true" : "false") << "\","; + result << "\"type\": \"" << p.verifier->type() << "\","; + TableVerifier* tv = dynamic_cast(p.verifier.get()); + if (tv) { + std::string json = generateJsonDocumentation({ "", "", tv->documentations }); + // We have a TableVerifier, so we need to recurse + result << "\"restrictions\": " << json << ","; + } + else { + result << "\"restrictions\": \"" << p.verifier->documentation() << "\","; + } + result << "},"; + } + + result << ']'; + result << "}"; + + return result.str(); +} + +std::string generateHtmlDocumentation(const Documentation& d) { + std::stringstream html; + + html << "\t\n" + << "\t\t" << d.name << "\n"; + + for (const auto& p : d.entries) { + html << "\t\n" + << "\t\t\n" + << "\t\t" << p.key << "\n" + << "\t\t" << (p.optional ? "Optional" : "Required") << "\n" + << "\t\t" << p.verifier->type() << "\n"; + + TableVerifier* tv = dynamic_cast(p.verifier.get()); + ReferencingVerifier* rv = dynamic_cast(p.verifier.get()); + + // We have to check ReferencingVerifier first as a ReferencingVerifier is also a + // TableVerifier + if (rv) { + std::vector documentations = DocEng.documentations(); + auto it = std::find_if( + documentations.begin(), + documentations.end(), + [rv](const Documentation& doc) { return doc.id == rv->identifier; } + ); + + html << "\t\t" + << "\t\t\tReferencing: " + << "identifier << "\">" << it->name << "" + << "\t\t"; + } + else if (tv) { + // We have a TableVerifier, so we need to recurse + html << "\n" + << "\t\n" + << "\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\n" + << "\t\n" + << "\t\n" + << generateHtmlDocumentation({ "", "", tv->documentations }) + << "\t\n" + << "
KeyOptionalTypeRestrictionsDocumentation
\n" + << "\n"; + } + else { + html << "\t\t" << p.verifier->documentation() << "\n"; + } + html << "\t\t" << p.documentation << "\n" + << "\t\n"; + + } + + return html.str(); +} + +void DocumentationEngine::writeDocumentation(const std::string& f, const std::string& t) { + if (t == "text") { + std::ofstream file; + file.exceptions(~std::ofstream::goodbit); + file.open(f); + + for (const Documentation& d : _documentations) { + int indent = 0; + file << documentation::generateTextDocumentation(d, indent) << "\n\n"; + } + } + else if (t == "html") { + std::ofstream file; + file.exceptions(~std::ofstream::goodbit); + file.open(f); + +#ifdef JSON + std::stringstream json; + json << "["; + + for (const Documentation& d : _documentations) { + json << generateJsonDocumentation(d); + json << ","; + } + + json << "]"; + + std::string jsonText = json.str(); +#else + std::stringstream html; + + html << "\n" + << "\t\n" + << "\t\tDocumentation\n" + << "\t\n" + << "\n"; + + + html << "\n" + << "\t\n\n" + << "\t\n" + << "\t\t\n" + << "\t\t\t\n" + << "\t\t\n" + << "\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\n" + << "\t\n" + << "\t\n"; + + for (const Documentation& d : _documentations) { + html << generateHtmlDocumentation(d); + + html << "\t\n"; + } + + html << "\t\n" + << "
Documentation
Name
KeyOptionalTypeRestrictionsDocumentation

\n"; + + html << "\n"; + html << "\n"; + + file << html.str(); +#endif + } +} + +void DocumentationEngine::addDocumentation(Documentation doc) { + if (doc.id.empty()) { + _documentations.push_back(std::move(doc)); + } + else { + auto it = std::find_if( + _documentations.begin(), + _documentations.end(), + [doc](const Documentation& d) { return doc.id == d.id; } + ); + + if (it != _documentations.end()) { + throw DuplicateDocumentationException(std::move(doc)); + } + else { + _documentations.push_back(std::move(doc)); + } + } +} + +std::vector DocumentationEngine::documentations() const { + return _documentations; +} + +} // namespace documentation +} // namespace openspace diff --git a/src/documentation/verifier.cpp b/src/documentation/verifier.cpp new file mode 100644 index 0000000000..7e90e34be3 --- /dev/null +++ b/src/documentation/verifier.cpp @@ -0,0 +1,273 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include + +namespace openspace { +namespace documentation { + +// The explicit template instantiations for many of the commonly used template values +// This cuts down on the compilation time by only compiling these once +template struct Vector2Verifier; +template struct Vector2Verifier; +template struct Vector2Verifier; +template struct Vector3Verifier; +template struct Vector3Verifier; +template struct Vector3Verifier; +template struct Vector4Verifier; +template struct Vector4Verifier; +template struct Vector4Verifier; + +template struct LessVerifier; +template struct LessVerifier; +template struct LessEqualVerifier; +template struct LessEqualVerifier; +template struct GreaterVerifier; +template struct GreaterVerifier; +template struct GreaterEqualVerifier; +template struct GreaterEqualVerifier; +template struct EqualVerifier; +template struct EqualVerifier; +template struct EqualVerifier; +template struct EqualVerifier; +template struct UnequalVerifier; +template struct UnequalVerifier; +template struct UnequalVerifier; +template struct UnequalVerifier; + +template struct InListVerifier; +template struct InListVerifier; +template struct InListVerifier; +template struct InListVerifier; +template struct NotInListVerifier; +template struct NotInListVerifier; +template struct NotInListVerifier; +template struct NotInListVerifier; + +template struct InRangeVerifier; +template struct InRangeVerifier; +template struct NotInRangeVerifier; +template struct NotInRangeVerifier; + +template struct AnnotationVerifier; +template struct AnnotationVerifier; +template struct AnnotationVerifier; +template struct AnnotationVerifier; +template struct AnnotationVerifier; + +std::string BoolVerifier::type() const { + return "Boolean"; +} + +std::string DoubleVerifier::type() const { + return "Double"; +} + +TestResult IntVerifier::operator()(const ghoul::Dictionary & dict, + const std::string & key) const { + if (dict.hasKeyAndValue(key)) { + // We we have a key and the value is int, we are done + return{ true, {} }; + } + else { + if (dict.hasKey(key)) { + if (dict.hasValue(key)) { + // If we have a double value, we need to check if it is integer + double value = dict.value(key); + double intPart; + bool isInt = modf(value, &intPart) == 0.0; + if (isInt) { + return{ true,{} }; + } + else { + return{ false, { { key, TestResult::Offense::Reason::WrongType } } }; + } + } + else { + // If we don't have a double value, we cannot have an int value + return{ false, { { key, TestResult::Offense::Reason::WrongType } } }; + } + } + else { + return{ false, { {key, TestResult::Offense::Reason::MissingKey }} }; + } + } +} + +std::string IntVerifier::type() const { + return "Integer"; +} + +std::string StringVerifier::type() const { + return "String"; +} + +TableVerifier::TableVerifier(std::vector d, Exhaustive exhaustive) + : documentations(std::move(d)) + , exhaustive(std::move(exhaustive)) {} + +TestResult TableVerifier::operator()(const ghoul::Dictionary& dict, + const std::string& key) const { + if (dict.hasKeyAndValue(key)) { + ghoul::Dictionary d = dict.value(key); + TestResult res = testSpecification({ "", documentations, exhaustive }, d); + + for (TestResult::Offense& s : res.offenses) { + s.offender = key + "." + s.offender; + } + + return res; + } + else { + if (dict.hasKey(key)) { + return{ false, { { key, TestResult::Offense::Reason::WrongType } } }; + + } + else { + return{ false, { { key, TestResult::Offense::Reason::MissingKey } } }; + } + } +} + +std::string TableVerifier::type() const { + return "Table"; +} + +ReferencingVerifier::ReferencingVerifier(std::string id) + : identifier(std::move(id)) +{ + ghoul_assert(!identifier.empty(), "identifier must not be empty"); +} + +TestResult ReferencingVerifier::operator()(const ghoul::Dictionary& dictionary, + const std::string& key) const +{ + TestResult res = TableVerifier::operator()(dictionary, key); + if (res.success) { + std::vector documentations = DocEng.documentations(); + + auto it = std::find_if( + documentations.begin(), + documentations.end(), + [this](const Documentation& doc) { return doc.id == identifier; } + ); + + if (it == documentations.end()) { + return { false, { { key, TestResult::Offense::Reason::UnknownIdentifier } } }; + } + else { + ghoul::Dictionary d = dictionary.value(key); + TestResult res = testSpecification(*it, d); + + for (TestResult::Offense& s : res.offenses) { + s.offender = key + "." + s.offender; + } + + return res; + } + } + else { + return res; + } +} + +std::string ReferencingVerifier::documentation() const { + using namespace std::string_literals; + return "Referencing Documentation: '"s + identifier + "'"; +} + +AndVerifier::AndVerifier(Verifier* lhs, Verifier* rhs) + : lhs(lhs) + , rhs(rhs) +{ + ghoul_assert(lhs, "lhs must not be nullptr"); + ghoul_assert(rhs, "rhs must not be nullptr"); +} + +TestResult AndVerifier::operator()(const ghoul::Dictionary& dict, + const std::string& key) const +{ + TestResult resLhs = lhs->operator()(dict, key); + TestResult resRhs = rhs->operator()(dict, key); + + if (resLhs.success && resRhs.success) { + return { true, {} }; + } + else { + return { false, { { key, TestResult::Offense::Reason::Verification } } }; + } +} + +std::string AndVerifier::type() const { + if (lhs->type() != rhs->type()) { + return lhs->type() + " and " + rhs->type(); + } + else { + return lhs->type(); + } +} + +std::string AndVerifier::documentation() const { + return lhs->documentation() + " and " + rhs->documentation(); +} + +OrVerifier::OrVerifier(Verifier* lhs, Verifier* rhs) + : lhs(lhs) + , rhs(rhs) +{ + ghoul_assert(lhs, "lhs must not be nullptr"); + ghoul_assert(rhs, "rhs must not be nullptr"); +} + +TestResult OrVerifier::operator()(const ghoul::Dictionary& dict, + const std::string& key) const { + TestResult resA = lhs->operator()(dict, key); + TestResult resB = rhs->operator()(dict, key); + + if (resA.success || resB.success) { + return { true, {} }; + } + else { + return { false, { { key, TestResult::Offense::Reason::Verification } } }; + } +} + +std::string OrVerifier::type() const { + if (lhs->type() != rhs->type()) { + return lhs->type() + " or " + rhs->type(); + } + else { + return lhs->type(); + } +} + +std::string OrVerifier::documentation() const { + return lhs->documentation() + " or " + rhs->documentation(); +} + + +} // namespace documentation +} // namespace openspace diff --git a/src/engine/configurationmanager.cpp b/src/engine/configurationmanager.cpp index e72939c75d..fb0972d471 100644 --- a/src/engine/configurationmanager.cpp +++ b/src/engine/configurationmanager.cpp @@ -32,6 +32,8 @@ using std::string; +#include "configurationmanager_doc.inl" + namespace { const string _configurationFile = "openspace.cfg"; const string _keyBasePath = "BASE_PATH"; @@ -43,25 +45,32 @@ const string ConfigurationManager::KeyPaths = "Paths"; const string ConfigurationManager::KeyCache = "CACHE"; const string ConfigurationManager::KeyFonts = "Fonts"; const string ConfigurationManager::KeyConfigSgct = "SGCTConfig"; -const string ConfigurationManager::KeyLuaDocumentationType = "LuaDocumentationFile.Type"; -const string ConfigurationManager::KeyLuaDocumentationFile = "LuaDocumentationFile.File"; -const string ConfigurationManager::KeyScriptLogType = "ScriptLogFile.Type"; -const string ConfigurationManager::KeyScriptLogFile = "ScriptLogFile.File"; -const string ConfigurationManager::KeyPropertyDocumentationType = - "PropertyDocumentationFile.Type"; -const string ConfigurationManager::KeyPropertyDocumentationFile = - "PropertyDocumentationFile.File"; -const string ConfigurationManager::KeyKeyboardShortcutsType = "KeyboardShortcuts.Type"; -const string ConfigurationManager::KeyKeyboardShortcutsFile = "KeyboardShortcuts.File"; + +const string ConfigurationManager::PartType = "Type"; +const string ConfigurationManager::PartFile = "File"; + +const string ConfigurationManager::KeyLuaDocumentation = "LuaDocumentation"; +const string ConfigurationManager::KeyScriptLog = "ScriptLog"; +const string ConfigurationManager::KeyPropertyDocumentation = "PropertyDocumentation"; +const string ConfigurationManager::KeyKeyboardShortcuts = "KeyboardShortcuts"; +const string ConfigurationManager::KeyDocumentation = "Documentation"; +const string ConfigurationManager::KeyFactoryDocumentation = "FactoryDocumentation"; const string ConfigurationManager::KeyConfigScene = "Scene"; -const string ConfigurationManager::KeyLogLevel = "Logging.LogLevel"; -const string ConfigurationManager::KeyLogImmediateFlush = "Logging.ImmediateFlush"; -const string ConfigurationManager::KeyLogs = "Logging.Logs"; + +const string ConfigurationManager::KeyLogging = "Logging"; +const string ConfigurationManager::PartLogLevel = "LogLevel"; +const string ConfigurationManager::PartImmediateFlush = "ImmediateFlush"; +const string ConfigurationManager::PartLogs = "Logs"; +const string ConfigurationManager::PartAppend = "Append"; +const string ConfigurationManager::PartCapabilitiesVerbosity = "CapabilitiesVerbosity"; + const string ConfigurationManager::KeyCapabilitiesVerbosity = - "Logging.CapabilitiesVerbosity"; + KeyLogging + "." + PartCapabilitiesVerbosity; + const string ConfigurationManager::KeyShutdownCountdown = "ShutdownCountdown"; const string ConfigurationManager::KeyDisableMasterRendering = "DisableRenderingOnMaster"; const string ConfigurationManager::KeyDownloadRequestURL = "DownloadRequestURL"; +const string ConfigurationManager::KeyRenderingMethod = "RenderingMethod"; string ConfigurationManager::findConfiguration(const string& filename) { using ghoul::filesystem::Directory; @@ -94,8 +103,9 @@ string ConfigurationManager::findConfiguration(const string& filename) { void ConfigurationManager::loadFromFile(const string& filename) { using ghoul::filesystem::FileSystem; - if (!FileSys.fileExists(filename)) + if (!FileSys.fileExists(filename)) { throw ghoul::FileNotFoundError(filename, "ConfigurationManager"); + } // ${BASE_PATH} string basePathToken = FileSystem::TokenOpeningBraces + _keyBasePath @@ -109,6 +119,13 @@ void ConfigurationManager::loadFromFile(const string& filename) { // Loading the configuration file into ourselves ghoul::lua::loadDictionaryFromFile(filename, *this); + // Perform testing against the documentation/specification + openspace::documentation::testSpecificationAndThrow( + ConfigurationManager::Documentation(), + *this, + "ConfigurationManager" + ); + // Register all the paths ghoul::Dictionary dictionary = value(KeyPaths); diff --git a/src/engine/configurationmanager_doc.inl b/src/engine/configurationmanager_doc.inl new file mode 100644 index 0000000000..e83cda2239 --- /dev/null +++ b/src/engine/configurationmanager_doc.inl @@ -0,0 +1,333 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +namespace openspace { + +Documentation ConfigurationManager::Documentation() { + using namespace documentation; + + return { + "OpenSpace Configuration", + { + { + ConfigurationManager::KeyConfigSgct, + new StringAnnotationVerifier("A valid SGCT configuration file"), + "The SGCT configuration file that determines the window and view frustum " + "settings that are being used when OpenSpace is started." + }, + { + ConfigurationManager::KeyConfigScene, + new StringAnnotationVerifier( + "A valid scene file as described in the Scene documentation"), + "The scene description that is used to populate the application after " + "startup. The scene determines which objects are loaded, the startup " + "time and other scene-specific settings. More information is provided in " + "the Scene documentation." + }, + { + ConfigurationManager::KeyPaths, + new TableVerifier({ + { "*", new StringVerifier } + }), + "A list of paths that are automatically registered with the file system. " + "If a key X is used in the table, it is then useable by referencing ${X} " + "in all other configuration files or scripts.", + Optional::Yes + }, + { + ConfigurationManager::KeyFonts, + new TableVerifier({ + { "*", new StringVerifier, "Font paths loadable by FreeType" } + }), + "A list of all fonts that will automatically be loaded on startup. Each " + "key-value pair contained in the table will become the name and the file " + "for a font.", + Optional::Yes + }, + { + ConfigurationManager::KeyLogging, + new TableVerifier({ + { + ConfigurationManager::PartLogLevel, + new StringInListVerifier( + // List from logmanager.cpp::levelFromString + {"Debug", "Info", "Warning", "Error", "Fatal", "None" } + ), + "The severity of log messages that will be displayed. Only " + "messages of the selected level or higher will be displayed. All " + "levels below will be silently discarded. The order of " + "severities is: Debug < Info < Warning < Error < Fatal < None.", + Optional::Yes + }, + { + ConfigurationManager::PartImmediateFlush, + new BoolVerifier, + "Determines whether error messages will be displayed immediately " + "or if it is acceptable to have a short delay, but being more " + "performant. If the delay is allowed ('true'), messages might " + "get lost if the application crashes shortly after a message was " + "logged.", + Optional::Yes + }, + { + ConfigurationManager::PartLogs, + new TableVerifier({ + { + "*", + new TableVerifier({ + { + ConfigurationManager::PartType, + new StringInListVerifier({ + // List from logfactory.cpp::createLog + "text", "html" + }), + "The type of the new log to be generated." + }, + { + ConfigurationManager::PartFile, + new StringVerifier, + "The filename to which the log will be written." + }, + { + ConfigurationManager::PartAppend, + new BoolVerifier, + "Determines whether the file will be cleared at " + "startup or if the contents will be appended to " + "previous runs.", + Optional::Yes + } + }), + "Additional log files", + Optional::Yes + } + }), + "Per default, log messages are written to the console, the " + "onscreen text, and (if available) the Visual Studio output " + "window. This table can define other logging methods that will " + "be used additionally.", + Optional::Yes + }, + { + ConfigurationManager::PartCapabilitiesVerbosity, + new StringInListVerifier( + // List from OpenspaceEngine::initialize + { "None", "Minimal", "Default", "Full" } + ), + "At startup, a list of system capabilities is created and logged." + "This value determines how verbose this listing should be.", + Optional::Yes + } + }), + "Configurations for the logging of messages that are generated " + "throughout the code and are useful for debugging potential errors or " + "other information.", + Optional::Yes + }, + { + ConfigurationManager::KeyLuaDocumentation, + new TableVerifier({ + { + ConfigurationManager::PartType, + new StringInListVerifier( + // List from ScriptEngine::writeDocumentation + { "text", "html" } + ), + "The type of documentation that will be written." + }, + { + ConfigurationManager::PartFile, + new StringVerifier, + "The filename that will be created on startup containing the " + "documentation of available Lua functions. Any existing file " + "will be silently overwritten." + } + }), + "Descriptions of whether and where to create a documentation file that " + "describes the available Lua functions that can be executed in scene " + "files or per console.", + Optional::Yes + }, + { + ConfigurationManager::KeyPropertyDocumentation, + new TableVerifier({ + { + ConfigurationManager::PartType, + new StringInListVerifier( + // List taken from Scene::writePropertyDocumentation + { "text", "html" } + ), + "The type of property documentation file that is created." + }, + { + ConfigurationManager::PartFile, + new StringVerifier, + "The file that will be created on startup containing a list of " + "all properties in the scene. Any existing file will be silently " + "overwritten." + } + }), + "Descriptions of whether and where to create a list of all properties " + "that were created in the current scene.", + Optional::Yes + }, + { + ConfigurationManager::KeyScriptLog, + new TableVerifier({ + { + ConfigurationManager::PartType, + new StringInListVerifier( + // List taken from ScriptEngine::writeLog + { "text" } + ), + "The type of logfile that will be created." + }, + { + ConfigurationManager::PartFile, + new StringVerifier, + "The file that will be created on startup containing the log of " + "all Lua scripts that are executed. Any existing file (including " + "the results from previous runs) will be silently overwritten." + } + }), + "Contains a log of all Lua scripts that were executed in the last " + "session.", + Optional::Yes + }, + { + ConfigurationManager::KeyKeyboardShortcuts, + new TableVerifier({ + { + ConfigurationManager::PartType, + new StringInListVerifier( + // List from InteractionHandler::writeKeyboardDocumentation + { "text", "html" } + ), + "The type of keyboard binding documentation that should be " + "written." + }, + { + ConfigurationManager::PartFile, + new StringVerifier, + "The file that will be created on startup containing the list of " + "all keyboard bindings with their respective Lua scripts. Any " + "previous file in this location will be silently overritten." + } + }), + "Contains the collection of all keyboard shortcuts that were collected " + "during startup. For each key, it mentions which scripts will be " + "executed in the current session.", + Optional::Yes + }, + { + ConfigurationManager::KeyDocumentation, + new TableVerifier({ + { + ConfigurationManager::PartType, + new StringInListVerifier( + // List from DocumentationEngine::writeDocumentation + { "text", "html" } + ), + "The type of documentation that should be written." + }, + { + ConfigurationManager::PartFile, + new StringVerifier, + "The file that will be created on startup containing this " + "documentation. Any previous file in this location will be silently " + "overritten." + } + }), + "This defines the location and type of this documentation file.", + Optional::Yes + }, + { + ConfigurationManager::KeyFactoryDocumentation, + new TableVerifier({ + { + ConfigurationManager::PartType, + new StringInListVerifier( + // List from FactoryManager::writeDocumentation + { "text", "html" } + ), + "The type of documentation that should be written." + }, + { + ConfigurationManager::PartFile, + new StringVerifier, + "The file that will be created on startup containing the factory " + "documentation. Any previous file in this location will be silently " + "overritten." + } + }), + "This defines the location and type of the factory documentation file, which " + "shows the different types of objects that can be created in the current " + "application configuration." + }, + { + ConfigurationManager::KeyShutdownCountdown, + new DoubleGreaterEqualVerifier(0.0), + "The countdown that the application will wait between pressing ESC and " + "actually shutting down. If ESC is pressed again in this time, the " + "shutdown is aborted.", + Optional::Yes + }, + { + ConfigurationManager::KeyDownloadRequestURL, + new OrVerifier( + new StringVerifier, + new TableVerifier({ + { "*", new StringVerifier } + }) + ), + "The URL from which files will be downloaded by the Launcher. This can " + "either be a single URL or a list of possible URLs from which the " + "Launcher can then choose.", + Optional::Yes + }, + { + ConfigurationManager::KeyRenderingMethod, + new StringInListVerifier( + // List from RenderEngine::setRendererFromString + { "Framebuffer", "ABuffer" } + ), + "The renderer that is use after startup. The renderer 'ABuffer' requires " + "support for at least OpenGL 4.3", + Optional::Yes + }, + { + ConfigurationManager::KeyDisableMasterRendering, + new BoolVerifier, + "Toggles whether the master in a multi-application setup should be rendering " + "or just managing the state of the network. This is desired in cases where " + "the master computer does not have the resources to render a scene.", + Optional::Yes + } + } + }; +}; + + +} // namespace openspace \ No newline at end of file diff --git a/src/engine/logfactory.cpp b/src/engine/logfactory.cpp index 2b5f2cd226..ffc145d95e 100644 --- a/src/engine/logfactory.cpp +++ b/src/engine/logfactory.cpp @@ -32,14 +32,14 @@ namespace { const std::string keyType = "Type"; - const std::string keyFilename = "FileName"; + const std::string keyFilename = "File"; const std::string keyAppend = "Append"; const std::string keyTimeStamping = "TimeStamping"; const std::string keyDateStamping = "DateStamping"; const std::string keyCategoryStamping = "CategoryStamping"; const std::string keyLogLevelStamping = "LogLevelStamping"; - const std::string valueHtmlLog = "HTML"; + const std::string valueHtmlLog = "html"; const std::string valueTextLog = "Text"; } diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index e41b46fecb..29d5f9934c 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -26,6 +26,8 @@ #include +#include +#include #include #include #include @@ -152,12 +154,15 @@ OpenSpaceEngine::OpenSpaceEngine(std::string programName, _interactionHandler->setPropertyOwner(_globalPropertyNamespace.get()); _globalPropertyNamespace->addPropertySubOwner(_interactionHandler.get()); _globalPropertyNamespace->addPropertySubOwner(_settingsEngine.get()); + FactoryManager::initialize(); FactoryManager::ref().addFactory( - std::make_unique>() + std::make_unique>(), + "Renderable" ); FactoryManager::ref().addFactory( - std::make_unique>() + std::make_unique>(), + "Ephemeris" ); SpiceManager::initialize(); Time::initialize(); @@ -299,6 +304,15 @@ bool OpenSpaceEngine::create(int argc, char** argv, // Register modules _engine->_moduleEngine->initialize(); + documentation::registerCoreClasses(DocEng); + // After registering the modules, the documentations for the available classes + // can be added as well + for (OpenSpaceModule* m : _engine->_moduleEngine->modules()) { + for (auto&& doc : m->documentations()) { + DocEng.addDocumentation(doc); + } + } + // Create the cachemanager FileSys.createCacheManager( absPath("${" + ConfigurationManager::KeyCache + "}"), CacheVersion @@ -361,7 +375,9 @@ bool OpenSpaceEngine::initialize() { using Verbosity = ghoul::systemcapabilities::SystemCapabilitiesComponent::Verbosity; Verbosity verbosity = Verbosity::Default; - if (configurationManager().hasKeyAndValue(ConfigurationManager::KeyCapabilitiesVerbosity)) { + if (configurationManager().hasKeyAndValue( + + ConfigurationManager::KeyCapabilitiesVerbosity)) { std::map verbosityMap = { { "None", Verbosity::None }, { "Minimal", Verbosity::Minimal }, @@ -403,18 +419,54 @@ bool OpenSpaceEngine::initialize() { scriptEngine().initialize(); // If a LuaDocumentationFile was specified, generate it now - const bool hasType = configurationManager().hasKey(ConfigurationManager::KeyLuaDocumentationType); - const bool hasFile = configurationManager().hasKey(ConfigurationManager::KeyLuaDocumentationFile); - if (hasType && hasFile) { + const std::string LuaDocumentationType = + ConfigurationManager::KeyLuaDocumentation + "." + ConfigurationManager::PartType; + const std::string LuaDocumentationFile = + ConfigurationManager::KeyLuaDocumentation + "." + ConfigurationManager::PartFile; + + const bool hasLuaDocType = configurationManager().hasKey(LuaDocumentationType); + const bool hasLuaDocFile = configurationManager().hasKey(LuaDocumentationFile); + if (hasLuaDocType && hasLuaDocFile) { std::string luaDocumentationType; - configurationManager().getValue(ConfigurationManager::KeyLuaDocumentationType, luaDocumentationType); + configurationManager().getValue(LuaDocumentationType, luaDocumentationType); std::string luaDocumentationFile; - configurationManager().getValue(ConfigurationManager::KeyLuaDocumentationFile, luaDocumentationFile); + configurationManager().getValue(LuaDocumentationFile, luaDocumentationFile); luaDocumentationFile = absPath(luaDocumentationFile); _scriptEngine->writeDocumentation(luaDocumentationFile, luaDocumentationType); } + // If a general documentation was specified, generate it now + const std::string DocumentationType = + ConfigurationManager::KeyDocumentation + '.' + ConfigurationManager::PartType; + const std::string DocumentationFile = + ConfigurationManager::KeyDocumentation + '.' + ConfigurationManager::PartFile; + + const bool hasDocumentationType = configurationManager().hasKey(DocumentationType); + const bool hasDocumentationFile = configurationManager().hasKey(DocumentationFile); + if (hasDocumentationType && hasDocumentationFile) { + std::string documentationType; + configurationManager().getValue(DocumentationType, documentationType); + std::string documentationFile; + configurationManager().getValue(DocumentationFile, documentationFile); + documentationFile = absPath(documentationFile); + DocEng.writeDocumentation(documentationFile, documentationType); + } + + const std::string FactoryDocumentationType = + ConfigurationManager::KeyFactoryDocumentation + '.' + ConfigurationManager::PartType; + + const std::string FactoryDocumentationFile = + ConfigurationManager::KeyFactoryDocumentation + '.' + ConfigurationManager::PartFile; + bool hasFactoryDocumentationType = configurationManager().hasKey(FactoryDocumentationType); + bool hasFactoryDocumentationFile = configurationManager().hasKey(FactoryDocumentationFile); + if (hasFactoryDocumentationType && hasFactoryDocumentationFile) { + std::string type = configurationManager().value(FactoryDocumentationType); + std::string file = configurationManager().value(FactoryDocumentationFile); + + FactoryManager::ref().writeDocumentation(absPath(file), type); + } + bool disableMasterRendering = false; configurationManager().getValue( ConfigurationManager::KeyDisableMasterRendering, disableMasterRendering); @@ -680,12 +732,21 @@ void OpenSpaceEngine::loadFonts() { } void OpenSpaceEngine::configureLogging() { - if (configurationManager().hasKeyAndValue(ConfigurationManager::KeyLogLevel)) { + const std::string KeyLogLevel = + ConfigurationManager::KeyLogging + '.' + ConfigurationManager::PartLogLevel; + const std::string KeyLogImmediateFlush = + ConfigurationManager::KeyLogging + '.' + ConfigurationManager::PartImmediateFlush; + const std::string KeyLogs = + ConfigurationManager::KeyLogging + '.' + ConfigurationManager::PartLogs; + + + + if (configurationManager().hasKeyAndValue(KeyLogLevel)) { std::string logLevel; - configurationManager().getValue(ConfigurationManager::KeyLogLevel, logLevel); + configurationManager().getValue(KeyLogLevel, logLevel); bool immediateFlush = false; - configurationManager().getValue(ConfigurationManager::KeyLogImmediateFlush, immediateFlush); + configurationManager().getValue(KeyLogImmediateFlush, immediateFlush); LogManager::LogLevel level = LogManager::levelFromString(logLevel); LogManager::deinitialize(); @@ -697,9 +758,9 @@ void OpenSpaceEngine::configureLogging() { LogMgr.addLog(std::make_unique()); } - if (configurationManager().hasKeyAndValue(ConfigurationManager::KeyLogs)) { + if (configurationManager().hasKeyAndValue(KeyLogs)) { ghoul::Dictionary logs; - configurationManager().getValue(ConfigurationManager::KeyLogs, logs); + configurationManager().getValue(KeyLogs, logs); for (size_t i = 1; i <= logs.size(); ++i) { ghoul::Dictionary logInfo; diff --git a/src/interaction/interactionhandler.cpp b/src/interaction/interactionhandler.cpp index 53c50c3be1..246ab64d32 100644 --- a/src/interaction/interactionhandler.cpp +++ b/src/interaction/interactionhandler.cpp @@ -784,8 +784,8 @@ void InteractionHandler::updateCamera() { _cameraUpdatedFromScript = false; } else { - _currentInteractionMode->updateCameraStateFromMouseStates(*_camera); - if (focusNode() != nullptr) { + if (_camera && focusNode()) { + _currentInteractionMode->updateCameraStateFromMouseStates(*_camera); _camera->setFocusPositionVec3(focusNode()->worldPosition()); } } @@ -938,13 +938,66 @@ void InteractionHandler::bindKey(Key key, KeyModifier modifier, std::string lua) void InteractionHandler::writeKeyboardDocumentation(const std::string& type, const std::string& file) { if (type == "text") { - std::ofstream f(absPath(file)); + std::ofstream f; + f.exceptions(~std::ofstream::goodbit); + f.open(absPath(file)); for (const auto& p : _keyLua) { f << std::to_string(p.first) << ": " << p.second << std::endl; } } + else if (type == "html") { + std::ofstream f; + f.exceptions(~std::ofstream::goodbit); + f.open(absPath(file)); + +#ifdef JSON + std::stringstream json; + json << "["; + for (const auto& p : _keyLua) { + json << "{"; + json << "\"key\": \"" << std::to_string(p.first) << "\","; + json << "\"script\": \"" << p.second << "\","; + json << "},"; + } + json << "]"; + + std::string jsonText = json.str(); + +#else + std::stringstream html; + + html << "\n" + << "\t\n" + << "\t\tKey Bindings\n" + << "\t\n" + << "\n" + << "\n" + << "\t\n\n" + << "\t\n" + << "\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\n" + << "\t\n" + << "\t\n"; + + for (const auto& p : _keyLua) { + html << "\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\n"; + } + + html << "\t\n" + << "
Key Bindings
KeyBinding
" << std::to_string(p.first) << "" << p.second << "
\n" + << ""; + + f << html.str(); +#endif + + } else { throw ghoul::RuntimeError( "Unsupported keyboard documentation type '" + type + "'", diff --git a/src/properties/property.cpp b/src/properties/property.cpp index 3f0253b136..265789fadb 100644 --- a/src/properties/property.cpp +++ b/src/properties/property.cpp @@ -55,10 +55,8 @@ Property::Property(std::string identifier, std::string guiName) : _owner(nullptr) , _identifier(std::move(identifier)) { - if (_identifier.empty()) - LWARNING("Property identifier is empty"); - if (guiName.empty()) - LWARNING("Property GUI name is empty"); + ghoul_assert(!_identifier.empty(), "Identifier must not be empty"); + ghoul_assert(!guiName.empty(), "guiName must not be empty"); setVisible(true); _metaData.setValue(MetaDataKeyGuiName, std::move(guiName)); @@ -75,8 +73,9 @@ std::string Property::fullyQualifiedIdentifier() const { PropertyOwner* currentOwner = owner(); while (currentOwner) { std::string ownerId = currentOwner->name(); - if (!ownerId.empty()) + if (!ownerId.empty()) { identifier = ownerId + "." + identifier; + } currentOwner = currentOwner->owner(); } return identifier; @@ -162,19 +161,18 @@ void Property::onChange(std::function callback) { _onChangeCallback = std::move(callback); } -PropertyOwner* Property::owner() const -{ +PropertyOwner* Property::owner() const { return _owner; } -void Property::setPropertyOwner(PropertyOwner* owner) -{ +void Property::setPropertyOwner(PropertyOwner* owner) { _owner = owner; } void Property::notifyListener() { - if (_onChangeCallback) + if (_onChangeCallback) { _onChangeCallback(); + } } std::string Property::generateBaseDescription() const { diff --git a/src/rendering/abufferrenderer.cpp b/src/rendering/abufferrenderer.cpp index 234c2b205c..c38e3c9487 100644 --- a/src/rendering/abufferrenderer.cpp +++ b/src/rendering/abufferrenderer.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -260,7 +261,12 @@ void ABufferRenderer::render(float blackoutFactor, bool doPerformanceMeasurement glBindImageTexture(1, _fragmentTexture, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32UI); // Render the scene to the fragment buffer. Collect renderer tasks (active raycasters) - RenderData data{ *_camera, psc(), doPerformanceMeasurements }; + int renderBinMask = static_cast(Renderable::RenderBin::Background) | + static_cast(Renderable::RenderBin::Opaque) | + static_cast(Renderable::RenderBin::Transparent) | + static_cast(Renderable::RenderBin::Overlay); + + RenderData data{ *_camera, psc(), doPerformanceMeasurements, renderBinMask }; RendererTasks tasks; _scene->render(data, tasks); diff --git a/src/rendering/framebufferrenderer.cpp b/src/rendering/framebufferrenderer.cpp index bd6b920987..af1c2c23c2 100644 --- a/src/rendering/framebufferrenderer.cpp +++ b/src/rendering/framebufferrenderer.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -329,7 +330,7 @@ void FramebufferRenderer::render(float blackoutFactor, bool doPerformanceMeasure glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - RenderData data = { *_camera, psc(), doPerformanceMeasurements }; + RenderData data = { *_camera, psc(), doPerformanceMeasurements, 0 }; RendererTasks tasks; // Capture standard fbo @@ -339,7 +340,13 @@ void FramebufferRenderer::render(float blackoutFactor, bool doPerformanceMeasure glBindFramebuffer(GL_FRAMEBUFFER, _mainFramebuffer); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - // bind new fbo A with color and depth buffer. + data.renderBinMask = static_cast(Renderable::RenderBin::Background); + _scene->render(data, tasks); + data.renderBinMask = static_cast(Renderable::RenderBin::Opaque); + _scene->render(data, tasks); + data.renderBinMask = static_cast(Renderable::RenderBin::Transparent); + _scene->render(data, tasks); + data.renderBinMask = static_cast(Renderable::RenderBin::Overlay); _scene->render(data, tasks); for (const RaycasterTask& raycasterTask : tasks.raycasterTasks) { diff --git a/src/rendering/renderable.cpp b/src/rendering/renderable.cpp index b310ab3c4e..3313c432ef 100644 --- a/src/rendering/renderable.cpp +++ b/src/rendering/renderable.cpp @@ -22,14 +22,14 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -// open space includes #include #include #include #include #include -// ghoul +#include + #include #include #include @@ -44,22 +44,37 @@ namespace { namespace openspace { +Documentation Renderable::Documentation() { + using namespace openspace::documentation; + + return { + "Renderable", + "renderable", + { + { + KeyType, + new StringAnnotationVerifier("A valid Renderable created by a factory"), + "This key specifies the type of Renderable that gets created. It has to be one" + "of the valid Renderables that are available for creation (see the " + "FactoryDocumentation for a list of possible Renderables), which depends on " + "the configration of the application", + Optional::No + } + } + }; +} + Renderable* Renderable::createFromDictionary(const ghoul::Dictionary& dictionary) { // The name is passed down from the SceneGraphNode std::string name; bool success = dictionary.getValue(SceneGraphNode::KeyName, name); - assert(success); + ghoul_assert(success, "The SceneGraphNode did not set the 'name' key"); - std::string renderableType; - success = dictionary.getValue(KeyType, renderableType); + documentation::testSpecificationAndThrow(Documentation(), dictionary, "Renderable"); - if (!success) { - LERROR("Renderable '" << name << "' did not have key '" << KeyType << "'"); - return nullptr; - } + std::string renderableType = dictionary.value(KeyType); - ghoul::TemplateFactory* factory - = FactoryManager::ref().factory(); + auto factory = FactoryManager::ref().factory(); Renderable* result = factory->create(renderableType, dictionary); if (result == nullptr) { LERROR("Failed to create a Renderable object of type '" << renderableType << "'"); @@ -71,66 +86,55 @@ Renderable* Renderable::createFromDictionary(const ghoul::Dictionary& dictionary Renderable::Renderable() : _enabled("enabled", "Is Enabled", true) + , _renderBin(RenderBin::Opaque) , _startTime("") , _endTime("") , _hasTimeInterval(false) -{ - -} +{} Renderable::Renderable(const ghoul::Dictionary& dictionary) : _enabled("enabled", "Is Enabled", true) + , _renderBin(RenderBin::Opaque) , _startTime("") , _endTime("") , _hasTimeInterval(false) { setName("renderable"); -#ifndef NDEBUG - std::string name; - ghoul_assert(dictionary.getValue(SceneGraphNode::KeyName, name), - "Scenegraphnode need to specify '" << SceneGraphNode::KeyName - << "' because renderables is going to use this for debugging!"); -#endif + + ghoul_assert( + dictionary.hasKeyAndValue(SceneGraphNode::KeyName), + "SceneGraphNode must specify '" << SceneGraphNode::KeyName << "'" + ); dictionary.getValue(keyStart, _startTime); dictionary.getValue(keyEnd, _endTime); - if (_startTime != "" && _endTime != "") + if (_startTime != "" && _endTime != "") { _hasTimeInterval = true; + } addProperty(_enabled); } -Renderable::~Renderable() { +Renderable::~Renderable() {} + +void Renderable::setBoundingSphere(PowerScaledScalar boundingSphere) { + boundingSphere_ = std::move(boundingSphere); } -void Renderable::setBoundingSphere(const PowerScaledScalar& boundingSphere) -{ - boundingSphere_ = boundingSphere; -} - -const PowerScaledScalar& Renderable::getBoundingSphere() -{ +PowerScaledScalar Renderable::getBoundingSphere() { return boundingSphere_; } -void Renderable::update(const UpdateData&) -{ -} +void Renderable::update(const UpdateData&) {} -void Renderable::render(const RenderData& data, RendererTasks& tasks) -{ - (void) tasks; +void Renderable::render(const RenderData& data, RendererTasks&) { render(data); } -void Renderable::render(const RenderData& data) -{ -} +void Renderable::render(const RenderData& data) {} -void Renderable::postRender(const RenderData& data) -{ -} +void Renderable::postRender(const RenderData& data) {} void Renderable::setPscUniforms( ghoul::opengl::ProgramObject& program, @@ -143,6 +147,18 @@ void Renderable::setPscUniforms( program.setUniform("scaling", camera.scaling()); } +Renderable::RenderBin Renderable::renderBin() const { + return _renderBin; +} + +void Renderable::setRenderBin(RenderBin bin) { + _renderBin = bin; +} + +bool Renderable::matchesRenderBinMask(int binMask) { + return binMask & static_cast(renderBin()); +} + bool Renderable::isVisible() const { return _enabled; } diff --git a/src/rendering/renderengine.cpp b/src/rendering/renderengine.cpp index 5ab34ae81b..5e18d0b1e4 100644 --- a/src/rendering/renderengine.cpp +++ b/src/rendering/renderengine.cpp @@ -40,6 +40,7 @@ #include +#include #include #include #include @@ -426,9 +427,7 @@ void RenderEngine::render(const glm::mat4& projectionMatrix, const glm::mat4& vi // Print some useful information on the master viewport if (OsEng.isMaster() && OsEng.windowWrapper().isSimpleRendering()) { - if (_showInfo) { - renderInformation(); - } + renderInformation(); } glm::vec2 penPosition = glm::vec2( @@ -513,8 +512,7 @@ void RenderEngine::toggleFrametimeType(int t) { } Scene* RenderEngine::scene() { - // TODO custom assert (ticket #5) - assert(_sceneGraph); + ghoul_assert(_sceneGraph, "Scenegraph not initialized"); return _sceneGraph; } @@ -1283,7 +1281,7 @@ void RenderEngine::renderInformation() { using Font = ghoul::fontrendering::Font; using ghoul::fontrendering::RenderFont; - if (_showInfo && _fontDate && _fontInfo) { + if (_fontDate) { glm::vec2 penPosition = glm::vec2( 10.f, OsEng.windowWrapper().viewportPixelCoordinates().w @@ -1296,298 +1294,309 @@ void RenderEngine::renderInformation() { Time::ref().UTC().c_str() ); - RenderFontCr(*_fontInfo, - penPosition, - "Simulation increment (s): %.0f", - Time::ref().deltaTime() + if (_showInfo && _fontInfo) { + RenderFontCr(*_fontInfo, + penPosition, + "Simulation increment (s): %.0f", + Time::ref().deltaTime() ); - switch (_frametimeType) { - case FrametimeType::DtTimeAvg: - RenderFontCr(*_fontInfo, - penPosition, - "Avg. Frametime: %.5f", - OsEng.windowWrapper().averageDeltaTime() - ); - break; - case FrametimeType::FPS: - RenderFontCr(*_fontInfo, - penPosition, - "FPS: %3.2f", - 1.0 / OsEng.windowWrapper().deltaTime() - ); - break; - case FrametimeType::FPSAvg: - RenderFontCr(*_fontInfo, - penPosition, - "Avg. FPS: %3.2f", - 1.0 / OsEng.windowWrapper().averageDeltaTime() - ); - break; - default: - RenderFontCr(*_fontInfo, - penPosition, - "Avg. Frametime: %.5f", - OsEng.windowWrapper().averageDeltaTime() - ); - break; - } + switch (_frametimeType) { + case FrametimeType::DtTimeAvg: + RenderFontCr(*_fontInfo, + penPosition, + "Avg. Frametime: %.5f", + OsEng.windowWrapper().averageDeltaTime() + ); + break; + case FrametimeType::FPS: + RenderFontCr(*_fontInfo, + penPosition, + "FPS: %3.2f", + 1.0 / OsEng.windowWrapper().deltaTime() + ); + break; + case FrametimeType::FPSAvg: + RenderFontCr(*_fontInfo, + penPosition, + "Avg. FPS: %3.2f", + 1.0 / OsEng.windowWrapper().averageDeltaTime() + ); + break; + default: + RenderFontCr(*_fontInfo, + penPosition, + "Avg. Frametime: %.5f", + OsEng.windowWrapper().averageDeltaTime() + ); + break; + } #ifdef OPENSPACE_MODULE_NEWHORIZONS_ENABLED +//<<<<<<< HEAD bool hasNewHorizons = scene()->sceneGraphNode("NewHorizons"); double currentTime = Time::ref().j2000Seconds(); if (MissionManager::ref().hasCurrentMission()) { const Mission& mission = MissionManager::ref().currentMission(); +//======= +// bool hasNewHorizons = scene()->sceneGraphNode("NewHorizons"); +// double currentTime = Time::ref().currentTime(); +//>>>>>>> develop +// +// if (MissionManager::ref().hasCurrentMission()) { +// +// const Mission& mission = MissionManager::ref().currentMission(); - if (mission.phases().size() > 0) { + if (mission.phases().size() > 0) { - static const glm::vec4 nextMissionColor(0.7, 0.3, 0.3, 1); - //static const glm::vec4 missionProgressColor(0.4, 1.0, 1.0, 1); - static const glm::vec4 currentMissionColor(0.0, 0.5, 0.5, 1); - static const glm::vec4 missionProgressColor = currentMissionColor;// (0.4, 1.0, 1.0, 1); - static const glm::vec4 currentLeafMissionColor = missionProgressColor; - static const glm::vec4 nonCurrentMissionColor(0.3, 0.3, 0.3, 1); + static const glm::vec4 nextMissionColor(0.7, 0.3, 0.3, 1); + //static const glm::vec4 missionProgressColor(0.4, 1.0, 1.0, 1); + static const glm::vec4 currentMissionColor(0.0, 0.5, 0.5, 1); + static const glm::vec4 missionProgressColor = currentMissionColor;// (0.4, 1.0, 1.0, 1); + static const glm::vec4 currentLeafMissionColor = missionProgressColor; + static const glm::vec4 nonCurrentMissionColor(0.3, 0.3, 0.3, 1); - // Add spacing - RenderFontCr(*_fontInfo, penPosition, nonCurrentMissionColor, " "); + // Add spacing + RenderFontCr(*_fontInfo, penPosition, nonCurrentMissionColor, " "); - std::list phaseTrace = mission.phaseTrace(currentTime); + std::list phaseTrace = mission.phaseTrace(currentTime); - if (phaseTrace.size()) { - std::string title = "Current Mission Phase: " + phaseTrace.back()->name(); - RenderFontCr(*_fontInfo, penPosition, missionProgressColor, title.c_str()); - double remaining = phaseTrace.back()->timeRange().end - currentTime; - float t = static_cast(1.0 - remaining / phaseTrace.back()->timeRange().duration()); - std::string progress = progressToStr(25, t); - //RenderFontCr(*_fontInfo, penPosition, missionProgressColor, - // "%.0f s %s %.1f %%", remaining, progress.c_str(), t * 100); - } - else { - RenderFontCr(*_fontInfo, penPosition, nextMissionColor, "Next Mission:"); - double remaining = mission.timeRange().start - currentTime; - RenderFontCr(*_fontInfo, penPosition, nextMissionColor, - "%.0f s", remaining); - } - - bool showAllPhases = false; - - typedef std::pair PhaseWithDepth; - std::stack S; - int pixelIndentation = 20; - S.push({ &mission, 0 }); - while (!S.empty()) { - const MissionPhase* phase = S.top().first; - int depth = S.top().second; - S.pop(); - - bool isCurrentPhase = phase->timeRange().includes(currentTime); - - penPosition.x += depth * pixelIndentation; - if (isCurrentPhase) { - double remaining = phase->timeRange().end - currentTime; - float t = static_cast(1.0 - remaining / phase->timeRange().duration()); + if (phaseTrace.size()) { + std::string title = "Current Mission Phase: " + phaseTrace.back()->name(); + RenderFontCr(*_fontInfo, penPosition, missionProgressColor, title.c_str()); + double remaining = phaseTrace.back()->timeRange().end - currentTime; + float t = static_cast(1.0 - remaining / phaseTrace.back()->timeRange().duration()); std::string progress = progressToStr(25, t); - RenderFontCr(*_fontInfo, penPosition, currentMissionColor, - "%s %s %.1f %%", - phase->name().c_str(), - progress.c_str(), - t * 100 - ); + //RenderFontCr(*_fontInfo, penPosition, missionProgressColor, + // "%.0f s %s %.1f %%", remaining, progress.c_str(), t * 100); } else { - RenderFontCr(*_fontInfo, penPosition, nonCurrentMissionColor, phase->name().c_str()); + RenderFontCr(*_fontInfo, penPosition, nextMissionColor, "Next Mission:"); + double remaining = mission.timeRange().start - currentTime; + RenderFontCr(*_fontInfo, penPosition, nextMissionColor, + "%.0f s", remaining); } - penPosition.x -= depth * pixelIndentation; - if (isCurrentPhase || showAllPhases) { - // phases are sorted increasingly by start time, and will be popped - // last-in-first-out from the stack, so add them in reversed order. - int indexLastPhase = phase->phases().size() - 1; - for (int i = indexLastPhase; 0 <= i; --i) { - S.push({ &phase->phase(i), depth + 1 }); - } - } - } - } - } + bool showAllPhases = false; + typedef std::pair PhaseWithDepth; + std::stack S; + int pixelIndentation = 20; + S.push({ &mission, 0 }); + while (!S.empty()) { + const MissionPhase* phase = S.top().first; + int depth = S.top().second; + S.pop(); + bool isCurrentPhase = phase->timeRange().includes(currentTime); - if (openspace::ImageSequencer::ref().isReady()) { - penPosition.y -= 25.f; - - glm::vec4 targetColor(0.00, 0.75, 1.00, 1); - - if (hasNewHorizons) { - try { - double lt; - glm::dvec3 p = - SpiceManager::ref().targetPosition("PLUTO", "NEW HORIZONS", "GALACTIC", {}, currentTime, lt); - psc nhPos = PowerScaledCoordinate::CreatePowerScaledCoordinate(p.x, p.y, p.z); - float a, b, c; - glm::dvec3 radii; - SpiceManager::ref().getValue("PLUTO", "RADII", radii); - a = radii.x; - b = radii.y; - float radius = (a + b) / 2.f; - float distToSurf = glm::length(nhPos.vec3()) - radius; - - RenderFont(*_fontInfo, - penPosition, - "Distance to Pluto: % .1f (KM)", - distToSurf - ); - penPosition.y -= _fontInfo->height(); - } - catch (...) { - } - } - - double remaining = openspace::ImageSequencer::ref().getNextCaptureTime() - currentTime; - float t = static_cast(1.0 - remaining / openspace::ImageSequencer::ref().getIntervalLength()); - - std::string str = SpiceManager::ref().dateFromEphemerisTime( - ImageSequencer::ref().getNextCaptureTime(), - "YYYY MON DD HR:MN:SC" - ); - - glm::vec4 active(0.6, 1, 0.00, 1); - glm::vec4 brigther_active(0.9, 1, 0.75, 1); - - if (remaining > 0) { - - std::string progress = progressToStr(25, t); - brigther_active *= (1 - t); - - RenderFontCr(*_fontInfo, - penPosition, - active * t + brigther_active, - "Next instrument activity:" - ); - - RenderFontCr(*_fontInfo, - penPosition, - active * t + brigther_active, - "%.0f s %s %.1f %%", - remaining, progress.c_str(), t * 100 - ); - - RenderFontCr(*_fontInfo, - penPosition, - active, - "Data acquisition time: %s", - str.c_str() - ); - } - std::pair nextTarget = ImageSequencer::ref().getNextTarget(); - std::pair currentTarget = ImageSequencer::ref().getCurrentTarget(); - - if (currentTarget.first > 0.0) { - int timeleft = static_cast(nextTarget.first - currentTime); - - int hour = timeleft / 3600; - int second = timeleft % 3600; - int minute = second / 60; - second = second % 60; - - std::string hh, mm, ss; - - if (hour < 10) - hh.append("0"); - if (minute < 10) - mm.append("0"); - if (second < 10) - ss.append("0"); - - hh.append(std::to_string(hour)); - mm.append(std::to_string(minute)); - ss.append(std::to_string(second)); - - RenderFontCr(*_fontInfo, - penPosition, - targetColor, - "Data acquisition adjacency: [%s:%s:%s]", - hh.c_str(), mm.c_str(), ss.c_str() - ); - -#if 0 -// Why is it (2) in the original? ---abock - //std::pair> incidentTargets = ImageSequencer::ref().getIncidentTargetList(0); - //std::pair> incidentTargets = ImageSequencer::ref().getIncidentTargetList(2); - std::string space; - glm::vec4 color; - size_t isize = incidentTargets.second.size(); - for (size_t p = 0; p < isize; p++) { - double t = static_cast(p + 1) / static_cast(isize + 1); - t = (p > isize / 2) ? 1 - t : t; - t += 0.3; - color = (p == isize / 2) ? targetColor : glm::vec4(t, t, t, 1); - - RenderFont(*_fontInfo, - penPosition, - color, - "%s%s", - space.c_str(), incidentTargets.second[p].c_str() - ); - - - for (int k = 0; k < incidentTargets.second[p].size() + 2; k++) - space += " "; - } -#endif - penPosition.y -= _fontInfo->height(); - - std::map activeMap = ImageSequencer::ref().getActiveInstruments(); - glm::vec4 firing(0.58 - t, 1 - t, 1 - t, 1); - glm::vec4 notFiring(0.5, 0.5, 0.5, 1); - - RenderFontCr(*_fontInfo, - penPosition, - active, - "Active Instruments:" - ); - - for (auto t : activeMap) { - if (t.second == false) { - RenderFont(*_fontInfo, - penPosition, - glm::vec4(0.3, 0.3, 0.3, 1), - "| |" - ); - RenderFontCr(*_fontInfo, - penPosition, - glm::vec4(0.3, 0.3, 0.3, 1), - " %5s", - t.first.c_str() - ); - - } - else { - RenderFont(*_fontInfo, - penPosition, - glm::vec4(0.3, 0.3, 0.3, 1), - "|" - ); - if (t.first == "NH_LORRI") { - RenderFont(*_fontInfo, - penPosition, - firing, - " + " + penPosition.x += depth * pixelIndentation; + if (isCurrentPhase) { + double remaining = phase->timeRange().end - currentTime; + float t = static_cast(1.0 - remaining / phase->timeRange().duration()); + std::string progress = progressToStr(25, t); + RenderFontCr(*_fontInfo, penPosition, currentMissionColor, + "%s %s %.1f %%", + phase->name().c_str(), + progress.c_str(), + t * 100 ); } + else { + RenderFontCr(*_fontInfo, penPosition, nonCurrentMissionColor, phase->name().c_str()); + } + penPosition.x -= depth * pixelIndentation; + + if (isCurrentPhase || showAllPhases) { + // phases are sorted increasingly by start time, and will be popped + // last-in-first-out from the stack, so add them in reversed order. + int indexLastPhase = phase->phases().size() - 1; + for (int i = indexLastPhase; 0 <= i; --i) { + S.push({ &phase->phase(i), depth + 1 }); + } + } + } + } + } + + + + if (openspace::ImageSequencer::ref().isReady()) { + penPosition.y -= 25.f; + + glm::vec4 targetColor(0.00, 0.75, 1.00, 1); + + if (hasNewHorizons) { + try { + double lt; + glm::dvec3 p = + SpiceManager::ref().targetPosition("PLUTO", "NEW HORIZONS", "GALACTIC", {}, currentTime, lt); + psc nhPos = PowerScaledCoordinate::CreatePowerScaledCoordinate(p.x, p.y, p.z); + float a, b, c; + glm::dvec3 radii; + SpiceManager::ref().getValue("PLUTO", "RADII", radii); + a = radii.x; + b = radii.y; + float radius = (a + b) / 2.f; + float distToSurf = glm::length(nhPos.vec3()) - radius; + + RenderFont(*_fontInfo, + penPosition, + "Distance to Pluto: % .1f (KM)", + distToSurf + ); + penPosition.y -= _fontInfo->height(); + } + catch (...) { + } + } + + double remaining = openspace::ImageSequencer::ref().getNextCaptureTime() - currentTime; + float t = static_cast(1.0 - remaining / openspace::ImageSequencer::ref().getIntervalLength()); + + std::string str = SpiceManager::ref().dateFromEphemerisTime( + ImageSequencer::ref().getNextCaptureTime(), + "YYYY MON DD HR:MN:SC" + ); + + glm::vec4 active(0.6, 1, 0.00, 1); + glm::vec4 brigther_active(0.9, 1, 0.75, 1); + + if (remaining > 0) { + + std::string progress = progressToStr(25, t); + brigther_active *= (1 - t); + + RenderFontCr(*_fontInfo, + penPosition, + active * t + brigther_active, + "Next instrument activity:" + ); + + RenderFontCr(*_fontInfo, + penPosition, + active * t + brigther_active, + "%.0f s %s %.1f %%", + remaining, progress.c_str(), t * 100 + ); + + RenderFontCr(*_fontInfo, + penPosition, + active, + "Data acquisition time: %s", + str.c_str() + ); + } + std::pair nextTarget = ImageSequencer::ref().getNextTarget(); + std::pair currentTarget = ImageSequencer::ref().getCurrentTarget(); + + if (currentTarget.first > 0.0) { + int timeleft = static_cast(nextTarget.first - currentTime); + + int hour = timeleft / 3600; + int second = timeleft % 3600; + int minute = second / 60; + second = second % 60; + + std::string hh, mm, ss; + + if (hour < 10) + hh.append("0"); + if (minute < 10) + mm.append("0"); + if (second < 10) + ss.append("0"); + + hh.append(std::to_string(hour)); + mm.append(std::to_string(minute)); + ss.append(std::to_string(second)); + + RenderFontCr(*_fontInfo, + penPosition, + targetColor, + "Data acquisition adjacency: [%s:%s:%s]", + hh.c_str(), mm.c_str(), ss.c_str() + ); + + #if 0 + // Why is it (2) in the original? ---abock + //std::pair> incidentTargets = ImageSequencer::ref().getIncidentTargetList(0); + //std::pair> incidentTargets = ImageSequencer::ref().getIncidentTargetList(2); + std::string space; + glm::vec4 color; + size_t isize = incidentTargets.second.size(); + for (size_t p = 0; p < isize; p++) { + double t = static_cast(p + 1) / static_cast(isize + 1); + t = (p > isize / 2) ? 1 - t : t; + t += 0.3; + color = (p == isize / 2) ? targetColor : glm::vec4(t, t, t, 1); + RenderFont(*_fontInfo, penPosition, - glm::vec4(0.3, 0.3, 0.3, 1), - " |" - ); - RenderFontCr(*_fontInfo, - penPosition, - active, - " %5s", - t.first.c_str() + color, + "%s%s", + space.c_str(), incidentTargets.second[p].c_str() ); + + + for (int k = 0; k < incidentTargets.second[p].size() + 2; k++) + space += " "; + } + #endif + penPosition.y -= _fontInfo->height(); + + std::map activeMap = ImageSequencer::ref().getActiveInstruments(); + glm::vec4 firing(0.58 - t, 1 - t, 1 - t, 1); + glm::vec4 notFiring(0.5, 0.5, 0.5, 1); + + RenderFontCr(*_fontInfo, + penPosition, + active, + "Active Instruments:" + ); + + for (auto t : activeMap) { + if (t.second == false) { + RenderFont(*_fontInfo, + penPosition, + glm::vec4(0.3, 0.3, 0.3, 1), + "| |" + ); + RenderFontCr(*_fontInfo, + penPosition, + glm::vec4(0.3, 0.3, 0.3, 1), + " %5s", + t.first.c_str() + ); + + } + else { + RenderFont(*_fontInfo, + penPosition, + glm::vec4(0.3, 0.3, 0.3, 1), + "|" + ); + if (t.first == "NH_LORRI") { + RenderFont(*_fontInfo, + penPosition, + firing, + " + " + ); + } + RenderFont(*_fontInfo, + penPosition, + glm::vec4(0.3, 0.3, 0.3, 1), + " |" + ); + RenderFontCr(*_fontInfo, + penPosition, + active, + " %5s", + t.first.c_str() + ); + } } } } diff --git a/src/rendering/screenspacerenderable.cpp b/src/rendering/screenspacerenderable.cpp index dc047dd558..c95734a2b9 100644 --- a/src/rendering/screenspacerenderable.cpp +++ b/src/rendering/screenspacerenderable.cpp @@ -30,6 +30,8 @@ #include #include +#include + #ifdef WIN32 #define _USE_MATH_DEFINES #include @@ -50,16 +52,36 @@ namespace { namespace openspace { +Documentation ScreenSpaceRenderable::Documentation() { + using namespace openspace::documentation; + + return { + "Screenspace Renderable", + "core_screenspacerenderable", + { + { + KeyType, + new StringAnnotationVerifier("Must name a valid Screenspace renderable"), + "The type of the Screenspace renderable that is to be created. The " + "available types of Screenspace renderable depend on the configuration of" + "the application and can be written to disk on application startup into " + "the FactoryDocumentation.", + Optional::No + } + } + }; +} + ScreenSpaceRenderable* ScreenSpaceRenderable::createFromDictionary( const ghoul::Dictionary& dictionary) { - std::string renderableType; - bool success = dictionary.getValue(KeyType, renderableType); + documentation::testSpecificationAndThrow( + Documentation(), + dictionary, + "ScreenSpaceRenderable" + ); - if (!success) { - LERROR("ScreenSpaceRenderable did not have key '" << KeyType << "'"); - return nullptr; - } + std::string renderableType = dictionary.value(KeyType); auto factory = FactoryManager::ref().factory(); ScreenSpaceRenderable* result = factory->create(renderableType, dictionary); diff --git a/src/scene/ephemeris.cpp b/src/scene/ephemeris.cpp index f7088b3234..7bba161310 100644 --- a/src/scene/ephemeris.cpp +++ b/src/scene/ephemeris.cpp @@ -26,6 +26,8 @@ #include #include +#include + namespace { const std::string _loggerCat = "Ephemeris"; const std::string KeyType = "Type"; @@ -33,6 +35,26 @@ namespace { namespace openspace { +Documentation Ephemeris::Documentation() { + using namespace openspace::documentation; + + return{ + "Transformation Translation", + "core_transform_translation", + { + { + KeyType, + new StringAnnotationVerifier("Must name a valid Translation type"), + "The type of translation that is described in this element. " + "The available types of translations depend on the " + "configuration of the application and can be written to disk " + "on application startup into the FactoryDocumentation.", + Optional::No + } + } + }; +} + Ephemeris* Ephemeris::createFromDictionary(const ghoul::Dictionary& dictionary) { if (!dictionary.hasValue(KeyType)) { LERROR("Ephemeris did not have key '" << KeyType << "'"); diff --git a/src/scene/rotation.cpp b/src/scene/rotation.cpp index 74a026fe3f..a4d77d54ff 100644 --- a/src/scene/rotation.cpp +++ b/src/scene/rotation.cpp @@ -26,6 +26,8 @@ #include #include +#include + namespace { const std::string _loggerCat = "Rotation"; const std::string KeyType = "Type"; @@ -33,16 +35,31 @@ namespace { namespace openspace { -Rotation* Rotation::createFromDictionary(const ghoul::Dictionary& dictionary) { - if (!dictionary.hasValue(KeyType)) { - LERROR("Ephemeris did not have key '" << KeyType << "'"); - return nullptr; - } +Documentation Rotation::Documentation() { + using namespace openspace::documentation; - std::string rotationType; - dictionary.getValue(KeyType, rotationType); - ghoul::TemplateFactory* factory - = FactoryManager::ref().factory(); + return { + "Transformation Rotation", + "core_transform_rotation", + { + { + KeyType, + new StringAnnotationVerifier("Must name a valid Rotation type."), + "The type of the rotation that is described in this element. The " + "available types of rotations depend on the configuration of the " + "application and can be written to disk on application startup into the " + "FactoryDocumentation.", + Optional::No + } + } + }; +} + +Rotation* Rotation::createFromDictionary(const ghoul::Dictionary& dictionary) { + documentation::testSpecificationAndThrow(Documentation(), dictionary, "Rotation"); + + std::string rotationType = dictionary.value(KeyType); + auto factory = FactoryManager::ref().factory(); Rotation* result = factory->create(rotationType, dictionary); if (result == nullptr) { LERROR("Failed creating Rotation object of type '" << rotationType << "'"); diff --git a/src/scene/scale.cpp b/src/scene/scale.cpp index 09812c9b3e..22bd2d7cbe 100644 --- a/src/scene/scale.cpp +++ b/src/scene/scale.cpp @@ -26,6 +26,8 @@ #include #include +#include + namespace { const std::string _loggerCat = "Scale"; const std::string KeyType = "Type"; @@ -33,16 +35,33 @@ namespace { namespace openspace { -Scale* Scale::createFromDictionary(const ghoul::Dictionary& dictionary) { - if (!dictionary.hasValue(KeyType)) { - LERROR("Ephemeris did not have key '" << KeyType << "'"); - return nullptr; - } +Documentation Scale::Documentation() { + using namespace openspace::documentation; - std::string scaleType; - dictionary.getValue(KeyType, scaleType); - ghoul::TemplateFactory* factory - = FactoryManager::ref().factory(); + return { + "Transformation Scaling", + "core_transform_scaling", + { + { + KeyType, + new StringAnnotationVerifier("Must name a valid Scale type"), + "The type of the scaling that is described in this element. " + "The available types of scaling depend on the configuration " + "of the application and can be written to disk on " + "application startup into the FactoryDocumentation.", + Optional::No + } + } + }; +} + + +Scale* Scale::createFromDictionary(const ghoul::Dictionary& dictionary) { + documentation::testSpecificationAndThrow(Documentation(), dictionary, "Scale"); + + std::string scaleType = dictionary.value(KeyType); + + auto factory = FactoryManager::ref().factory(); Scale* result = factory->create(scaleType, dictionary); if (result == nullptr) { LERROR("Failed creating Scale object of type '" << scaleType << "'"); diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index 428ec1c9f9..f016dd2159 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -55,12 +55,12 @@ #include #endif +#include "scene_doc.inl" #include "scene_lua.inl" namespace { const std::string _loggerCat = "Scene"; const std::string _moduleExtension = ".mod"; - const std::string _defaultCommonDirectory = "common"; const std::string _commonModuleToken = "${COMMON_MODULE}"; const std::string KeyCamera = "Camera"; @@ -98,15 +98,23 @@ void Scene::update(const UpdateData& data) { OsEng.interactionHandler().setInteractionMode("Orbital"); // After loading the scene, the keyboard bindings have been set - + const std::string KeyboardShortcutsType = + ConfigurationManager::KeyKeyboardShortcuts + "." + + ConfigurationManager::PartType; + + const std::string KeyboardShortcutsFile = + ConfigurationManager::KeyKeyboardShortcuts + "." + + ConfigurationManager::PartFile; + + std::string type; std::string file; bool hasType = OsEng.configurationManager().getValue( - ConfigurationManager::KeyKeyboardShortcutsType, type + KeyboardShortcutsType, type ); bool hasFile = OsEng.configurationManager().getValue( - ConfigurationManager::KeyKeyboardShortcutsFile, file + KeyboardShortcutsFile, file ); if (hasType && hasFile) { @@ -189,6 +197,14 @@ bool Scene::loadSceneInternal(const std::string& sceneDescriptionFilePath) { state ); + // Perform testing against the documentation/specification + openspace::documentation::testSpecificationAndThrow( + Scene::Documentation(), + dictionary, + "Scene" + ); + + _graph.loadFromFile(sceneDescriptionFilePath); // Initialize all nodes @@ -230,13 +246,21 @@ bool Scene::loadSceneInternal(const std::string& sceneDescriptionFilePath) { } // If a PropertyDocumentationFile was specified, generate it now - const bool hasType = OsEng.configurationManager().hasKey(ConfigurationManager::KeyPropertyDocumentationType); - const bool hasFile = OsEng.configurationManager().hasKey(ConfigurationManager::KeyPropertyDocumentationFile); + const std::string KeyPropertyDocumentationType = + ConfigurationManager::KeyPropertyDocumentation + '.' + + ConfigurationManager::PartType; + + const std::string KeyPropertyDocumentationFile = + ConfigurationManager::KeyPropertyDocumentation + '.' + + ConfigurationManager::PartFile; + + const bool hasType = OsEng.configurationManager().hasKey(KeyPropertyDocumentationType); + const bool hasFile = OsEng.configurationManager().hasKey(KeyPropertyDocumentationFile); if (hasType && hasFile) { std::string propertyDocumentationType; - OsEng.configurationManager().getValue(ConfigurationManager::KeyPropertyDocumentationType, propertyDocumentationType); + OsEng.configurationManager().getValue(KeyPropertyDocumentationType, propertyDocumentationType); std::string propertyDocumentationFile; - OsEng.configurationManager().getValue(ConfigurationManager::KeyPropertyDocumentationFile, propertyDocumentationFile); + OsEng.configurationManager().getValue(KeyPropertyDocumentationFile, propertyDocumentationFile); propertyDocumentationFile = absPath(propertyDocumentationFile); writePropertyDocumentation(propertyDocumentationFile, propertyDocumentationType); @@ -414,13 +438,11 @@ SceneGraph& Scene::sceneGraph() { } void Scene::writePropertyDocumentation(const std::string& filename, const std::string& type) { + LDEBUG("Writing documentation for properties"); if (type == "text") { - LDEBUG("Writing documentation for properties"); - std::ofstream file(filename); - if (!file.good()) { - LERROR("Could not open file '" << filename << "' for writing property documentation"); - return; - } + std::ofstream file; + file.exceptions(~std::ofstream::goodbit); + file.open(filename); using properties::Property; for (SceneGraphNode* node : _graph.nodes()) { @@ -429,13 +451,100 @@ void Scene::writePropertyDocumentation(const std::string& filename, const std::s file << node->name() << std::endl; for (Property* p : properties) { - file << p->fullyQualifiedIdentifier() << ": " << p->guiName() << std::endl; + file << p->fullyQualifiedIdentifier() << ": " << + p->guiName() << std::endl; } file << std::endl; } } } + else if (type == "html") { + std::ofstream file; + file.exceptions(~std::ofstream::goodbit); + file.open(filename); + +#ifdef JSON + // Create JSON + std::function createJson = + [&createJson](properties::PropertyOwner* owner) -> std::string + { + std::stringstream json; + json << "{"; + json << "\"name\": \"" << owner->name() << "\","; + + json << "\"properties\": ["; + for (properties::Property* p : owner->properties()) { + json << "{"; + json << "\"id\": \"" << p->identifier() << "\","; + json << "\"type\": \"" << p->className() << "\","; + json << "\"fullyQualifiedId\": \"" << p->fullyQualifiedIdentifier() << "\","; + json << "\"guiName\": \"" << p->guiName() << "\","; + json << "},"; + } + json << "],"; + + json << "\"propertyOwner\": ["; + for (properties::PropertyOwner* o : owner->propertySubOwners()) { + json << createJson(o); + } + json << "],"; + json << "},"; + + return json.str(); + }; + + + std::stringstream json; + json << "["; + for (SceneGraphNode* node : _graph.nodes()) { + json << createJson(node); + } + + json << "]"; + + std::string jsonText = json.str(); +#else + std::stringstream html; + html << "\n" + << "\t\n" + << "\t\tProperties\n" + << "\t\n" + << "\n" + << "\n" + << "\t\n\n" + << "\t\n" + << "\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\n" + << "\t\n" + << "\t\n"; + + for (SceneGraphNode* node : _graph.nodes()) { + for (properties::Property* p : node->propertiesRecursive()) { + html << "\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\n"; + } + + if (!node->propertiesRecursive().empty()) { + html << "\t\n"; + } + + } + + html << "\t\n" + << "
Properties
IDTypeDescription
" << p->fullyQualifiedIdentifier() << "" << p->className() << "" << p->guiName() << "
\n" + << ";"; + + file << html.str(); +#endif + + } else LERROR("Undefined type '" << type << "' for Property documentation"); } @@ -455,6 +564,7 @@ scripting::LuaLibrary Scene::luaLibrary() { { "setPropertyValueRegex", &luascriptfunctions::property_setValueRegex, + "string, *", "Sets all properties that pass the regular expression in the first " "argument. The second argument can be any type, but it has to match the " "type of the properties that matched the regular expression. The regular " diff --git a/src/scene/scene_doc.inl b/src/scene/scene_doc.inl new file mode 100644 index 0000000000..9b069572c1 --- /dev/null +++ b/src/scene/scene_doc.inl @@ -0,0 +1,95 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +namespace openspace { + +Documentation Scene::Documentation() { + using namespace documentation; + + return { + "Scene Description", + { + { + "ScenePath", + new StringVerifier, + "The path to the base directory of the scene. The path is considered " + "relative to the location of the scene description file.", + Optional::Yes + }, + { + "CommonFolder", + new StringAnnotationVerifier("A valid scene module folder"), + "The path to the common folder that is loaded and will be bound to the " + "${COMMON_MODULE} path token so that assets can be reused easily.", + Optional::Yes + }, + { + "Camera", + new TableVerifier({ + { + "Focus", + new StringAnnotationVerifier("A valid object in the scene"), + "The initial focus node of the camera, i.e., the node around which " + "the interaction will be performed." + }, + { + "Position", + new DoubleVector3Verifier, + "The initial camera positive relative to the focus object.", + Optional::Yes + }, + { + "Rotation", + new DoubleVector4Verifier, + "The initial camera rotation expressed as a quaternion.", + Optional::Yes + } + }), + "Definitions of the camera starting parameters, such as focus, location, and " + "orientation.", + Optional::Yes + }, + { + "Modules", + new TableVerifier({ + { "*", new StringAnnotationVerifier( + "Loadable module folders. This means that they either have to point " + "to a folder that contains a ModuleFile or a folder which contains " + "other folders that eventually contain ModuleFile. This second " + "recursive approach is useful for grouping modules into logical " + "units." + )} + }), + "This is the list of modules that will be loaded into the initial scene. The " + "values in this table have to correspond to folders relative to the " + "ScenePath key. The order in which the modules are loaded is the same as the " + "order in which they are specified in this table." + } + } + }; +} + +} // namespace openspace diff --git a/src/scene/scenegraphnode.cpp b/src/scene/scenegraphnode.cpp index 6256c4fc7a..e3334e178d 100644 --- a/src/scene/scenegraphnode.cpp +++ b/src/scene/scenegraphnode.cpp @@ -24,6 +24,9 @@ // open space includes #include + +#include + #include #include #include @@ -46,6 +49,8 @@ #include #include +#include "scenegraphnode_doc.inl" + namespace { const std::string _loggerCat = "SceneGraphNode"; const std::string KeyRenderable = "Renderable"; @@ -63,8 +68,13 @@ const std::string SceneGraphNode::KeyName = "Name"; const std::string SceneGraphNode::KeyParentName = "Parent"; const std::string SceneGraphNode::KeyDependencies = "Dependencies"; -SceneGraphNode* SceneGraphNode::createFromDictionary(const ghoul::Dictionary& dictionary) -{ +SceneGraphNode* SceneGraphNode::createFromDictionary(const ghoul::Dictionary& dictionary){ + openspace::documentation::testSpecificationAndThrow( + SceneGraphNode::Documentation(), + dictionary, + "SceneGraphNode" + ); + SceneGraphNode* result = new SceneGraphNode; if (!dictionary.hasValue(KeyName)) { @@ -150,9 +160,6 @@ SceneGraphNode* SceneGraphNode::createFromDictionary(const ghoul::Dictionary& di //parentNode->addNode(result); - result->_scale->setName("Transform"); - result->addPropertySubOwner(result->_scale); - LDEBUG("Successfully created SceneGraphNode '" << result->name() << "'"); return result; @@ -346,12 +353,20 @@ void SceneGraphNode::render(const RenderData& data, RendererTasks& tasks) { data.camera, thisPositionPSC, data.doPerformanceMeasurement, + data.renderBinMask, _worldPositionCached, _worldRotationCached, _worldScaleCached}; _performanceRecord.renderTime = 0; - if (_renderableVisible && _renderable->isVisible() && _renderable->isReady() && _renderable->isEnabled()) { + + bool visible = _renderableVisible && + _renderable->isVisible() && + _renderable->isReady() && + _renderable->isEnabled() && + _renderable->matchesRenderBinMask(data.renderBinMask); + + if (visible) { if (data.doPerformanceMeasurement) { glFinish(); auto start = std::chrono::high_resolution_clock::now(); @@ -374,7 +389,7 @@ void SceneGraphNode::render(const RenderData& data, RendererTasks& tasks) { void SceneGraphNode::postRender(const RenderData& data) { const psc thisPosition = psc::CreatePowerScaledCoordinate(_worldPositionCached.x, _worldPositionCached.y, _worldPositionCached.z); - RenderData newData = { data.camera, thisPosition, data.doPerformanceMeasurement, _worldPositionCached}; + RenderData newData = { data.camera, thisPosition, data.doPerformanceMeasurement, data.renderBinMask, _worldPositionCached}; _performanceRecord.renderTime = 0; if (_renderableVisible && _renderable->isVisible() && _renderable->isReady() && _renderable->isEnabled()) { @@ -587,7 +602,7 @@ SceneGraphNode* SceneGraphNode::childNode(const std::string& name) void SceneGraphNode::updateCamera(Camera* camera) const{ - psc origin = psc(worldPosition()); + psc origin(worldPosition()); //int i = 0; // the camera position diff --git a/src/scene/scenegraphnode_doc.inl b/src/scene/scenegraphnode_doc.inl new file mode 100644 index 0000000000..5a491fb139 --- /dev/null +++ b/src/scene/scenegraphnode_doc.inl @@ -0,0 +1,102 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +namespace openspace { + +Documentation SceneGraphNode::Documentation() { + using namespace documentation; + + return { + "Scenegraph Node", + { + { + "Name", + new StringVerifier, + "The name of this scenegraph node. This name must be unique among all scene " + "graph nodes that are loaded in a specific scene. If a duplicate is detected " + "the loading of the node will fail, as will all childing that depend on the " + "node." + }, + { + "Parent", + new StringAnnotationVerifier( + "Must be a name for another scenegraph node, or 'Root'" + ), + "This names the parent of the currently specified scenegraph node. The " + "parent must not have been defined earlier, but must exist at loading time, " + "or the scenegraph node creation will fail. A special parent 'Root' is " + "available that denotes the root of the scenegraph." + }, + { + "Renderable", + new ReferencingVerifier("renderable"), + "The renderable that is to be created for this scenegraph node. A renderable " + "is a component of a scenegraph node that will lead to some visual result on " + "the screen. The specifics heavily depend on the 'Type' of the renderable. " + "If no Renderable is specified, this scenegraph node is an internal node and " + "can be used for either group children, or apply common transformations to a " + "group of children.", + Optional::Yes + }, + { + "Transform", + new TableVerifier({ + { + "Translation", + new ReferencingVerifier("core_transform_translation"), + "This node describes a translation that is applied to the scenegraph " + "node and all its children. Depending on the 'Type' of the " + "translation, this can either be a static translation or a " + "time-varying one.", + Optional::Yes + }, + { + "Rotation", + new ReferencingVerifier("core_transform_rotation"), + "This nodes describes a rotation that is applied to the scenegraph " + "node and all its children. Depending on the 'Type' of the rotation, " + "this can either be a static rotation or a time-varying one.", + Optional::Yes + }, + { + "Scale", + new ReferencingVerifier("core_transform_scaling"), + "This node describes a scaling that is applied to the scenegraph " + "node and all its children. Depending on the 'Type' of the scaling, " + "this can either be a static scaling or a time-varying one.", + Optional::Yes + } + }), + "This describes a set of transformations that are applied to this scenegraph " + "node and all of its children. There are only three possible values " + "corresponding to a 'Translation', a 'Rotation', and a 'Scale'.", + Optional::Yes + }, + } + }; +} + +} // namespace openspace diff --git a/src/scripting/scriptengine.cpp b/src/scripting/scriptengine.cpp index 680ed308a8..7ac37b5eb3 100644 --- a/src/scripting/scriptengine.cpp +++ b/src/scripting/scriptengine.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -51,7 +52,6 @@ namespace { //const lua_CFunction _printFunctionReplacement = luascriptfunctions::printInfo; const int _setTableOffset = -3; // -1 (top) -1 (first argument) -1 (second argument) - } void ScriptEngine::initialize() { @@ -487,41 +487,36 @@ std::vector ScriptEngine::allLuaFunctions() const { return result; } -bool ScriptEngine::writeDocumentation(const std::string& filename, const std::string& type) const { - if (type == "text") { - // The additional space between the longest function name and the descriptions - LDEBUG("Writing Lua documentation of type '" << type << - "' to file '" << filename << "'"); - std::ofstream file(filename); - if (!file.good()) { - LERROR("Could not open file '" << filename << "' for writing documentation"); - return false; +void ScriptEngine::writeDocumentation(const std::string& filename, const std::string& type) const { + auto concatenate = [](std::string library, std::string function) { + std::string total = "openspace."; + if (!library.empty()) { + total += std::move(library) + "."; } + total += std::move(function); + return total; + }; - auto concatenate = [](std::string library, std::string function) { - std::string total = "openspace."; - if (!library.empty()) - total += std::move(library) + "."; - total += std::move(function); - return total; - }; - + LDEBUG("Writing Lua documentation of type '" << type << + "' to file '" << filename << "'"); + if (type == "text") { // Settings const unsigned int lineWidth = 80; static const std::string whitespace = " \t"; static const std::string padding = " "; - const bool commandListArguments = true; + + // The additional space between the longest function name and the descriptions + + std::ofstream file; + file.exceptions(~std::ofstream::goodbit); + file.open(filename); file << "Available commands:\n"; // Now write out the functions - for (const LuaLibrary& library : _registeredLibraries) { - for (const LuaLibrary::Function& function : library.functions) { - - std::string functionName = concatenate(library.name, function.name); - file << padding << functionName; - if (commandListArguments) - file << "(" << function.argumentText << ")"; - file << std::endl; + for (const LuaLibrary& l : _registeredLibraries) { + for (const LuaLibrary::Function& f : l.functions) { + std::string name = concatenate(l.name, f.name); + file << padding << name << "(" << f.argumentText << ")" << std::endl; } } file << std::endl; @@ -529,24 +524,32 @@ bool ScriptEngine::writeDocumentation(const std::string& filename, const std::st // Now write out the functions definitions for (const LuaLibrary& library : _registeredLibraries) { for (const LuaLibrary::Function& function : library.functions) { - - std::string functionName = concatenate(library.name, function.name); - file << functionName << "(" << function.argumentText << "):" << std::endl; + std::string name = concatenate(library.name, function.name); + file << name << "(" << function.argumentText << "):" << std::endl; std::string remainingHelptext = function.helpText; - - // @CLEANUP This needs to become a bit prettier ---abock while (!remainingHelptext.empty()) { - const auto length = remainingHelptext.length(); - const auto paddingLength = padding.length(); + const size_t length = remainingHelptext.length(); + const size_t paddingLength = padding.length(); + if ((length + paddingLength) > lineWidth) { - auto lastSpace = remainingHelptext.find_last_of(whitespace, lineWidth - 1 - paddingLength); - if (lastSpace == remainingHelptext.npos) + size_t lastSpace = remainingHelptext.find_last_of( + whitespace, + lineWidth - 1 - paddingLength + ); + if (lastSpace == remainingHelptext.npos) { lastSpace = lineWidth; - file << padding << remainingHelptext.substr(0, lastSpace) << std::endl; - auto firstNotSpace = remainingHelptext.find_first_not_of(whitespace, lastSpace); - if (firstNotSpace == remainingHelptext.npos) + } + + file << padding << remainingHelptext.substr(0, lastSpace) << '\n'; + + size_t firstNotSpace = remainingHelptext.find_first_not_of( + whitespace, + lastSpace + ); + if (firstNotSpace == remainingHelptext.npos) { firstNotSpace = lastSpace; + } remainingHelptext = remainingHelptext.substr(firstNotSpace); } else { @@ -557,27 +560,115 @@ bool ScriptEngine::writeDocumentation(const std::string& filename, const std::st file << std::endl; } } - return true; + } + else if (type == "html") { + std::ofstream file; + file.exceptions(~std::ofstream::goodbit); + file.open(filename); + +#ifdef JSON + // Create JSON + std::stringstream json; + json << "["; + + for (const LuaLibrary& l : _registeredLibraries) { + json << "{"; + + json << "\"library\": \"" << l.name << "\","; + json << "\"functions\": ["; + + for (const LuaLibrary::Function& f : l.functions) { + json << "{"; + json << "\"name\": \"" << f.name << "\", "; + json << "\"arguments\": \"" << f.argumentText << "\", "; + json << "\"help\": \"" << f.helpText << "\""; + json << "},"; + } + json << "]},"; + } + json << "]"; + + + std::string jsonText = json.str(); +#else + std::stringstream html; + + html << "\n" + << "\t\n" + << "\t\tScript Log\n" + << "\t\n" + << "\n" + << "\n" + << "\t\n\n" + << "\t\n" + << "\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\n" + << "\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\n" + << "\t\n" + << "\t\n"; + + + + for (const LuaLibrary& l : _registeredLibraries) { + html << "\t\n"; + + if (l.name.empty()) { + html << "\t\t\n"; + } + else { + html << "\t\t\n"; + } + html << "\t\t\n" + << "\t\"; + + for (const LuaLibrary::Function& f : l.functions) { + html << "\t\n" + << "\t\t\n" + << "\t\t\n" + << "\t\t\n" + << "\t\t\n" + << "\t\n"; + } + + html << "\t\n"; + } + + html << "\t\n" + << "
Script Log
LibraryFunctions
NameArgumentsHelp
openspaceopenspace." << l.name << "
" << f.name << "" << f.argumentText << "" << f.helpText << "
\n" + << ""; + + file << html.str(); +#endif } else { - LERROR("Undefined type '" << type << "' for Lua documentation"); - return false; + throw ghoul::RuntimeError("Undefined type '" + type + "' for Lua documentation"); } } bool ScriptEngine::writeLog(const std::string& script) { + const std::string KeyScriptLogType = + ConfigurationManager::KeyScriptLog + '.' + ConfigurationManager::PartType; + const std::string KeyScriptLogFile = + ConfigurationManager::KeyScriptLog + '.' + ConfigurationManager::PartFile; + // Check that logging is enabled and initialize if necessary if (!_logFileExists) { // If a ScriptLogFile was specified, generate it now const bool hasType = OsEng.configurationManager() - .hasKey(ConfigurationManager::KeyScriptLogType); + .hasKey(KeyScriptLogType); const bool hasFile = OsEng.configurationManager() - .hasKey(ConfigurationManager::KeyScriptLogFile); + .hasKey(KeyScriptLogFile); if (hasType && hasFile) { OsEng.configurationManager() - .getValue(ConfigurationManager::KeyScriptLogType, _logType); + .getValue(KeyScriptLogType, _logType); OsEng.configurationManager() - .getValue(ConfigurationManager::KeyScriptLogFile, _logFilename); + .getValue(KeyScriptLogFile, _logFilename); _logFilename = absPath(_logFilename); _logFileExists = true; @@ -596,8 +687,8 @@ bool ScriptEngine::writeLog(const std::string& script) { } } else { LDEBUG("No script log specified in 'openspace.cfg.' To log, set '" - << ConfigurationManager::KeyScriptLogType << " and " - << ConfigurationManager::KeyScriptLogFile + << KeyScriptLogType << " and " + << KeyScriptLogFile << " in configuration table."); _logScripts = false; return false; diff --git a/src/util/factorymanager.cpp b/src/util/factorymanager.cpp index 5af4dab379..dd0f60cdce 100644 --- a/src/util/factorymanager.cpp +++ b/src/util/factorymanager.cpp @@ -26,6 +26,8 @@ #include +#include + namespace openspace { FactoryManager* FactoryManager::_manager = nullptr; @@ -57,9 +59,94 @@ FactoryManager& FactoryManager::ref() { return *_manager; } -void FactoryManager::addFactory(std::unique_ptr factory) { +void FactoryManager::addFactory(std::unique_ptr factory, + std::string name +) { ghoul_assert(factory, "Factory must not be nullptr"); - _factories.push_back(std::move(factory)); + _factories.push_back({ std::move(factory), std::move(name) }); +} + +void FactoryManager::writeDocumentation(const std::string& file, const std::string& type) { + if (type == "text") { + std::ofstream f; + f.exceptions(~std::ofstream::goodbit); + f.open(file); + + for (const FactoryInfo& factoryInfo : _factories) { + f << factoryInfo.name << '\n'; + + ghoul::TemplateFactoryBase* factory = factoryInfo.factory.get(); + for (const std::string& c : factory->registeredClasses()) { + f << '\t' << c << '\n'; + } + f << "\n\n"; + } + + } + else if (type == "html") { + +#ifdef JSON + std::stringstream json; + + json << "["; + for (const FactoryInfo& factoryInfo : _factories) { + json << "{"; + json << "\"name\": \"" << factoryInfo.name << "\","; + json << "\"classes\": ["; + + ghoul::TemplateFactoryBase* factory = factoryInfo.factory.get(); + for (const std::string& c : factory->registeredClasses()) { + json << "\"" << c << "\","; + } + + json << "]},"; + } + + json << "]"; + + // I did not check the output of this for correctness ---abock + std::string jsonText = json.str(); +#else + std::ofstream f; + f.exceptions(~std::ofstream::goodbit); + f.open(file); + + std::stringstream html; + html << "\n" + << "\t\n" + << "\t\tFactories\n" + << "\t\n" + << "\n" + << "\n" + << "\t\n\n" + << "\t\n" + << "\t\t\n" + << "\t\t\t\n" + << "\t\t\t\n" + << "\t\t\n" + << "\t\n" + << "\t\n"; + + for (const FactoryInfo& factoryInfo : _factories) { + html << "\t\t\n" + << "\t\t\t\n"; + + ghoul::TemplateFactoryBase* factory = factoryInfo.factory.get(); + for (const std::string& c : factory->registeredClasses()) { + html << "\t\t\t\n"; + } + html << "\t\n"; + + } + + html << "\t\n" + << "
Factories
TypeObject
" << factoryInfo.name << "
" << c << "
\n" + << ";"; + + f << html.str(); +#endif + + } } } // namespace openspace diff --git a/src/util/openspacemodule.cpp b/src/util/openspacemodule.cpp index 3c71ffaf67..dc61f1c014 100644 --- a/src/util/openspacemodule.cpp +++ b/src/util/openspacemodule.cpp @@ -63,6 +63,10 @@ void OpenSpaceModule::deinitialize() { internalDeinitialize(); } +std::vector OpenSpaceModule::documentations() const { + return {}; +} + std::string OpenSpaceModule::modulePath() const { std::string moduleName = name(); std::transform(moduleName.begin(), moduleName.end(), moduleName.begin(), tolower); diff --git a/support/server/request.wsgi b/support/server/request.wsgi new file mode 100644 index 0000000000..abefb3d51d --- /dev/null +++ b/support/server/request.wsgi @@ -0,0 +1,53 @@ +import os.path +import imp +from cgi import parse_qs, escape + +responseHeader = [('Content-Type', 'text/plain')] + +baseUrl = %REQUIRED% URL TO THE BASE DIRECTORY OF FILES +baseFile = %REQUIRED% LOCAL PATH TO THE BASE DIRECTORY OF FILES +baseRequest = %REQUIRED% LOCAL PATH TO THE DIRECTORY OF REQUESTS + +def find_file(identifier, file_version, application_version): + file = identifier + "_v" + application_version + "_v" + file_version + + if os.path.isfile(baseFile + file): + # If the direct file exists, it lists all necessary files line by line + with open(baseFile + file, "r") as f: + return True, f.read() + elif os.path.isfile(baseRequest + file + '.py'): + # If the direct file doesn't exist, we look for a python script that + # will generate the returns string dynamically + module = imp.load_source('files', baseRequest + file + '.py') + return True, module.files() + else: + # If neither exists, an unknown file was requested + return False, "Could not find identifier '" + file + "'" + +def application(environ, start_response): + parameters = parse_qs(environ.get('QUERY_STRING', '')) + + if 'identifier' in parameters: + file = escape(parameters['identifier'][0]) + else: + start_response('400 Bad Request', responseHeader) + return ['No identifier provided'] + + if 'file_version' in parameters: + file_version = escape(parameters['file_version'][0]) + else: + file_version = "1" + + if 'application_version' in parameters: + application_version = escape(parameters['application_version'][0]) + else: + application_version = "1" + + status_ok, url = find_file(file, file_version, application_version) + + if (status_ok): + start_response('200 OK', responseHeader) + else: + start_response('400 Bad Request', responseHeader) + + return [url] \ No newline at end of file diff --git a/tests/main.cpp b/tests/main.cpp index 0cbe2b6040..354d923c9c 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -58,6 +58,8 @@ //#include #endif +#include + #include #include #include diff --git a/tests/test_documentation.inl b/tests/test_documentation.inl new file mode 100644 index 0000000000..35b6bca709 --- /dev/null +++ b/tests/test_documentation.inl @@ -0,0 +1,2432 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2016 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include "gtest/gtest.h" + +#include +#include +#include + +#include + +#include + +class DocumentationTest : public testing::Test {}; + +TEST_F(DocumentationTest, Constructor) { + using namespace openspace::documentation; + + Documentation doc; + + // Basic Verifiers + doc.entries.emplace_back("BoolVerifier", new BoolVerifier); + doc.entries.emplace_back("DoubleVerifier", new DoubleVerifier); + doc.entries.emplace_back("IntVerifier", new IntVerifier); + doc.entries.emplace_back("StringVerifier", new StringVerifier); + doc.entries.emplace_back("TableVerifier", new TableVerifier); + + // Operator Verifiers + doc.entries.emplace_back("LessDouble", new DoubleLessVerifier(0.0)); + doc.entries.emplace_back("LessInt", new IntLessVerifier(0)); + + doc.entries.emplace_back("LessEqualDouble", new DoubleLessEqualVerifier(0.0)); + doc.entries.emplace_back("LessEqualInt", new IntLessEqualVerifier(0)); + + doc.entries.emplace_back("GreaterDouble", new DoubleGreaterVerifier(0.0)); + doc.entries.emplace_back("GreaterInt", new IntGreaterVerifier(0)); + + doc.entries.emplace_back("GreaterEqualDouble", new DoubleGreaterEqualVerifier(0.0)); + doc.entries.emplace_back("GreaterEqualInt", new IntGreaterEqualVerifier(0)); + + doc.entries.emplace_back("EqualBool", new BoolEqualVerifier(false)); + doc.entries.emplace_back("EqualDouble", new DoubleEqualVerifier(0.0)); + doc.entries.emplace_back("EqualInt", new IntEqualVerifier(0)); + doc.entries.emplace_back("EqualString", new StringEqualVerifier("")); + + doc.entries.emplace_back("UnequalBool", new BoolUnequalVerifier(false)); + doc.entries.emplace_back("UnequalDouble", new DoubleUnequalVerifier(0.0)); + doc.entries.emplace_back("UnequalInt", new IntUnequalVerifier(0)); + doc.entries.emplace_back("UnequalString", new StringUnequalVerifier("")); + + // List Verifiers + doc.entries.emplace_back("InListBool", new BoolInListVerifier({ true, false })); + doc.entries.emplace_back("InListDouble", new DoubleInListVerifier({ 0.0, 1.0})); + doc.entries.emplace_back("InListInt", new IntInListVerifier({ 0, 1 })); + doc.entries.emplace_back("InListString", new StringInListVerifier({ "", "a" })); + + doc.entries.emplace_back("NotInListBool", new BoolNotInListVerifier({ true, false })); + doc.entries.emplace_back("NotInListDouble", new DoubleNotInListVerifier({ 0.0, 1.0 })); + doc.entries.emplace_back("NotInListInt", new IntNotInListVerifier({ 0, 1 })); + doc.entries.emplace_back("NotInListString", new StringNotInListVerifier({ "", "a" })); + + // Range Verifiers + doc.entries.emplace_back("InListDouble", new DoubleInRangeVerifier({ 0.0, 1.0 })); + doc.entries.emplace_back("InListInt", new IntInRangeVerifier({ 0, 1 })); + + doc.entries.emplace_back("NotInListDouble", new DoubleNotInRangeVerifier({ 0.0, 1.0 })); + doc.entries.emplace_back("NotInListInt", new IntNotInRangeVerifier({ 0, 1 })); + + // Misc Verifiers + doc.entries.emplace_back("AnnotationBool", new BoolAnnotationVerifier("Bool")); + doc.entries.emplace_back("AnnotationDouble", new DoubleAnnotationVerifier("Double")); + doc.entries.emplace_back("AnnotationInt", new IntAnnotationVerifier("Int")); + doc.entries.emplace_back("AnnotationString", new StringAnnotationVerifier("String")); + doc.entries.emplace_back("AnnotationTable", new TableAnnotationVerifier("Table")); +} + +TEST_F(DocumentationTest, InitializerConstructor) { + using namespace openspace::documentation; + + Documentation doc { + { + // Basic Verifiers + {"BoolVerifier", new BoolVerifier }, + {"DoubleVerifier", new DoubleVerifier}, + {"IntVerifier", new IntVerifier}, + {"StringVerifier", new StringVerifier}, + {"TableVerifier", new TableVerifier}, + + // Operator Verifiers + { "LessDouble", new DoubleLessVerifier(0.0)}, + { "LessInt", new IntLessVerifier(0)}, + + {"LessEqualDouble", new DoubleLessEqualVerifier(0.0)}, + {"LessEqualInt", new IntLessEqualVerifier(0)}, + + {"GreaterDouble", new DoubleGreaterVerifier(0.0)}, + {"GreaterInt", new IntGreaterVerifier(0)}, + + {"GreaterEqualDouble", new DoubleGreaterEqualVerifier(0.0)}, + {"GreaterEqualInt", new IntGreaterEqualVerifier(0)}, + + {"EqualBool", new BoolEqualVerifier(false)}, + {"EqualDouble", new DoubleEqualVerifier(0.0)}, + {"EqualInt", new IntEqualVerifier(0)}, + {"EqualString", new StringEqualVerifier("")}, + + {"UnequalBool", new BoolUnequalVerifier(false)}, + {"UnequalDouble", new DoubleUnequalVerifier(0.0)}, + {"UnequalInt", new IntUnequalVerifier(0)}, + {"UnequalString", new StringUnequalVerifier("")}, + + // List Verifiers + {"InListBool", new BoolInListVerifier({ true, false })}, + {"InListDouble", new DoubleInListVerifier({ 0.0, 1.0 })}, + {"InListInt", new IntInListVerifier({ 0, 1 })}, + {"InListString", new StringInListVerifier({ "", "a" })}, + + {"NotInListBool", new BoolNotInListVerifier({ true, false })}, + {"NotInListDouble", new DoubleNotInListVerifier({ 0.0, 1.0 })}, + {"NotInListInt", new IntNotInListVerifier({ 0, 1 })}, + {"NotInListString", new StringNotInListVerifier({ "", "a" })}, + + // Range Verifiers + {"InRangeDouble", new DoubleInRangeVerifier(0.0, 1.0)}, + {"InRangeInt", new IntInRangeVerifier(0, 1)}, + + {"InRangeDouble", new DoubleNotInRangeVerifier(0.0, 1.0)}, + {"InRangeInt", new IntNotInRangeVerifier(0, 1)}, + + // Misc Verifiers + {"AnnotationBool", new BoolAnnotationVerifier("Bool")}, + {"AnnotationDouble", new DoubleAnnotationVerifier("Double")}, + {"AnnotationInt", new IntAnnotationVerifier("Int")}, + {"AnnotationString", new StringAnnotationVerifier("String")}, + {"AnnotationTable", new TableAnnotationVerifier("Table")} + } + }; +} + +TEST_F(DocumentationTest, BoolVerifier) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Bool", new BoolVerifier }} + }; + + ghoul::Dictionary positive { + { "Bool", true } + }; + + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Bool", 0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Bool", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negativeExist { + { "Bool2", 0} + }; + negativeRes = testSpecification(doc, negativeExist); + + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Bool", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::MissingKey, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, DoubleVerifier) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Double", new DoubleVerifier }} + }; + + ghoul::Dictionary positive { + { "Double", 0.0 } + }; + + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Double", 0 } + }; + + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negativeExist{ + { "Double2" , 0.0 } + }; + negativeRes = testSpecification(doc, negativeExist); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::MissingKey, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, IntVerifier) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Int", new IntVerifier }} + }; + + ghoul::Dictionary positive { + { "Int", 0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { "Int", 0.0 } + }; + positiveRes = testSpecification(doc, positive2); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Int", 0.1 } + }; + + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negativeExist { + { "Int2", 0 } + }; + negativeRes = testSpecification(doc, negativeExist); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::MissingKey, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, StringVerifier) { + using namespace openspace::documentation; + using namespace std::string_literals; + + Documentation doc { + {{ "String", new StringVerifier }} + }; + + ghoul::Dictionary positive { + { "String", ""s } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "String", 0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("String", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negativeExist { + { "String2", ""s } + }; + negativeRes = testSpecification(doc, negativeExist); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("String", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::MissingKey, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, TableVerifierType) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Table", new TableVerifier }} + }; + + ghoul::Dictionary positive { + { "Table", ghoul::Dictionary{} } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Table", 0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Table", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negativeExist { + { "Table2", ghoul::Dictionary{} } + }; + negativeRes = testSpecification(doc, negativeExist); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Table", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::MissingKey, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, MixedVerifiers) { + using namespace openspace::documentation; + using namespace std::string_literals; + + Documentation doc { + { + { "Bool", new BoolVerifier }, + { "Double", new DoubleVerifier }, + { "Int", new IntVerifier }, + { "String", new StringVerifier }, + { "Table", new TableVerifier } + } + }; + + ghoul::Dictionary positive { + { "Bool", true }, + { "Double", 0.0 }, + { "Int", 0 }, + { "String", ""s }, + { "Table", ghoul::Dictionary{} } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative1 { + { "Bool", true }, + { "Double", 1 }, + { "Int", 0 }, + { "String", ""s }, + { "Table", ghoul::Dictionary{} } + }; + TestResult negativeRes = testSpecification(doc, negative1); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "Bool", true }, + { "Double", 0.0 }, + { "Int", ""s }, + { "String", 1 }, + { "Table", ghoul::Dictionary{} } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(2, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + EXPECT_EQ("String", negativeRes.offenses[1].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[1].reason); +} + +TEST_F(DocumentationTest, NestedTables) { + using namespace openspace::documentation; + using namespace std::string_literals; + + Documentation doc { + { + { "Outer_Int", new IntVerifier }, + { "Outer_Table", new TableVerifier({ + { "Inner_Double", new DoubleVerifier }, + { "Inner_String", new StringVerifier } + })}, + { "Outer_Double", new DoubleVerifier }, + { "Outer_Table2" , new TableVerifier({ + { "Inner_Double2", new DoubleVerifier }, + { "Inner_String2", new StringVerifier }, + { "Inner_Table" , new TableVerifier({ + { "Inner_Inner_Int", new IntVerifier } + })} + })} + } + }; + + ghoul::Dictionary positive { + { "Outer_Int", 1 }, + { "Outer_Table", ghoul::Dictionary { + { "Inner_Double", 0.0 }, + { "Inner_String", ""s } + }}, + { "Outer_Double", 0.0 }, + { "Outer_Table2", ghoul::Dictionary { + { "Inner_Double2", 0.0 }, + { "Inner_String2", ""s }, + { "Inner_Table", ghoul::Dictionary { + { "Inner_Inner_Int", 0 } + }} + }} + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negativeSimple { + { "Outer_Int", 1 }, + { "Outer_Table", 0}, + { "Outer_Double", 0.0 }, + { "Outer_Table2", ghoul::Dictionary { + { "Inner_Double2", 0.0 }, + { "Inner_String2", ""s }, + { "Inner_Table", ghoul::Dictionary { + { "Inner_Inner_Int", 0 } + }} + }} + }; + TestResult negativeRes = testSpecification(doc, negativeSimple); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Outer_Table", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negativeInner { + { "Outer_Int", 1 }, + { "Outer_Table", ghoul::Dictionary { + { "Inner_Double", ""s }, + { "Inner_String", ""s } + }}, + { "Outer_Double", 0.0 }, + { "Outer_Table2", ghoul::Dictionary { + { "Inner_Double2", 0.0 }, + { "Inner_String2", ""s }, + { "Inner_Table", ghoul::Dictionary { + { "Inner_Inner_Int", 0 } + }} + }} + }; + negativeRes = testSpecification(doc, negativeInner); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Outer_Table.Inner_Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negativeInner2 { + { "Outer_Int", 1 }, + { "Outer_Table", ghoul::Dictionary { + { "Inner_Double", ""s }, + { "Inner_String", 0.0 } + }}, + { "Outer_Double", 0.0 }, + { "Outer_Table2", ghoul::Dictionary { + { "Inner_Double2", 0.0 }, + { "Inner_String2", ""s }, + { "Inner_Table", ghoul::Dictionary { + { "Inner_Inner_Int", 0 } + }} + }} + }; + negativeRes = testSpecification(doc, negativeInner2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(2, negativeRes.offenses.size()); + EXPECT_EQ("Outer_Table.Inner_Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + EXPECT_EQ("Outer_Table.Inner_String", negativeRes.offenses[1].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[1].reason); + + ghoul::Dictionary negativeInnerSeparate { + { "Outer_Int", 1 }, + { "Outer_Table", ghoul::Dictionary { + { "Inner_Double", ""s }, + { "Inner_String", ""s } + } }, + { "Outer_Double", 0.0 }, + { "Outer_Table2", ghoul::Dictionary { + { "Inner_Double2", ""s }, + { "Inner_String2", ""s }, + { "Inner_Table", ghoul::Dictionary { + { "Inner_Inner_Int", 0 } + }} + }} + }; + negativeRes = testSpecification(doc, negativeInnerSeparate); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(2, negativeRes.offenses.size()); + EXPECT_EQ("Outer_Table.Inner_Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + EXPECT_EQ("Outer_Table2.Inner_Double2", negativeRes.offenses[1].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[1].reason); + + ghoul::Dictionary negativeInnerFull { + { "Outer_Int", 1 }, + { "Outer_Table", ghoul::Dictionary { + { "Inner_Double", ""s }, + { "Inner_String", ""s } + } }, + { "Outer_Double", 0.0 }, + { "Outer_Table2", ghoul::Dictionary { + { "Inner_Double2", ""s }, + { "Inner_String2", ""s }, + { "Inner_Table", ghoul::Dictionary { + { "Inner_Inner_Int", ""s } + }} + }} + }; + negativeRes = testSpecification(doc, negativeInnerFull); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(3, negativeRes.offenses.size()); + EXPECT_EQ("Outer_Table.Inner_Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + EXPECT_EQ("Outer_Table2.Inner_Double2", negativeRes.offenses[1].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[1].reason); + EXPECT_EQ("Outer_Table2.Inner_Table.Inner_Inner_Int", negativeRes.offenses[2].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[2].reason); +} + +TEST_F(DocumentationTest, Optional) { + using namespace openspace::documentation; + + Documentation doc { + { + { "Bool_Force", new BoolVerifier, "", Optional::No }, + { "Bool_Optional", new BoolVerifier, "", Optional::Yes } + } + }; + + ghoul::Dictionary positive { + { "Bool_Force", true }, + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { "Bool_Force", true }, + { "Bool_Optional", true } + }; + positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Bool_Force", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::MissingKey, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "Bool_Optional", true } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Bool_Force", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::MissingKey, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative3 { + { "Bool_Force", true }, + { "Bool_Optional", 1 } + }; + negativeRes = testSpecification(doc, negative3); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Bool_Optional", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, RequiredInOptional) { + using namespace openspace::documentation; + + Documentation doc { + {{ + "a", + new TableVerifier({ + { + "b", + new IntVerifier + }, + { + "c", + new IntVerifier, + "", + Optional::Yes + } + }), + "", + Optional::Yes + }} + }; + + ghoul::Dictionary positive { + { + "a", ghoul::Dictionary{ + { "b", 1 } + } + } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { + "a", ghoul::Dictionary{ + { "b", 1 }, + { "c", 2 } + } + } + }; + positiveRes = testSpecification(doc, positive2); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive3 {}; + positiveRes = testSpecification(doc, positive3); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", ghoul::Dictionary{ { "c", 2 }}} + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a.b", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::MissingKey, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, Exhaustive) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Int", new IntVerifier }}, + Exhaustive::Yes + }; + + ghoul::Dictionary positive { + { "Int" , 1 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "False_Int", 1 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(2, negativeRes.offenses.size()); + EXPECT_EQ("False_Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::ExtraKey, negativeRes.offenses[0].reason); + EXPECT_EQ("Int", negativeRes.offenses[1].offender); + EXPECT_EQ(TestResult::Offense::Reason::MissingKey, negativeRes.offenses[1].reason); + + ghoul::Dictionary negative2 { + { "Double", 2.0 } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(2, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::ExtraKey, negativeRes.offenses[0].reason); + EXPECT_EQ("Int", negativeRes.offenses[1].offender); + EXPECT_EQ(TestResult::Offense::Reason::MissingKey, negativeRes.offenses[1].reason); +} + +TEST_F(DocumentationTest, NestedExhaustive) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Table", new TableVerifier( + { { "a", new IntVerifier } }, + Exhaustive::Yes + ) + }} + }; + + ghoul::Dictionary positive { + { "Table", ghoul::Dictionary{{ "a", 1 }}} + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Table", ghoul::Dictionary{{ "b", 2.0 }}} + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(2, negativeRes.offenses.size()); + EXPECT_EQ("Table.a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::MissingKey, negativeRes.offenses[0].reason); + EXPECT_EQ("Table.b", negativeRes.offenses[1].offender); + EXPECT_EQ(TestResult::Offense::Reason::ExtraKey, negativeRes.offenses[1].reason); +} + +TEST_F(DocumentationTest, EmptyEntriesNonExhaustive) { + using namespace openspace::documentation; + + Documentation doc; + + ghoul::Dictionary positive {}; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { "a", 1 } + }; + positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); +} + +TEST_F(DocumentationTest, EmptyEntriesExhaustive) { + using namespace openspace::documentation; + + Documentation doc { + {}, + Exhaustive::Yes + }; + + ghoul::Dictionary positive {}; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", 1 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::ExtraKey, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, EmptyNestedExhaustive) { + using namespace openspace::documentation; + + Documentation doc { + {{ + "Table", + new TableVerifier( + { + }, + Exhaustive::Yes + ) + }} + }; + + ghoul::Dictionary positive { + { "Table", ghoul::Dictionary() } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Table", ghoul::Dictionary{ { "a", 1 }}} + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Table.a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::ExtraKey, negativeRes.offenses[0].reason); +} + + + +TEST_F(DocumentationTest, LessInt) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Int", new IntLessVerifier(5) }} + }; + + ghoul::Dictionary positive { + { "Int", 0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Int", 10 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, LessDouble) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Double", new DoubleLessVerifier(5.0) }} + }; + + ghoul::Dictionary positive { + { "Double", 0.0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Double", 10.0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, LessEqualInt) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Int", new IntLessEqualVerifier(5) }} + }; + + ghoul::Dictionary positive { + { "Int", 0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positiveEqual { + { "Int", 5 } + }; + positiveRes = testSpecification(doc, positiveEqual); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Int", 10 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, LessEqualDouble) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Double", new DoubleLessEqualVerifier(5.0) }} + }; + + ghoul::Dictionary positive { + { "Double", 0.0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positiveEqual { + { "Double", 5.0 } + }; + positiveRes = testSpecification(doc, positiveEqual); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Double", 10.0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, GreaterInt) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Int", new IntGreaterVerifier(5) }} + }; + + ghoul::Dictionary positive { + { "Int", 10 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Int", 0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, GreaterDouble) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Double", new DoubleGreaterVerifier(5.0) }} + }; + + ghoul::Dictionary positive { + { "Double", 10.0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Double", 0.0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, GreaterEqualInt) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Int", new IntGreaterEqualVerifier(5) }} + }; + + ghoul::Dictionary positive { + { "Int", 10 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positiveEqual { + { "Int", 5 } + }; + positiveRes = testSpecification(doc, positiveEqual); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Int", 0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, GreaterEqualDouble) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Double", new DoubleGreaterEqualVerifier(5.0) }} + }; + + ghoul::Dictionary positive { + { "Double", 10.0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positiveEqual { + { "Double", 5.0 } + }; + positiveRes = testSpecification(doc, positiveEqual); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Double", 0.0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, EqualBool) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Bool", new BoolEqualVerifier(true) }} + }; + + ghoul::Dictionary positive { + { "Bool", true} + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Bool", false } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Bool", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, EqualInt) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Int", new IntEqualVerifier(1) }} + }; + + ghoul::Dictionary positive { + { "Int", 1} + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Int", 0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, EqualDouble) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Double", new DoubleEqualVerifier(1.0) }} + }; + + ghoul::Dictionary positive { + { "Double", 1.0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Double", 0.0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, EqualString) { + using namespace openspace::documentation; + using namespace std::string_literals; + + Documentation doc { + {{ "String", new StringEqualVerifier("string"s) }} + }; + + ghoul::Dictionary positive { + { "String", "string"s } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "String", "no_string"s } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("String", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, UnequalBool) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Bool", new BoolUnequalVerifier(true) }} + }; + + ghoul::Dictionary positive { + { "Bool", false } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Bool", true } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Bool", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, UnequalInt) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Int", new IntUnequalVerifier(1) }} + }; + + ghoul::Dictionary positive { + { "Int", 0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Int", 1 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, UnequalDouble) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Double", new DoubleUnequalVerifier(1.0) }} + }; + + ghoul::Dictionary positive { + { "Double", 0.0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Double", 1.0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, UnequalString) { + using namespace openspace::documentation; + using namespace std::string_literals; + + Documentation doc { + {{ "String", new StringUnequalVerifier("string"s) }} + }; + + ghoul::Dictionary positive { + { "String", "no_string"s } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "String", "string"s } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("String", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, ListBool) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Bool" , new BoolInListVerifier({ true }) }} + }; + + ghoul::Dictionary positive { + { "Bool", true } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Bool", false } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Bool", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, ListInt) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Int" , new IntInListVerifier({ 0, 1, 2 }) }} + }; + + ghoul::Dictionary positive { + { "Int", 1 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { "Int", 2 } + }; + positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Int", 5 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, ListDouble) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Double" , new DoubleInListVerifier({ 0.0, 1.0, 2.0 }) }} + }; + + ghoul::Dictionary positive { + { "Double", 1.0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { "Double", 2.0 } + }; + positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Double", 5.0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, ListString) { + using namespace openspace::documentation; + using namespace std::string_literals; + + Documentation doc { + {{ "String" , new StringInListVerifier({ "0"s, "1"s, "2"s }) }} + }; + + ghoul::Dictionary positive { + { "String", "1"s } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { "String", "2"s } + }; + positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "String", "5"s } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("String", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, NotListBool) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Bool" , new BoolNotInListVerifier({ true }) }} + }; + + ghoul::Dictionary positive { + { "Bool", false } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Bool", true } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Bool", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, NotListInt) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Int" , new IntNotInListVerifier({ 0, 1, 2 }) }} + }; + + ghoul::Dictionary positive { + { "Int", -1 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { "Int", 3 } + }; + positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Int", 2 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, NotListDouble) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Double" , new DoubleNotInListVerifier({ 0.0, 1.0, 2.0 }) }} + }; + + ghoul::Dictionary positive { + { "Double", -1.0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { "Double", 3.0 } + }; + positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Double", 1.0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, NotListString) { + using namespace openspace::documentation; + using namespace std::string_literals; + + Documentation doc { + {{ "String" , new StringNotInListVerifier({ "0"s, "1"s, "2"s }) }} + }; + + ghoul::Dictionary positive { + { "String", "string"s } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { "String", "foo_string"s } + }; + positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "String", "1"s } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("String", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, AnnotationBool) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Bool", new BoolAnnotationVerifier("Bool") }} + }; + + ghoul::Dictionary positive { + { "Bool", true } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Bool", 0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Bool", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, AnnotationInt) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Int", new IntAnnotationVerifier("Int") }} + }; + + ghoul::Dictionary positive { + { "Int", 1 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Int", 1.1 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, AnnotationDouble) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Double", new DoubleAnnotationVerifier("Double") }} + }; + + ghoul::Dictionary positive { + { "Double", 0.0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Double", true } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, AnnotationString) { + using namespace openspace::documentation; + using namespace std::string_literals; + + Documentation doc { + {{ "String", new StringAnnotationVerifier("String") }} + }; + + ghoul::Dictionary positive { + { "String", ""s } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "String", 1 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("String", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, AnnotationTable) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Table", new TableAnnotationVerifier("Table") }} + }; + + ghoul::Dictionary positive { + { "Table", ghoul::Dictionary{} } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Table", 1 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Table", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, InRangeInt) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Int", new InRangeVerifier(0, 5) }} + }; + + ghoul::Dictionary positive { + { "Int", 2 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { "Int", 0 } + }; + positiveRes = testSpecification(doc, positive2); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive3 { + { "Int", 5 } + }; + positiveRes = testSpecification(doc, positive3); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Int", 10 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, InRangeDouble) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Double", new InRangeVerifier(0.0, 5.0) }} + }; + + ghoul::Dictionary positive { + { "Double", 2.0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { "Double", 0.0 } + }; + positiveRes = testSpecification(doc, positive2); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive3 { + { "Double", 5.0 } + }; + positiveRes = testSpecification(doc, positive3); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive4 { + { "Double", 1.5 } + }; + positiveRes = testSpecification(doc, positive4); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Double", 10.0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, NotInRangeInt) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Int", new NotInRangeVerifier(0, 5) }} + }; + + ghoul::Dictionary positive { + { "Int", -1 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { "Int", 6 } + }; + positiveRes = testSpecification(doc, positive2); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Int", 2 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "Int", 0 } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative3 { + { "Int", 5 } + }; + negativeRes = testSpecification(doc, negative3); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Int", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, NotInRangeDouble) { + using namespace openspace::documentation; + + Documentation doc { + {{ "Double", new NotInRangeVerifier(0.0, 5.0) }} + }; + + ghoul::Dictionary positive { + { "Double", -1.0 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { "Double", 6.0 } + }; + positiveRes = testSpecification(doc, positive2); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Double", 0.0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "Double", 5.0 } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative3 { + { "Double", 2.5 } + }; + negativeRes = testSpecification(doc, negative3); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Double", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, Wildcard) { + using namespace openspace::documentation; + + Documentation doc { + {{ DocumentationEntry::Wildcard, new IntVerifier }} + }; + + ghoul::Dictionary positive { + { "a", 1 }, + { "b", 2 }, + { "c", 3 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", false }, + { "b", 2 }, + { "c", 3 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "a", false }, + { "b", false }, + { "c", 3 } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(2, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + EXPECT_EQ("b", negativeRes.offenses[1].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[1].reason); + + ghoul::Dictionary negative3 { + { "a", false }, + { "b", false }, + { "c", false } + }; + negativeRes = testSpecification(doc, negative3); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(3, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + EXPECT_EQ("b", negativeRes.offenses[1].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[1].reason); + EXPECT_EQ("c", negativeRes.offenses[2].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[2].reason); +} + +TEST_F(DocumentationTest, WildcardMixed) { + using namespace openspace::documentation; + + Documentation doc { + { + { DocumentationEntry::Wildcard, new IntVerifier }, + { "b", new IntGreaterVerifier(5) } + } + }; + + ghoul::Dictionary positive { + { "a", 1 }, + { "b", 8 }, + { "c", 3 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", false }, + { "b", 2 }, + { "c", 3 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(2, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + EXPECT_EQ("b", negativeRes.offenses[1].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[1].reason); + + ghoul::Dictionary negative2 { + { "a", false }, + { "b", false }, + { "c", 3 } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(2, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + EXPECT_EQ("b", negativeRes.offenses[1].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[1].reason); + + ghoul::Dictionary negative3 { + { "a", false }, + { "b", 1 }, + { "c", false } + }; + negativeRes = testSpecification(doc, negative3); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(3, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + EXPECT_EQ("b", negativeRes.offenses[1].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[1].reason); + EXPECT_EQ("c", negativeRes.offenses[2].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[2].reason); + + ghoul::Dictionary negative4 { + { "a", false }, + { "b", 10 }, + { "c", false } + }; + negativeRes = testSpecification(doc, negative4); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(2, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + EXPECT_EQ("c", negativeRes.offenses[1].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[1].reason); +} + +TEST_F(DocumentationTest, Referencing) { + using namespace openspace::documentation; + + Documentation referenced { + "Referenced Name", + "referenced_id", + { + { "a", new IntVerifier }, + { "b", new DoubleVerifier } + }, + }; + DocEng.addDocumentation(referenced); + + Documentation doc {{ + { "Table", new ReferencingVerifier("referenced_id") } + }}; + + ghoul::Dictionary positive { + { "Table", ghoul::Dictionary{ { "a", 1 }, { "b", 2.0 } }} + }; + + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "Table", 1 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Table", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "Table", ghoul::Dictionary{ { "a", 1 }, { "b", true }}} + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Table.b", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + + Documentation wrongDoc {{ + { "Table", new ReferencingVerifier("WRONG") } + } }; + ghoul::Dictionary wrongNegative { + { "Table", ghoul::Dictionary{ { "a", 1 },{ "b", 2.0 } } } + }; + negativeRes = testSpecification(wrongDoc, wrongNegative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("Table", negativeRes.offenses[0].offender); + EXPECT_EQ( + TestResult::Offense::Reason::UnknownIdentifier, + negativeRes.offenses[0].reason + ); +} + + +TEST_F(DocumentationTest, AndOperator) { + using namespace openspace::documentation; + + Documentation doc { + { + { "a", new AndVerifier( + new IntGreaterEqualVerifier(2), new IntLessEqualVerifier(5) + ) + } + } + }; + + ghoul::Dictionary positive { + { "a", 4 } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", 0 } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "a", 8 } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, OrOperator) { + using namespace openspace::documentation; + using namespace std::string_literals; + + Documentation doc { + {{ "a", new OrVerifier(new StringVerifier, new IntVerifier)}} + }; + + ghoul::Dictionary positive { + { "a", ""s } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary positive2 { + { "a", 1 } + }; + positiveRes = testSpecification(doc, positive2); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", false } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::Verification, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, BoolVector2Verifier) { + using namespace openspace::documentation; + + Documentation doc { + {{ "a", new BoolVector2Verifier }} + }; + + ghoul::Dictionary positive { + { "a", glm::bvec2(true) } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", ghoul::Dictionary{ { "1", true }, { "2", 1.0 } } } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "a", true } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, IntVector2Verifier) { + using namespace openspace::documentation; + + Documentation doc { + { { "a", new IntVector2Verifier } } + }; + + ghoul::Dictionary positive { + { "a", glm::ivec2(2) } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", ghoul::Dictionary{ { "1", true },{ "2", 1 } } } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "a", true } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, DoubleVector2Verifier) { + using namespace openspace::documentation; + + Documentation doc { + { { "a", new DoubleVector2Verifier } } + }; + + ghoul::Dictionary positive { + { "a", glm::dvec2(2.0) } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", ghoul::Dictionary{ { "1", true }, { "2", 1.0 } } } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "a", true } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, BoolVector3Verifier) { + using namespace openspace::documentation; + + Documentation doc { + { { "a", new BoolVector3Verifier } } + }; + + ghoul::Dictionary positive { + { "a", glm::bvec3(true) } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", ghoul::Dictionary{ { "1", true },{ "2", 1.0 }, { "3", "s" } } } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "a", true } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, IntVector3Verifier) { + using namespace openspace::documentation; + + Documentation doc { + { { "a", new IntVector3Verifier } } + }; + + ghoul::Dictionary positive { + { "a", glm::ivec3(2) } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", ghoul::Dictionary{ { "1", true },{ "2", 1 }, { "3", "s" } } } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "a", true } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, DoubleVector3Verifier) { + using namespace openspace::documentation; + + Documentation doc { + { { "a", new DoubleVector3Verifier } } + }; + + ghoul::Dictionary positive { + { "a", glm::dvec3(2.0) } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", ghoul::Dictionary{ { "1", true },{ "2", 1.0 }, { "3", "s"} } } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "a", true } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, BoolVector4Verifier) { + using namespace openspace::documentation; + + Documentation doc { + { { "a", new BoolVector4Verifier } } + }; + + ghoul::Dictionary positive { + { "a", glm::bvec4(true) } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", ghoul::Dictionary{ { "1", true },{ "2", 1.0 }, { "3", "s" }, { "4", 1 }}} + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "a", true } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, IntVector4Verifier) { + using namespace openspace::documentation; + + Documentation doc { + { { "a", new IntVector4Verifier } } + }; + + ghoul::Dictionary positive { + { "a", glm::ivec4(2) } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", ghoul::Dictionary{ { "1", true },{ "2", 1 },{ "3", "s" }, { "4", 1 } } } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2{ + { "a", true } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, DoubleVector4Verifier) { + using namespace openspace::documentation; + + Documentation doc { + { { "a", new DoubleVector4Verifier } } + }; + + ghoul::Dictionary positive { + { "a", glm::dvec4(2.0) } + }; + TestResult positiveRes = testSpecification(doc, positive); + EXPECT_TRUE(positiveRes.success); + EXPECT_EQ(0, positiveRes.offenses.size()); + + ghoul::Dictionary negative { + { "a", ghoul::Dictionary{ { "1", true },{ "2", 1.0 },{ "3", "s" }, { "4", 1 } } } + }; + TestResult negativeRes = testSpecification(doc, negative); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); + + ghoul::Dictionary negative2 { + { "a", true } + }; + negativeRes = testSpecification(doc, negative2); + EXPECT_FALSE(negativeRes.success); + ASSERT_EQ(1, negativeRes.offenses.size()); + EXPECT_EQ("a", negativeRes.offenses[0].offender); + EXPECT_EQ(TestResult::Offense::Reason::WrongType, negativeRes.offenses[0].reason); +} + +TEST_F(DocumentationTest, VerifierTypePostConditions) { + using namespace openspace::documentation; + using namespace std::string_literals; + + EXPECT_NE("", BoolVerifier().type()); + EXPECT_NE("", DoubleVerifier().type()); + EXPECT_NE("", IntVerifier().type()); + EXPECT_NE("", StringVerifier().type()); + EXPECT_NE("", TableVerifier().type()); + + EXPECT_NE("", BoolVector2Verifier().type()); + EXPECT_NE("", IntVector2Verifier().type()); + EXPECT_NE("", DoubleVector2Verifier().type()); + EXPECT_NE("", BoolVector3Verifier().type()); + EXPECT_NE("", IntVector3Verifier().type()); + EXPECT_NE("", DoubleVector3Verifier().type()); + EXPECT_NE("", BoolVector4Verifier().type()); + EXPECT_NE("", IntVector4Verifier().type()); + EXPECT_NE("", DoubleVector4Verifier().type()); + + EXPECT_NE("", IntLessVerifier(0).type()); + EXPECT_NE("", DoubleLessVerifier(0.0).type()); + EXPECT_NE("", IntLessEqualVerifier(0).type()); + EXPECT_NE("", DoubleLessEqualVerifier(0.0).type()); + EXPECT_NE("", IntGreaterVerifier(0).type()); + EXPECT_NE("", DoubleGreaterVerifier(0.0).type()); + EXPECT_NE("", IntGreaterEqualVerifier(0).type()); + EXPECT_NE("", DoubleGreaterEqualVerifier(0.0).type()); + + EXPECT_NE("", BoolEqualVerifier(true).type()); + EXPECT_NE("", IntEqualVerifier(0).type()); + EXPECT_NE("", DoubleEqualVerifier(0.0).type()); + EXPECT_NE("", StringEqualVerifier(""s).type()); + EXPECT_NE("", BoolUnequalVerifier(true).type()); + EXPECT_NE("", IntUnequalVerifier(0).type()); + EXPECT_NE("", DoubleUnequalVerifier(0.0).type()); + EXPECT_NE("", StringUnequalVerifier(""s).type()); + + EXPECT_NE("", BoolInListVerifier({ true }).type()); + EXPECT_NE("", IntInListVerifier({ 0 }).type()); + EXPECT_NE("", DoubleInListVerifier({ 0.0 }).type()); + EXPECT_NE("", StringInListVerifier({ ""s }).type()); + EXPECT_NE("", BoolNotInListVerifier({ true }).type()); + EXPECT_NE("", IntNotInListVerifier({ 0 }).type()); + EXPECT_NE("", DoubleNotInListVerifier({ 0.0 }).type()); + EXPECT_NE("", StringNotInListVerifier({ ""s }).type()); + + EXPECT_NE("", IntInRangeVerifier({ 0, 1 }).type()); + EXPECT_NE("", DoubleInRangeVerifier({ 0.0, 1.0 }).type()); + EXPECT_NE("", IntNotInRangeVerifier({ 0, 1 }).type()); + EXPECT_NE("", DoubleNotInRangeVerifier({ 0.0, 1.0 }).type()); + + EXPECT_NE("", BoolAnnotationVerifier("Annotation"s).type()); + EXPECT_NE("", IntAnnotationVerifier("Annotation"s).type()); + EXPECT_NE("", DoubleAnnotationVerifier("Annotation"s).type()); + EXPECT_NE("", StringAnnotationVerifier("Annotation"s).type()); + EXPECT_NE("", TableAnnotationVerifier("Annotation"s).type()); + + EXPECT_NE("", ReferencingVerifier("identifier"s).type()); +} + +TEST_F(DocumentationTest, VerifierDocumentationPostConditions) { + using namespace openspace::documentation; + using namespace std::string_literals; + + EXPECT_NE("", BoolVerifier().documentation()); + EXPECT_NE("", DoubleVerifier().documentation()); + EXPECT_NE("", IntVerifier().documentation()); + EXPECT_NE("", StringVerifier().documentation()); + EXPECT_NE("", TableVerifier().documentation()); + + EXPECT_NE("", BoolVector2Verifier().documentation()); + EXPECT_NE("", IntVector2Verifier().documentation()); + EXPECT_NE("", DoubleVector2Verifier().documentation()); + EXPECT_NE("", BoolVector3Verifier().documentation()); + EXPECT_NE("", IntVector3Verifier().documentation()); + EXPECT_NE("", DoubleVector3Verifier().documentation()); + EXPECT_NE("", BoolVector4Verifier().documentation()); + EXPECT_NE("", IntVector4Verifier().documentation()); + EXPECT_NE("", DoubleVector4Verifier().documentation()); + + EXPECT_NE("", IntLessVerifier(0).documentation()); + EXPECT_NE("", DoubleLessVerifier(0.0).documentation()); + EXPECT_NE("", IntLessEqualVerifier(0).documentation()); + EXPECT_NE("", DoubleLessEqualVerifier(0.0).documentation()); + EXPECT_NE("", IntGreaterVerifier(0).documentation()); + EXPECT_NE("", DoubleGreaterVerifier(0.0).documentation()); + EXPECT_NE("", IntGreaterEqualVerifier(0).documentation()); + EXPECT_NE("", DoubleGreaterEqualVerifier(0.0).documentation()); + + EXPECT_NE("", BoolEqualVerifier(true).documentation()); + EXPECT_NE("", IntEqualVerifier(0).documentation()); + EXPECT_NE("", DoubleEqualVerifier(0.0).documentation()); + EXPECT_NE("", StringEqualVerifier(""s).documentation()); + EXPECT_NE("", BoolUnequalVerifier(true).documentation()); + EXPECT_NE("", IntUnequalVerifier(0).documentation()); + EXPECT_NE("", DoubleUnequalVerifier(0.0).documentation()); + EXPECT_NE("", StringUnequalVerifier(""s).documentation()); + + EXPECT_NE("", BoolInListVerifier({ true }).documentation()); + EXPECT_NE("", IntInListVerifier({ 0 }).documentation()); + EXPECT_NE("", DoubleInListVerifier({ 0.0 }).documentation()); + EXPECT_NE("", StringInListVerifier({ ""s }).documentation()); + EXPECT_NE("", BoolNotInListVerifier({ true }).documentation()); + EXPECT_NE("", IntNotInListVerifier({ 0 }).documentation()); + EXPECT_NE("", DoubleNotInListVerifier({ 0.0 }).documentation()); + EXPECT_NE("", StringNotInListVerifier({ ""s }).documentation()); + + EXPECT_NE("", IntInRangeVerifier({ 0, 1 }).documentation()); + EXPECT_NE("", DoubleInRangeVerifier({ 0.0, 1.0 }).documentation()); + EXPECT_NE("", IntNotInRangeVerifier({ 0, 1 }).documentation()); + EXPECT_NE("", DoubleNotInRangeVerifier({ 0.0, 1.0 }).documentation()); + + EXPECT_NE("", BoolAnnotationVerifier("Annotation"s).documentation()); + EXPECT_NE("", IntAnnotationVerifier("Annotation"s).documentation()); + EXPECT_NE("", DoubleAnnotationVerifier("Annotation"s).documentation()); + EXPECT_NE("", StringAnnotationVerifier("Annotation"s).documentation()); + EXPECT_NE("", TableAnnotationVerifier("Annotation"s).documentation()); + + EXPECT_NE("", ReferencingVerifier("identifier"s).documentation()); + +}