diff --git a/.hgtags b/.hgtags index 315ac2fb0..e8df12010 100644 --- a/.hgtags +++ b/.hgtags @@ -1,3 +1,4 @@ +14518462105ba7e265e3e6bb970c127656302861 stable-2019-12 4174e5d213265d708442b7444a82e2e679ef5061 simdis10.0_SR6 4174e5d213265d708442b7444a82e2e679ef5061 simdissdk-1.11 24ae53881d9993f0500061fe9c2045acaa919359 simdissdk-1.9 diff --git a/CMakeImport/ImportGDAL.cmake b/CMakeImport/ImportGDAL.cmake index cec6f2c5c..a219f7dd2 100644 --- a/CMakeImport/ImportGDAL.cmake +++ b/CMakeImport/ImportGDAL.cmake @@ -2,7 +2,7 @@ # Setting the GDAL_DIR environment variable will allow use of a custom built library set(LIBRARYNAME GDAL) -set(${LIBRARYNAME}_VERSION 2.1.1) +set(${LIBRARYNAME}_VERSION 2.4.4) set(${LIBRARYNAME}_INSTALL_COMPONENT ThirdPartyLibs) # Setup search paths based off GDAL_ROOT @@ -23,11 +23,11 @@ find_path(${LIBRARYNAME}_LIBRARY_INCLUDE_PATH PATHS ${${LIBRARYNAME}_ROOT}/include NO_DEFAULT_PATH) # GDAL_LIBRARY_RELEASE_PATH find_library(${LIBRARYNAME}_LIBRARY_RELEASE_NAME - NAMES gdal gdal_i gdal201 gdal111 + NAMES gdal gdal_i gdal204 gdal201 gdal111 PATHS ${${LIBRARYNAME}_ROOT}/lib NO_DEFAULT_PATH) # GDAL_LIBRARY_DEBUG_PATH find_library(${LIBRARYNAME}_LIBRARY_DEBUG_NAME - NAMES gdal_d gdal201_d gdal111_d + NAMES gdal_d gdal204_d gdal201_d gdal111_d PATHS ${${LIBRARYNAME}_ROOT}/lib NO_DEFAULT_PATH) # Fall back on release library explicitly, only on Windows if(WIN32 AND ${LIBRARYNAME}_LIBRARY_RELEASE_NAME AND NOT ${LIBRARYNAME}_LIBRARY_DEBUG_NAME) diff --git a/CMakeImport/ImportProtobuf.cmake b/CMakeImport/ImportProtobuf.cmake index 0f9d14f52..3dfddcc1e 100644 --- a/CMakeImport/ImportProtobuf.cmake +++ b/CMakeImport/ImportProtobuf.cmake @@ -261,6 +261,7 @@ function(CREATE_PROTOBUF_LIBRARY LIB_TARGETNAME LIB_PROJECTNAME FOLDER PROJECT_L set_target_properties(${LIB_TARGETNAME} PROPERTIES FOLDER "${FOLDER}" PROJECT_LABEL "${PROJECT_LABEL}" + UNITY_BUILD OFF ) endfunction() diff --git a/CMakeImport/ImportSQLite.cmake b/CMakeImport/ImportSQLite.cmake index df4495ada..3a0345a2b 100644 --- a/CMakeImport/ImportSQLite.cmake +++ b/CMakeImport/ImportSQLite.cmake @@ -1,7 +1,7 @@ # Setup SQLite library # Setting the SQLITE3_DIR environment variable will allow use of a custom built library -set(SQLITE_VERSION 3.29.0) +set(SQLITE_VERSION 3.31.1) # Setup search paths initialize_ENV(SQLITE3_DIR) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e2849ff6..67b8cf178 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,6 +206,9 @@ include(ImportProtobuf) # --- SQLITE3 ----------------------------- set(SQLITE3_DIR "" CACHE PATH "Sqlite3 root directory") include(ImportSQLite) +if (SQLITE3_FOUND) + set(SIM_HAVE_DB_SUPPORT 1) +endif() # subprojects add_subdirectory(SDK) diff --git a/CMakeModules/CreateInstallProperties.cmake b/CMakeModules/CreateInstallProperties.cmake index d5bece91d..e12a6ee26 100644 --- a/CMakeModules/CreateInstallProperties.cmake +++ b/CMakeModules/CreateInstallProperties.cmake @@ -109,40 +109,6 @@ function(create_plugin_install_properties TARGET) endfunction() -# Used for SIMDIS SDK osg/osgEarth plug-ins (osgdb_osgearth_db) -function(create_osg_plugin_install_properties TARGET) - # Separate ARGN into specific lists so that the INSTALLATION_COMPONENT can be removed from create_library arg list and iterated over - set(arglists "INSTALLATION_COMPONENT") - # Option names - set(options "RPATH") - - # Get the argument lists - parse_arguments(ARG "${arglists}" "${options}" ${ARGN}) - - if(ARG_RPATH) - # Compute RPATH for lib directory containing dependencies relative to bin installation directory - set(LIBRARY_INSTALL_PATH ${CMAKE_INSTALL_PREFIX}/${INSTALLSETTINGS_LIBRARY_DIR}) - set(RUNTIME_INSTALL_PATH ${CMAKE_INSTALL_PREFIX}/${INSTALLSETTINGS_OSGPLUGIN_DIR}) - file(RELATIVE_PATH REL_INSTALL_PATH ${RUNTIME_INSTALL_PATH} ${LIBRARY_INSTALL_PATH}) - - # If the lib and bin paths were the same, an empty value will be returned - if(NOT REL_INSTALL_PATH) - set(REL_INSTALL_PATH ".") - endif() - - # Set install RPATH variable - set_target_properties(${TARGET} PROPERTIES INSTALL_RPATH "\$ORIGIN/${REL_INSTALL_PATH}") - endif() - - # Setup installation - foreach(COMPONENT ${ARG_INSTALLATION_COMPONENT}) - install(TARGETS ${TARGET} - COMPONENT ${COMPONENT} - RUNTIME DESTINATION ${INSTALLSETTINGS_OSGPLUGIN_DIR} # Windows DLL destination - LIBRARY DESTINATION ${INSTALLSETTINGS_OSGPLUGIN_DIR}) # UNIX shared object destination - endforeach() -endfunction() - # Include the code for post_build_install() command include(PostBuildInstall) diff --git a/CMakeModules/HelperFunctions.cmake b/CMakeModules/HelperFunctions.cmake index a2d98db48..523fe1464 100644 --- a/CMakeModules/HelperFunctions.cmake +++ b/CMakeModules/HelperFunctions.cmake @@ -503,7 +503,10 @@ endfunction() macro(vsi_require_target) foreach(target ${ARGN}) if(NOT TARGET ${target}) - if(VERBOSE) + if(WARN_SKIPPED_TARGETS) + get_filename_component(_DIR "${CMAKE_CURRENT_LIST_DIR}" NAME) + message(WARNING "Skipping ${_DIR}, missing target ${target}") + elseif(VERBOSE) get_filename_component(_DIR "${CMAKE_CURRENT_LIST_DIR}" NAME) message(STATUS "Skipping ${_DIR}, missing target ${target}") endif() diff --git a/Examples/ASIViewer/ASIViewer.cpp b/Examples/ASIViewer/ASIViewer.cpp index 9803116e6..89dfa97ff 100644 --- a/Examples/ASIViewer/ASIViewer.cpp +++ b/Examples/ASIViewer/ASIViewer.cpp @@ -43,9 +43,10 @@ #include "simVis/Types.h" #include "simUtil/ExampleResources.h" -#include -#include -#include +#include "osg/ImageStream" +#include "osgDB/ReadFile" +#include "osgEarth/Controls" +#include "osgEarth/StringUtils" namespace ui = osgEarth::Util::Controls; diff --git a/Examples/ASIViewer/CMakeLists.txt b/Examples/ASIViewer/CMakeLists.txt index dacfc084c..0f777b9cd 100644 --- a/Examples/ASIViewer/CMakeLists.txt +++ b/Examples/ASIViewer/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_asiviewer ${PROJECT_FILES}) target_link_libraries(example_asiviewer PRIVATE simVis simUtil) set_target_properties(example_asiviewer PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - ASI Simple Viewer" + PROJECT_LABEL "ASI Simple Viewer" ) vsi_install_target(example_asiviewer SDK_Examples) diff --git a/Examples/AngleTest/CMakeLists.txt b/Examples/AngleTest/CMakeLists.txt index 38af1de93..4a0feb87d 100644 --- a/Examples/AngleTest/CMakeLists.txt +++ b/Examples/AngleTest/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_angletest ${PROJECT_FILES}) target_link_libraries(example_angletest PRIVATE simVis simUtil) set_target_properties(example_angletest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Angle Test" + PROJECT_LABEL "Angle Test" ) vsi_install_target(example_angletest SDK_Examples) diff --git a/Examples/AnimatedLine/CMakeLists.txt b/Examples/AnimatedLine/CMakeLists.txt index aa10502be..54bb547b8 100644 --- a/Examples/AnimatedLine/CMakeLists.txt +++ b/Examples/AnimatedLine/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_animatedline ${PROJECT_FILES}) target_link_libraries(example_animatedline PRIVATE simVis simUtil) set_target_properties(example_animatedline PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Animated Line" + PROJECT_LABEL "Animated Line" ) vsi_install_target(example_animatedline SDK_Examples) diff --git a/Examples/AntennaPattern/CMakeLists.txt b/Examples/AntennaPattern/CMakeLists.txt index cb226b4a7..2ae577f23 100644 --- a/Examples/AntennaPattern/CMakeLists.txt +++ b/Examples/AntennaPattern/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_antennapattern ${PROJECT_FILES}) target_link_libraries(example_antennapattern PRIVATE simVis simUtil) set_target_properties(example_antennapattern PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Antenna Pattern" + PROJECT_LABEL "Antenna Pattern" ) vsi_install_target(example_antennapattern SDK_Examples) diff --git a/Examples/ArticulatedModel/CMakeLists.txt b/Examples/ArticulatedModel/CMakeLists.txt index 63b0e6cee..81b6bcdd7 100644 --- a/Examples/ArticulatedModel/CMakeLists.txt +++ b/Examples/ArticulatedModel/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_articulatedmodel ${PROJECT_FILES}) target_link_libraries(example_articulatedmodel PRIVATE simVis simUtil) set_target_properties(example_articulatedmodel PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Articulated Model" + PROJECT_LABEL "Articulated Model" ) vsi_install_target(example_articulatedmodel SDK_Examples) diff --git a/Examples/AsyncModelLoading/CMakeLists.txt b/Examples/AsyncModelLoading/CMakeLists.txt index 92dcfd545..9fafdcf59 100644 --- a/Examples/AsyncModelLoading/CMakeLists.txt +++ b/Examples/AsyncModelLoading/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_asyncmodelloading ${PROJECT_FILES}) target_link_libraries(example_asyncmodelloading PRIVATE simVis simUtil) set_target_properties(example_asyncmodelloading PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Asynchronous Model Loading" + PROJECT_LABEL "Asynchronous Model Loading" ) vsi_install_target(example_asyncmodelloading SDK_Examples) diff --git a/Examples/BasicViewer/BasicViewer.cpp b/Examples/BasicViewer/BasicViewer.cpp index a58d53f34..1b0b316f1 100644 --- a/Examples/BasicViewer/BasicViewer.cpp +++ b/Examples/BasicViewer/BasicViewer.cpp @@ -25,7 +25,6 @@ * map control. It shows how to adjust window appearance, how to add or remove * inset views, and how to change the motion model. */ - #include "simNotify/Notify.h" #include "simCore/Common/Version.h" #include "simCore/Common/HighPerformanceGraphics.h" diff --git a/Examples/BasicViewer/CMakeLists.txt b/Examples/BasicViewer/CMakeLists.txt index dac7a52eb..a581703de 100644 --- a/Examples/BasicViewer/CMakeLists.txt +++ b/Examples/BasicViewer/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_basicviewer ${PROJECT_FILES}) target_link_libraries(example_basicviewer PRIVATE simVis simUtil) set_target_properties(example_basicviewer PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Basic Viewer" + PROJECT_LABEL "Basic Viewer" ) vsi_install_target(example_basicviewer SDK_Examples) diff --git a/Examples/BasicViewerText/BasicViewerText.cpp b/Examples/BasicViewerText/BasicViewerText.cpp index f6763e4d0..0b3ce921c 100644 --- a/Examples/BasicViewerText/BasicViewerText.cpp +++ b/Examples/BasicViewerText/BasicViewerText.cpp @@ -24,7 +24,7 @@ * BasicViewerText is the BasicViewer example with Help overlay removed and various HudManager HudText elements added. * It demonstrates HudText layout behaviors. */ - +#include "osgDB/ReadFile" #include "simNotify/Notify.h" #include "simCore/Common/Version.h" #include "simCore/Common/HighPerformanceGraphics.h" @@ -234,7 +234,7 @@ int main(int argc, char** argv) simVis::View* superHUD = new simVis::View(); superHUD->setUpViewAsHUD(mainView); mainView->getViewManager()->addView(superHUD); - simUtil::HudManager hm(superHUD); + simUtil::HudManager hm(superHUD, superHUD->getOrCreateHUD()); // Create a background for some of the text using a large hyphen osg::ref_ptr background1 = hm.createText("-", 130, 132, false, false); diff --git a/Examples/BasicViewerText/CMakeLists.txt b/Examples/BasicViewerText/CMakeLists.txt index c437e7021..c3b1ea216 100644 --- a/Examples/BasicViewerText/CMakeLists.txt +++ b/Examples/BasicViewerText/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_basicviewertext ${PROJECT_FILES}) target_link_libraries(example_basicviewertext PRIVATE simVis simUtil) set_target_properties(example_basicviewertext PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Basic Viewer Text" + PROJECT_LABEL "Basic Viewer Text" ) vsi_install_target(example_basicviewertext SDK_Examples) diff --git a/Examples/BeamTest/CMakeLists.txt b/Examples/BeamTest/CMakeLists.txt index a1d2b9ad4..7662cbcce 100644 --- a/Examples/BeamTest/CMakeLists.txt +++ b/Examples/BeamTest/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_beamtest ${PROJECT_FILES}) target_link_libraries(example_beamtest PRIVATE simVis simUtil) set_target_properties(example_beamtest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Beams Test" + PROJECT_LABEL "Beams Test" ) vsi_install_target(example_beamtest SDK_Examples) diff --git a/Examples/CMakeLists.txt b/Examples/CMakeLists.txt index 7e71e2ba8..64e51cbed 100644 --- a/Examples/CMakeLists.txt +++ b/Examples/CMakeLists.txt @@ -10,7 +10,6 @@ add_subdirectory(BasicViewer) add_subdirectory(BasicViewerText) add_subdirectory(CentroidEyePosition) add_subdirectory(CustomRenderingTest) -add_subdirectory(DBReader) add_subdirectory(GeoFencing) add_subdirectory(GOGAttachments) add_subdirectory(GOGReader) @@ -20,7 +19,6 @@ add_subdirectory(MapScale) add_subdirectory(MassiveData) add_subdirectory(ObserverMaker) add_subdirectory(Ocean) -add_subdirectory(Overhead) add_subdirectory(Periscope) add_subdirectory(Picking) add_subdirectory(PlatformSymbology) @@ -30,11 +28,16 @@ add_subdirectory(RangeTool) add_subdirectory(RCS) add_subdirectory(RFProp) add_subdirectory(RocketBurn) -add_subdirectory(LoadEarthFile) add_subdirectory(SkyModel) add_subdirectory(SimpleServer) add_subdirectory(TimestampedLayer) +if(SIM_HAVE_DB_SUPPORT) + add_subdirectory(DBReader) + add_subdirectory(LoadEarthFile) + add_subdirectory(Overhead) +endif() + if(QT_FOUND) add_subdirectory(Qt) add_subdirectory(QtActionItemModelTest) diff --git a/Examples/CentroidEyePosition/CMakeLists.txt b/Examples/CentroidEyePosition/CMakeLists.txt index dde31be94..1b11dfa67 100644 --- a/Examples/CentroidEyePosition/CMakeLists.txt +++ b/Examples/CentroidEyePosition/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_centroideyeposition ${PROJECT_FILES}) target_link_libraries(example_centroideyeposition PRIVATE simVis simUtil) set_target_properties(example_centroideyeposition PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Centroid Eye Position" + PROJECT_LABEL "Centroid Eye Position" ) vsi_install_target(example_centroideyeposition SDK_Examples) diff --git a/Examples/CustomRenderingTest/CMakeLists.txt b/Examples/CustomRenderingTest/CMakeLists.txt index 3289edb62..f001a8682 100644 --- a/Examples/CustomRenderingTest/CMakeLists.txt +++ b/Examples/CustomRenderingTest/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_customrenderingtest ${PROJECT_FILES}) target_link_libraries(example_customrenderingtest PRIVATE simVis simUtil) set_target_properties(example_customrenderingtest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Custom Rendering" + PROJECT_LABEL "Custom Rendering" ) vsi_install_target(example_customrenderingtest SDK_Examples) diff --git a/Examples/DBReader/CMakeLists.txt b/Examples/DBReader/CMakeLists.txt index 66b216a66..efb359bbe 100644 --- a/Examples/DBReader/CMakeLists.txt +++ b/Examples/DBReader/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_dbreader ${PROJECT_FILES}) target_link_libraries(example_dbreader PRIVATE simVis simUtil) set_target_properties(example_dbreader PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - DB Reader" + PROJECT_LABEL "DB Reader Test" ) vsi_install_target(example_dbreader SDK_Examples) diff --git a/Examples/DBReader/DBReader.cpp b/Examples/DBReader/DBReader.cpp index 9dcf6c356..71b77cfcf 100644 --- a/Examples/DBReader/DBReader.cpp +++ b/Examples/DBReader/DBReader.cpp @@ -33,7 +33,6 @@ #include "simCore/Common/HighPerformanceGraphics.h" #include "simUtil/ExampleResources.h" #include "simVis/Viewer.h" -#include "simVis/DBOptions.h" #include "simVis/osgEarthVersion.h" #include "simUtil/ExampleResources.h" #include "simVis/DBFormat.h" diff --git a/Examples/GOGAttachments/CMakeLists.txt b/Examples/GOGAttachments/CMakeLists.txt index 74e006e16..d0b05cddb 100644 --- a/Examples/GOGAttachments/CMakeLists.txt +++ b/Examples/GOGAttachments/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_gogattachments ${PROJECT_FILES}) target_link_libraries(example_gogattachments PRIVATE simVis simUtil) set_target_properties(example_gogattachments PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - GOG Attachments" + PROJECT_LABEL "GOG Attachments" ) vsi_install_target(example_gogattachments SDK_Examples) diff --git a/Examples/GOGReader/CMakeLists.txt b/Examples/GOGReader/CMakeLists.txt index d76591e95..7681e9131 100644 --- a/Examples/GOGReader/CMakeLists.txt +++ b/Examples/GOGReader/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_gogreader ${PROJECT_FILES}) target_link_libraries(example_gogreader PRIVATE simVis simUtil) set_target_properties(example_gogreader PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - GOG Reader" + PROJECT_LABEL "GOG Reader" ) vsi_install_target(example_gogreader SDK_Examples) diff --git a/Examples/GateTest/CMakeLists.txt b/Examples/GateTest/CMakeLists.txt index af7637f4f..b02f023a2 100644 --- a/Examples/GateTest/CMakeLists.txt +++ b/Examples/GateTest/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_gatetest ${PROJECT_FILES}) target_link_libraries(example_gatetest PRIVATE simVis simUtil) set_target_properties(example_gatetest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Gates Test" + PROJECT_LABEL "Gates Test" ) vsi_install_target(example_gatetest SDK_Examples) diff --git a/Examples/GeoFencing/CMakeLists.txt b/Examples/GeoFencing/CMakeLists.txt index 9616c38d6..890771e49 100644 --- a/Examples/GeoFencing/CMakeLists.txt +++ b/Examples/GeoFencing/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_geofencing ${PROJECT_FILES}) target_link_libraries(example_geofencing PRIVATE simVis simUtil) set_target_properties(example_geofencing PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - GeoFencing" + PROJECT_LABEL "GeoFencing" ) vsi_install_target(example_geofencing SDK_Examples) diff --git a/Examples/HudPositionManager/CMakeLists.txt b/Examples/HudPositionManager/CMakeLists.txt index f8ce3d310..303e5c4fe 100644 --- a/Examples/HudPositionManager/CMakeLists.txt +++ b/Examples/HudPositionManager/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_hudpositionmanager ${PROJECT_FILES}) target_link_libraries(example_hudpositionmanager PRIVATE simVis simUtil) set_target_properties(example_hudpositionmanager PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - HUD Position Manager" + PROJECT_LABEL "HUD Position Manager" ) vsi_install_target(example_hudpositionmanager SDK_Examples) diff --git a/Examples/ImageIcons/CMakeLists.txt b/Examples/ImageIcons/CMakeLists.txt index 6589ec7c8..14098d02c 100644 --- a/Examples/ImageIcons/CMakeLists.txt +++ b/Examples/ImageIcons/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_imageicons ${PROJECT_FILES}) target_link_libraries(example_imageicons PRIVATE simVis simUtil) set_target_properties(example_imageicons PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Image Icons" + PROJECT_LABEL "Image Icons" ) vsi_install_target(example_imageicons SDK_Examples) diff --git a/Examples/LOBTest/CMakeLists.txt b/Examples/LOBTest/CMakeLists.txt index 3844a2048..3400aa27e 100644 --- a/Examples/LOBTest/CMakeLists.txt +++ b/Examples/LOBTest/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_lobtest ${PROJECT_FILES}) target_link_libraries(example_lobtest PRIVATE simVis simUtil) set_target_properties(example_lobtest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Line of Bearing Test" + PROJECT_LABEL "Line of Bearing Test" ) vsi_install_target(example_lobtest SDK_Examples) diff --git a/Examples/LoadEarthFile/CMakeLists.txt b/Examples/LoadEarthFile/CMakeLists.txt index 9ac312ea2..706773676 100644 --- a/Examples/LoadEarthFile/CMakeLists.txt +++ b/Examples/LoadEarthFile/CMakeLists.txt @@ -8,6 +8,6 @@ add_executable(example_loadearthfile LoadEarthFile.cpp) target_link_libraries(example_loadearthfile PRIVATE simVis simUtil OSGDB OSGEARTH) set_target_properties(example_loadearthfile PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Load Earth File" + PROJECT_LABEL "Load Earth File" ) vsi_install_target(example_loadearthfile SDK_Examples) diff --git a/Examples/LocalGrid/CMakeLists.txt b/Examples/LocalGrid/CMakeLists.txt index ec199e762..4b9ebbb9f 100644 --- a/Examples/LocalGrid/CMakeLists.txt +++ b/Examples/LocalGrid/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_localgrid ${PROJECT_FILES}) target_link_libraries(example_localgrid PRIVATE simVis simUtil simData) set_target_properties(example_localgrid PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Local Grid" + PROJECT_LABEL "Local Grid" ) vsi_install_target(example_localgrid SDK_Examples) diff --git a/Examples/LocatorTest/CMakeLists.txt b/Examples/LocatorTest/CMakeLists.txt index 033dd4009..4a21b6580 100644 --- a/Examples/LocatorTest/CMakeLists.txt +++ b/Examples/LocatorTest/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_locatortest ${PROJECT_FILES}) target_link_libraries(example_locatortest PRIVATE simVis simUtil) set_target_properties(example_locatortest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Locator Test" + PROJECT_LABEL "Locator Test" ) vsi_install_target(example_locatortest SDK_Examples) diff --git a/Examples/MapScale/CMakeLists.txt b/Examples/MapScale/CMakeLists.txt index a41c52707..513aed128 100644 --- a/Examples/MapScale/CMakeLists.txt +++ b/Examples/MapScale/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_mapscale ${PROJECT_FILES}) target_link_libraries(example_mapscale PRIVATE simVis simUtil) set_target_properties(example_mapscale PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Map Scale" + PROJECT_LABEL "Map Scale" ) vsi_install_target(example_mapscale SDK_Examples) diff --git a/Examples/MassiveData/CMakeLists.txt b/Examples/MassiveData/CMakeLists.txt index bf9c0ddbf..f5843ad70 100644 --- a/Examples/MassiveData/CMakeLists.txt +++ b/Examples/MassiveData/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_massivedata ${PROJECT_FILES}) target_link_libraries(example_massivedata PRIVATE simVis simUtil) set_target_properties(example_massivedata PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Massive Data" + PROJECT_LABEL "Massive Data" ) vsi_install_target(example_massivedata SDK_Examples) diff --git a/Examples/ObserverMaker/CMakeLists.txt b/Examples/ObserverMaker/CMakeLists.txt index e751b1b9b..1392fc9c0 100644 --- a/Examples/ObserverMaker/CMakeLists.txt +++ b/Examples/ObserverMaker/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_observermaker ${PROJECT_FILES}) target_link_libraries(example_observermaker PRIVATE simVis) set_target_properties(example_observermaker PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Observer Maker" + PROJECT_LABEL "Observer Maker" ) vsi_install_target(example_observermaker SDK_Examples) diff --git a/Examples/Ocean/CMakeLists.txt b/Examples/Ocean/CMakeLists.txt index b92ea00eb..52b8be7a7 100644 --- a/Examples/Ocean/CMakeLists.txt +++ b/Examples/Ocean/CMakeLists.txt @@ -12,7 +12,7 @@ add_executable(example_ocean ${PROJECT_FILES}) target_link_libraries(example_ocean PRIVATE simVis simUtil) set_target_properties(example_ocean PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Ocean" + PROJECT_LABEL "Ocean" ) vsi_install_target(example_ocean SDK_Examples) diff --git a/Examples/Ocean/Ocean.cpp b/Examples/Ocean/Ocean.cpp index 7b450aff2..cc7d7e7f9 100644 --- a/Examples/Ocean/Ocean.cpp +++ b/Examples/Ocean/Ocean.cpp @@ -42,7 +42,6 @@ #include "simData/MemoryDataStore.h" #include "simVis/BathymetryGenerator.h" -#include "simVis/DBOptions.h" #include "simVis/osgEarthVersion.h" #include "simVis/Platform.h" #include "simVis/PlatformModel.h" diff --git a/Examples/Overhead/CMakeLists.txt b/Examples/Overhead/CMakeLists.txt index 476af69ea..91eb3c6fe 100644 --- a/Examples/Overhead/CMakeLists.txt +++ b/Examples/Overhead/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_overhead ${PROJECT_FILES}) target_link_libraries(example_overhead PRIVATE simVis simUtil) set_target_properties(example_overhead PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Overhead" + PROJECT_LABEL "Overhead" ) vsi_install_target(example_overhead SDK_Examples) diff --git a/Examples/Periscope/CMakeLists.txt b/Examples/Periscope/CMakeLists.txt index 79a5f4f1a..1529204b4 100644 --- a/Examples/Periscope/CMakeLists.txt +++ b/Examples/Periscope/CMakeLists.txt @@ -12,7 +12,7 @@ add_executable(example_periscope ${PROJECT_FILES}) target_link_libraries(example_periscope PRIVATE simVis simUtil) set_target_properties(example_periscope PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Periscope" + PROJECT_LABEL "Periscope" ) vsi_install_target(example_periscope SDK_Examples) diff --git a/Examples/Periscope/PeriscopeExample.cpp b/Examples/Periscope/PeriscopeExample.cpp index 412187f96..3c0ebd290 100644 --- a/Examples/Periscope/PeriscopeExample.cpp +++ b/Examples/Periscope/PeriscopeExample.cpp @@ -42,6 +42,7 @@ #include "simUtil/ExampleResources.h" #include "simUtil/HudManager.h" +#include "osgDB/ReadFile" #include "osgEarth/Controls" #include "osgEarth/SimpleOceanLayer" #include "osgEarth/Version" @@ -343,7 +344,7 @@ int main(int argc, char** argv) viewer->getMainView()->setCameraManipulator(NULL); // apply the reticle overlay. - simUtil::HudManager hudManager(viewer->getMainView()); + simUtil::HudManager hudManager(viewer->getMainView(), viewer->getMainView()->getOrCreateHUD()); if (reticle.valid()) hudManager.createImage(reticle.get(), 0, 0, 100, 100); diff --git a/Examples/Picking/CMakeLists.txt b/Examples/Picking/CMakeLists.txt index f5ba987e7..f423ee8ef 100644 --- a/Examples/Picking/CMakeLists.txt +++ b/Examples/Picking/CMakeLists.txt @@ -13,6 +13,6 @@ add_executable(example_picking ${PROJECT_FILES}) target_link_libraries(example_picking PRIVATE simVis simUtil) set_target_properties(example_picking PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Picking" + PROJECT_LABEL "Picking" ) vsi_install_target(example_picking SDK_Examples) diff --git a/Examples/PlanetariumViewTest/CMakeLists.txt b/Examples/PlanetariumViewTest/CMakeLists.txt index 6958ba3e4..8a9f69cd6 100644 --- a/Examples/PlanetariumViewTest/CMakeLists.txt +++ b/Examples/PlanetariumViewTest/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_planetariumviewtest ${PROJECT_FILES}) target_link_libraries(example_planetariumviewtest PRIVATE simVis simUtil) set_target_properties(example_planetariumviewtest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Planetarium View Test" + PROJECT_LABEL "Planetarium View Test" ) vsi_install_target(example_planetariumviewtest SDK_Examples) diff --git a/Examples/PlatformAzimElevViewTest/CMakeLists.txt b/Examples/PlatformAzimElevViewTest/CMakeLists.txt index f346ca0a4..234418f5a 100644 --- a/Examples/PlatformAzimElevViewTest/CMakeLists.txt +++ b/Examples/PlatformAzimElevViewTest/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_platformazimelevviewtest ${PROJECT_FILES}) target_link_libraries(example_platformazimelevviewtest PRIVATE simVis simUtil) set_target_properties(example_platformazimelevviewtest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Platform AzimElev View Test" + PROJECT_LABEL "Platform AzimElev View Test" ) vsi_install_target(example_platformazimelevviewtest SDK_Examples) diff --git a/Examples/PlatformSymbology/CMakeLists.txt b/Examples/PlatformSymbology/CMakeLists.txt index 7b90eeba4..1729bae2b 100644 --- a/Examples/PlatformSymbology/CMakeLists.txt +++ b/Examples/PlatformSymbology/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_platformsymbology ${PROJECT_FILES}) target_link_libraries(example_platformsymbology PRIVATE simVis simUtil) set_target_properties(example_platformsymbology PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Platform Symbology" + PROJECT_LABEL "Platform Symbology" ) vsi_install_target(example_platformsymbology SDK_Examples) diff --git a/Examples/PlatformSymbology/PlatformSymbology.cpp b/Examples/PlatformSymbology/PlatformSymbology.cpp index 4608d84e9..77a99ac10 100644 --- a/Examples/PlatformSymbology/PlatformSymbology.cpp +++ b/Examples/PlatformSymbology/PlatformSymbology.cpp @@ -59,6 +59,7 @@ /// paths to models #include "simUtil/ExampleResources.h" +#include "osgDB/ReadFile" #include "osgEarth/LatLongFormatter" #include "osgEarth/MGRSFormatter" #include "osgEarth/StringUtils" diff --git a/Examples/Projectors/CMakeLists.txt b/Examples/Projectors/CMakeLists.txt index f36e83069..f7770632e 100644 --- a/Examples/Projectors/CMakeLists.txt +++ b/Examples/Projectors/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_projectors ${PROJECT_FILES}) target_link_libraries(example_projectors PRIVATE simVis simUtil) set_target_properties(example_projectors PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Projectors" + PROJECT_LABEL "Projectors" ) vsi_install_target(example_projectors SDK_Examples) diff --git a/Examples/Qt/CMakeLists.txt b/Examples/Qt/CMakeLists.txt index 1094afc9f..081c3f7d1 100644 --- a/Examples/Qt/CMakeLists.txt +++ b/Examples/Qt/CMakeLists.txt @@ -20,7 +20,7 @@ target_link_libraries(example_qt PRIVATE simVis simUtil simQt) target_include_directories(example_qt PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(example_qt PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt Integration" + PROJECT_LABEL "Qt Integration" ) vsi_install_target(example_qt SDK_Examples) diff --git a/Examples/QtActionItemModelTest/CMakeLists.txt b/Examples/QtActionItemModelTest/CMakeLists.txt index ce7d89a9c..fc065f563 100644 --- a/Examples/QtActionItemModelTest/CMakeLists.txt +++ b/Examples/QtActionItemModelTest/CMakeLists.txt @@ -20,7 +20,7 @@ target_link_libraries(example_qtactionitemmodeltest PRIVATE simQt) target_include_directories(example_qtactionitemmodeltest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(example_qtactionitemmodeltest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt Action Item Model Test" + PROJECT_LABEL "Qt Action Item Model Test" ) vsi_install_target(example_qtactionitemmodeltest SDK_Examples) diff --git a/Examples/QtCategoryFilterTest/CMakeLists.txt b/Examples/QtCategoryFilterTest/CMakeLists.txt index 9fa4b3178..8754f5dc1 100644 --- a/Examples/QtCategoryFilterTest/CMakeLists.txt +++ b/Examples/QtCategoryFilterTest/CMakeLists.txt @@ -23,7 +23,7 @@ target_link_libraries(example_qtcategoryfiltertest PRIVATE simQt simData) target_include_directories(example_qtcategoryfiltertest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(example_qtcategoryfiltertest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt Category Filter Test" + PROJECT_LABEL "Qt Category Filter Test" ) vsi_install_target(example_qtcategoryfiltertest SDK_Examples) diff --git a/Examples/QtCategoryFilterTest/CategoryFilterTest.cpp b/Examples/QtCategoryFilterTest/CategoryFilterTest.cpp index 1bfbea565..0c8d960de 100644 --- a/Examples/QtCategoryFilterTest/CategoryFilterTest.cpp +++ b/Examples/QtCategoryFilterTest/CategoryFilterTest.cpp @@ -47,12 +47,12 @@ MainWindow::MainWindow(simData::DataStore* dataStore, QWidget *parent) connect(ui_->togglePushButton, SIGNAL(clicked()), this, SLOT(toggleState_())); // Configure the new Category Filter Widget - ui_->categoryFilterWidget2->setDataStore(dataStore); - connect(ui_->categoryFilterWidget2, SIGNAL(filterChanged(simData::CategoryFilter)), + ui_->categoryFilterWidget->setDataStore(dataStore); + connect(ui_->categoryFilterWidget, SIGNAL(filterChanged(simData::CategoryFilter)), ui_->breadcrumbs, SLOT(setFilter(simData::CategoryFilter))); connect(ui_->breadcrumbs, SIGNAL(filterEdited(simData::CategoryFilter)), - ui_->categoryFilterWidget2, SLOT(setFilter(simData::CategoryFilter))); - connect(ui_->categoryFilterWidget2, SIGNAL(filterChanged(simData::CategoryFilter)), + ui_->categoryFilterWidget, SLOT(setFilter(simData::CategoryFilter))); + connect(ui_->categoryFilterWidget, SIGNAL(filterChanged(simData::CategoryFilter)), this, SLOT(categoryFilterChanged_(simData::CategoryFilter))); connect(ui_->breadcrumbs, SIGNAL(filterEdited(simData::CategoryFilter)), this, SLOT(categoryFilterChanged_(simData::CategoryFilter))); @@ -110,7 +110,7 @@ void MainWindow::toggleState_() { simData::CategoryFilter* filter = new simData::CategoryFilter(dataStore_, true); filter->updateAll(state_); - ui_->categoryFilterWidget2->setFilter(*filter); + ui_->categoryFilterWidget->setFilter(*filter); delete filter; state_ = !state_; } diff --git a/Examples/QtCategoryFilterTest/MainWindow.ui b/Examples/QtCategoryFilterTest/MainWindow.ui index 1b75ad1b5..ebbcd51b8 100644 --- a/Examples/QtCategoryFilterTest/MainWindow.ui +++ b/Examples/QtCategoryFilterTest/MainWindow.ui @@ -20,7 +20,7 @@ - + @@ -65,9 +65,9 @@
simQt/CategoryDataBreadcrumbs.h
- simQt::CategoryFilterWidget2 + simQt::CategoryFilterWidget QWidget -
simQt/CategoryTreeModel2.h
+
simQt/CategoryTreeModel.h
diff --git a/Examples/QtColorWidgetTest/CMakeLists.txt b/Examples/QtColorWidgetTest/CMakeLists.txt index 39e5da51d..81d6607b1 100644 --- a/Examples/QtColorWidgetTest/CMakeLists.txt +++ b/Examples/QtColorWidgetTest/CMakeLists.txt @@ -26,7 +26,7 @@ target_link_libraries(example_qtcolorwidgettest PRIVATE simQt) target_include_directories(example_qtcolorwidgettest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(example_qtcolorwidgettest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt Color Widget Test" + PROJECT_LABEL "Qt Color Widget Test" ) vsi_install_target(example_qtcolorwidgettest SDK_Examples) diff --git a/Examples/QtConsoleDataModel/CMakeLists.txt b/Examples/QtConsoleDataModel/CMakeLists.txt index 5408c0903..290040607 100644 --- a/Examples/QtConsoleDataModel/CMakeLists.txt +++ b/Examples/QtConsoleDataModel/CMakeLists.txt @@ -23,7 +23,7 @@ target_link_libraries(example_qtconsoledatamodel PRIVATE simQt simCore) target_include_directories(example_qtconsoledatamodel PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(example_qtconsoledatamodel PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt Console Data Model" + PROJECT_LABEL "Qt Console Data Model" ) vsi_install_target(example_qtconsoledatamodel SDK_Examples) diff --git a/Examples/QtDataTableViewTest/CMakeLists.txt b/Examples/QtDataTableViewTest/CMakeLists.txt index 3a25acf61..85036ca2f 100644 --- a/Examples/QtDataTableViewTest/CMakeLists.txt +++ b/Examples/QtDataTableViewTest/CMakeLists.txt @@ -23,7 +23,7 @@ target_link_libraries(example_qtdatatableviewtest PRIVATE simQt simData simUtil) target_include_directories(example_qtdatatableviewtest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(example_qtdatatableviewtest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt Data Table View Test" + PROJECT_LABEL "Qt Data Table View Test" ) vsi_install_target(example_qtdatatableviewtest SDK_Examples) diff --git a/Examples/QtDockableViews/CMakeLists.txt b/Examples/QtDockableViews/CMakeLists.txt index 1ee9bfb45..30901e6c3 100644 --- a/Examples/QtDockableViews/CMakeLists.txt +++ b/Examples/QtDockableViews/CMakeLists.txt @@ -21,7 +21,7 @@ target_link_libraries(example_qtdockableviews PRIVATE simVis simUtil simQt) target_include_directories(example_qtdockableviews PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(example_qtdockableviews PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt Dockable Views" + PROJECT_LABEL "Qt Dockable Views" ) vsi_install_target(example_qtdockableviews SDK_Examples) diff --git a/Examples/QtEntityLineEditTest/CMakeLists.txt b/Examples/QtEntityLineEditTest/CMakeLists.txt index f25e12746..04d3566f1 100644 --- a/Examples/QtEntityLineEditTest/CMakeLists.txt +++ b/Examples/QtEntityLineEditTest/CMakeLists.txt @@ -23,7 +23,7 @@ target_link_libraries(example_qtentitylineedittest PRIVATE simQt simData simVis) target_include_directories(example_qtentitylineedittest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(example_qtentitylineedittest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt Entity Line Edit Test" + PROJECT_LABEL "Qt Entity Line Edit Test" ) vsi_install_target(example_qtentitylineedittest SDK_Examples) diff --git a/Examples/QtEntityViewTest/CMakeLists.txt b/Examples/QtEntityViewTest/CMakeLists.txt index 61acb445f..41e634b45 100644 --- a/Examples/QtEntityViewTest/CMakeLists.txt +++ b/Examples/QtEntityViewTest/CMakeLists.txt @@ -26,7 +26,7 @@ endif() target_include_directories(example_qtentityviewtest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(example_qtentityviewtest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt Entity View Test" + PROJECT_LABEL "Qt Entity View Test" ) vsi_install_target(example_qtentityviewtest SDK_Examples) diff --git a/Examples/QtFileSelectorTest/CMakeLists.txt b/Examples/QtFileSelectorTest/CMakeLists.txt index 2c26aa3a8..1908a6c80 100644 --- a/Examples/QtFileSelectorTest/CMakeLists.txt +++ b/Examples/QtFileSelectorTest/CMakeLists.txt @@ -26,7 +26,7 @@ target_link_libraries(example_qtfileselectortest PRIVATE simQt simCore) target_include_directories(example_qtfileselectortest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(example_qtfileselectortest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt File Selector Test" + PROJECT_LABEL "Qt File Selector Test" ) vsi_install_target(example_qtfileselectortest SDK_Examples) diff --git a/Examples/QtThreadExample/CMakeLists.txt b/Examples/QtThreadExample/CMakeLists.txt index 02cb33115..9b9f2a360 100644 --- a/Examples/QtThreadExample/CMakeLists.txt +++ b/Examples/QtThreadExample/CMakeLists.txt @@ -35,7 +35,7 @@ target_link_libraries(example_qtthreadexample PRIVATE simCore simData simVis target_include_directories(example_qtthreadexample PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(example_qtthreadexample PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt Thread Example" + PROJECT_LABEL "Qt Thread Example" ) vsi_install_target(example_qtthreadexample SDK_Examples) diff --git a/Examples/QtTimeButtons/CMakeLists.txt b/Examples/QtTimeButtons/CMakeLists.txt index e7bb35066..50e75ed3a 100644 --- a/Examples/QtTimeButtons/CMakeLists.txt +++ b/Examples/QtTimeButtons/CMakeLists.txt @@ -21,7 +21,7 @@ target_link_libraries(example_qttimebuttons PRIVATE simQt simCore target_include_directories(example_qttimebuttons PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(example_qttimebuttons PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt Time Buttons" + PROJECT_LABEL "Qt Time Buttons" ) vsi_install_target(example_qttimebuttons SDK_Examples) diff --git a/Examples/QtTimeWidgetTest/CMakeLists.txt b/Examples/QtTimeWidgetTest/CMakeLists.txt index 99a3290e2..bf78263e4 100644 --- a/Examples/QtTimeWidgetTest/CMakeLists.txt +++ b/Examples/QtTimeWidgetTest/CMakeLists.txt @@ -24,7 +24,7 @@ target_link_libraries(example_qttimewidgettest PRIVATE simQt simCore) target_include_directories(example_qttimewidgettest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(example_qttimewidgettest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt Time Widget Test" + PROJECT_LABEL "Qt Time Widget Test" ) vsi_install_target(example_qttimewidgettest SDK_Examples) diff --git a/Examples/QtUnitsWidgetTest/CMakeLists.txt b/Examples/QtUnitsWidgetTest/CMakeLists.txt index acca63bec..2a9c09a8f 100644 --- a/Examples/QtUnitsWidgetTest/CMakeLists.txt +++ b/Examples/QtUnitsWidgetTest/CMakeLists.txt @@ -24,7 +24,7 @@ target_link_libraries(example_qtunitswidgettest PRIVATE simQt) target_include_directories(example_qtunitswidgettest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(example_qtunitswidgettest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt Units Widget Test" + PROJECT_LABEL "Qt Units Widget Test" ) vsi_install_target(example_qtunitswidgettest SDK_Examples) diff --git a/Examples/QtViewManagerDataModel/CMakeLists.txt b/Examples/QtViewManagerDataModel/CMakeLists.txt index b27b59b9c..debe855b5 100644 --- a/Examples/QtViewManagerDataModel/CMakeLists.txt +++ b/Examples/QtViewManagerDataModel/CMakeLists.txt @@ -17,7 +17,7 @@ add_executable(example_qtviewmanagerdatamodel ${PROJECT_FILES} ${PROJECT_MOC_FIL target_link_libraries(example_qtviewmanagerdatamodel PRIVATE simVis simUtil simQt) set_target_properties(example_qtviewmanagerdatamodel PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt View Manager Data Model" + PROJECT_LABEL "Qt View Manager Data Model" ) vsi_install_target(example_qtviewmanagerdatamodel SDK_Examples) diff --git a/Examples/QtViewManagerTest/CMakeLists.txt b/Examples/QtViewManagerTest/CMakeLists.txt index 37d0e9cde..23a68d42d 100644 --- a/Examples/QtViewManagerTest/CMakeLists.txt +++ b/Examples/QtViewManagerTest/CMakeLists.txt @@ -15,7 +15,7 @@ add_executable(example_qtviewmanagertest ${PROJECT_FILES}) target_link_libraries(example_qtviewmanagertest PRIVATE simVis simUtil simQt) set_target_properties(example_qtviewmanagertest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Qt View Manager Test" + PROJECT_LABEL "Qt View Manager Test" ) vsi_install_target(example_qtviewmanagertest SDK_Examples) diff --git a/Examples/RCS/CMakeLists.txt b/Examples/RCS/CMakeLists.txt index d8ba8760c..17aa23378 100644 --- a/Examples/RCS/CMakeLists.txt +++ b/Examples/RCS/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_rcs ${PROJECT_FILES}) target_link_libraries(example_rcs PRIVATE simVis simUtil) set_target_properties(example_rcs PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - RCS" + PROJECT_LABEL "RCS" ) vsi_install_target(example_rcs SDK_Examples) diff --git a/Examples/RFProp/CMakeLists.txt b/Examples/RFProp/CMakeLists.txt index f167cc339..0432f597e 100644 --- a/Examples/RFProp/CMakeLists.txt +++ b/Examples/RFProp/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_rfprop ${PROJECT_FILES}) target_link_libraries(example_rfprop PRIVATE simVis simUtil) set_target_properties(example_rfprop PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - RF Propagation" + PROJECT_LABEL "RF Propagation" ) vsi_install_target(example_rfprop SDK_Examples) diff --git a/Examples/RadialLOS/CMakeLists.txt b/Examples/RadialLOS/CMakeLists.txt index 65fc0c21a..038da3933 100644 --- a/Examples/RadialLOS/CMakeLists.txt +++ b/Examples/RadialLOS/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_radiallos ${PROJECT_FILES}) target_link_libraries(example_radiallos PRIVATE simVis simUtil) set_target_properties(example_radiallos PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Radial LOS" + PROJECT_LABEL "Radial LOS" ) vsi_install_target(example_radiallos SDK_Examples) diff --git a/Examples/RadialLOS/ExampleRadialLOS.cpp b/Examples/RadialLOS/ExampleRadialLOS.cpp index f9aa120b9..55d879bf4 100644 --- a/Examples/RadialLOS/ExampleRadialLOS.cpp +++ b/Examples/RadialLOS/ExampleRadialLOS.cpp @@ -253,7 +253,7 @@ namespace */ osg::Node* createP2PGraphics(AppData& app) { - MapNode* mapNode = app.mapNode; + MapNode* mapNode = app.mapNode.get(); osgEarth::SphereDragger* dragger = new osgEarth::SphereDragger(mapNode); dragger->setPosition(GeoPoint(mapNode->getMapSRS(), RLOS_LON, RLOS_LAT)); dragger->setColor(simVis::Color::White); diff --git a/Examples/RangeTool/CMakeLists.txt b/Examples/RangeTool/CMakeLists.txt index c7c9c32af..29664df2e 100644 --- a/Examples/RangeTool/CMakeLists.txt +++ b/Examples/RangeTool/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_rangetool ${PROJECT_FILES}) target_link_libraries(example_rangetool PRIVATE simVis simUtil) set_target_properties(example_rangetool PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Range Tool" + PROJECT_LABEL "Range Tool" ) vsi_install_target(example_rangetool SDK_Examples) diff --git a/Examples/RocketBurn/CMakeLists.txt b/Examples/RocketBurn/CMakeLists.txt index da0203025..d34c49d05 100644 --- a/Examples/RocketBurn/CMakeLists.txt +++ b/Examples/RocketBurn/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_rocketburn ${PROJECT_FILES}) target_link_libraries(example_rocketburn PRIVATE simVis simUtil) set_target_properties(example_rocketburn PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Rocket Burn" + PROJECT_LABEL "Rocket Burn" ) vsi_install_target(example_rocketburn SDK_Examples) diff --git a/Examples/RocketBurn/RocketBurn.cpp b/Examples/RocketBurn/RocketBurn.cpp index b8abdbd31..0df9ef242 100644 --- a/Examples/RocketBurn/RocketBurn.cpp +++ b/Examples/RocketBurn/RocketBurn.cpp @@ -19,6 +19,7 @@ * disclose, or release this software. * */ +#include "osgDB/ReadFile" #include "simCore/Common/Version.h" #include "simCore/Common/HighPerformanceGraphics.h" #include "simData/MemoryDataStore.h" diff --git a/Examples/SimpleServer/CMakeLists.txt b/Examples/SimpleServer/CMakeLists.txt index 1ae360f59..ffddbd5c9 100644 --- a/Examples/SimpleServer/CMakeLists.txt +++ b/Examples/SimpleServer/CMakeLists.txt @@ -20,7 +20,7 @@ add_executable(example_simpleserver ${PROJECT_FILES}) target_link_libraries(example_simpleserver PRIVATE simVis simUtil) set_target_properties(example_simpleserver PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Simple Server" + PROJECT_LABEL "Simple Server" ) vsi_install_target(example_simpleserver SDK_Examples) diff --git a/Examples/SkyModel/CMakeLists.txt b/Examples/SkyModel/CMakeLists.txt index cace9e069..2ef0432c5 100644 --- a/Examples/SkyModel/CMakeLists.txt +++ b/Examples/SkyModel/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_skymodel ${PROJECT_FILES}) target_link_libraries(example_skymodel PRIVATE simVis simUtil) set_target_properties(example_skymodel PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Sky Model" + PROJECT_LABEL "Sky Model" ) vsi_install_target(example_skymodel SDK_Examples) diff --git a/Examples/TimestampedLayer/CMakeLists.txt b/Examples/TimestampedLayer/CMakeLists.txt index 1c97a5fc7..c466165a8 100644 --- a/Examples/TimestampedLayer/CMakeLists.txt +++ b/Examples/TimestampedLayer/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_timestampedlayer ${PROJECT_FILES}) target_link_libraries(example_timestampedlayer PRIVATE simVis simUtil) set_target_properties(example_timestampedlayer PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Timestamped Layer" + PROJECT_LABEL "Timestamped Layer" ) vsi_install_target(example_timestampedlayer SDK_Examples) diff --git a/Examples/TimestampedLayer/ExampleTimestampedLayer.cpp b/Examples/TimestampedLayer/ExampleTimestampedLayer.cpp index 9c4d7a84a..287973474 100644 --- a/Examples/TimestampedLayer/ExampleTimestampedLayer.cpp +++ b/Examples/TimestampedLayer/ExampleTimestampedLayer.cpp @@ -20,6 +20,7 @@ * */ +#include "osgDB/ReadFile" #include "osgEarth/Map" #include "osgEarth/MapNode" #include "osgEarth/VisibleLayer" diff --git a/Examples/TrackHistoryTest/CMakeLists.txt b/Examples/TrackHistoryTest/CMakeLists.txt index eb6b1e197..d30560d69 100644 --- a/Examples/TrackHistoryTest/CMakeLists.txt +++ b/Examples/TrackHistoryTest/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_trackhistorytest ${PROJECT_FILES}) target_link_libraries(example_trackhistorytest PRIVATE simVis simUtil) set_target_properties(example_trackhistorytest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - Track History Test" + PROJECT_LABEL "Track History Test" ) vsi_install_target(example_trackhistorytest SDK_Examples) diff --git a/Examples/ViewManagerTest/CMakeLists.txt b/Examples/ViewManagerTest/CMakeLists.txt index 89605cd1d..cc6f3a58d 100644 --- a/Examples/ViewManagerTest/CMakeLists.txt +++ b/Examples/ViewManagerTest/CMakeLists.txt @@ -12,6 +12,6 @@ add_executable(example_viewmanagertest ${PROJECT_FILES}) target_link_libraries(example_viewmanagertest PRIVATE simVis simUtil) set_target_properties(example_viewmanagertest PROPERTIES FOLDER "Examples" - PROJECT_LABEL "Example - View Manager Test" + PROJECT_LABEL "View Manager Test" ) vsi_install_target(example_viewmanagertest SDK_Examples) diff --git a/Plugins/CMakeLists.txt b/Plugins/CMakeLists.txt index ca300b023..cee1e9df0 100644 --- a/Plugins/CMakeLists.txt +++ b/Plugins/CMakeLists.txt @@ -1,10 +1,3 @@ -# DB plugin no longer needed with osgEarth 3.x -if(SQLITE3_LIBRARY_RELEASE_NAME) - add_subdirectory(OSGEarthDBDriver) -else() - message(STATUS "Skipping osgEarth .db Driver plug-in because of missing dependencies.") -endif() - if(QT_FOUND) option(ENABLE_QTDESIGNER_WIDGETS "Build Qt Designer Plugins for simQt widgets" ON) if(ENABLE_QTDESIGNER_WIDGETS) diff --git a/Plugins/OSGEarthDBDriver/CMakeLists.txt b/Plugins/OSGEarthDBDriver/CMakeLists.txt deleted file mode 100644 index 9d276a300..000000000 --- a/Plugins/OSGEarthDBDriver/CMakeLists.txt +++ /dev/null @@ -1,50 +0,0 @@ -if(NOT SQLITE3_FOUND OR NOT OSG_FOUND OR NOT OPENTHREADS_FOUND OR NOT OSGEARTH_FOUND) - return() -endif() - -project(PLUGIN_OSGEARTH_DB_DRIVER) - -set(PROJECT_SRC - src/Plugin.cpp - src/DBTileSource.cpp - src/QSError.cpp - src/QSNodeID96.cpp - src/QSPosXYExtents.cpp - src/SQLiteDataBaseReadUtil.cpp -) - -set(PROJECT_HEADERS - include/DBTileSource.h - include/QSCommon.h - include/QSError.h - include/QSNodeID96.h - include/QSPosXYExtents.h - include/SQLiteDataBaseReadUtil.h - include/swapbytes.h -) - -add_library(osgdb_osgearth_db SHARED ${PROJECT_HEADERS} ${PROJECT_SRC}) -target_include_directories(osgdb_osgearth_db PRIVATE include ${SIMDIS_SDK_BINARY_DIR}/simVis/include) -target_link_libraries(osgdb_osgearth_db - PRIVATE - SQLITE3 - OSG OSGDB OPENTHREADS - OSGEARTH - simCore -) -target_compile_definitions(osgdb_osgearth_db PRIVATE USE_SIMDIS_SDK) -if(SDK_BIG_ENDIAN) - target_compile_definitions(osgdb_osgearth_db PRIVATE SIM_BIG_ENDIAN) -else() - target_compile_definitions(osgdb_osgearth_db PRIVATE SIM_LITTLE_ENDIAN) -endif() -if(NOT WIN32) - target_compile_definitions(osgdb_osgearth_db PRIVATE Linux) -endif() - -set_target_properties(osgdb_osgearth_db PROPERTIES - PREFIX "" - FOLDER "OSG Plugins" - PROJECT_LABEL "Plugin - OSGEarth .db Driver" -) -vsi_install_shared_library(osgdb_osgearth_db SDK_OSG_Plugins "${INSTALLSETTINGS_OSGPLUGIN_DIR}") diff --git a/Plugins/OSGEarthDBDriver/include/DBTileSource.h b/Plugins/OSGEarthDBDriver/include/DBTileSource.h deleted file mode 100644 index d8230b54d..000000000 --- a/Plugins/OSGEarthDBDriver/include/DBTileSource.h +++ /dev/null @@ -1,89 +0,0 @@ -/* -*- mode: c++ -*- */ -/**************************************************************************** - ***** ***** - ***** Classification: UNCLASSIFIED ***** - ***** Classified By: ***** - ***** Declassify On: ***** - ***** ***** - **************************************************************************** - * - * - * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. - * EW Modeling & Simulation, Code 5773 - * 4555 Overlook Ave. - * Washington, D.C. 20375-5339 - * - * License for source code at https://simdis.nrl.navy.mil/License.aspx - * - * The U.S. Government retains all rights to use, duplicate, distribute, - * disclose, or release this software. - * - */ - -#ifndef SIMDIS_PLUGIN_OSGEARTH_DB_DRIVER_H -#define SIMDIS_PLUGIN_OSGEARTH_DB_DRIVER_H 1 - -#include "sqlite3.h" -#include "osgEarth/TileSource" -#include "simCore/Time/Utils.h" -#include "simVis/DBOptions.h" -#include "QSPosXYExtents.h" -#include "SQLiteDataBaseReadUtil.h" - -namespace simVis_db -{ - class DBTileSource : public osgEarth::TileSource - { - public: - /** Constructs a new driver for reading .DB raster files */ - DBTileSource(const osgEarth::TileSourceOptions& options); - - /// TileSource interface - virtual osgEarth::Status initialize(const osgDB::Options* dbOptions); - virtual osg::Image* createImage(const osgEarth::TileKey& key, osgEarth::ProgressCallback* progress); - virtual osg::HeightField* createHeightField(const osgEarth::TileKey& key, osgEarth::ProgressCallback* progress); - virtual std::string getExtension() const; - virtual int getPixelsPerTile() const; - - protected: - virtual ~DBTileSource(); - - private: - bool decodeRaster_( - int rasterFormat, - const char* inputBuffer, - int inputBufferLen, - osg::ref_ptr& out_image); - - osg::Image* createImage_(const osgEarth::TileKey& key, bool isHeightField); - - const simVis::DBOptions options_; - std::string pathname_; - sqlite3* db_; - SQLiteDataBaseReadUtil dbUtil_; - - // layer metadata - /** Defined in RasterCommon.h */ - int rasterFormat_; - /** AKA tile size */ - int pixelLength_; - int shallowLevel_; - int deepLevel_; - PosXPosYExtents extents_[6]; - std::string source_; - std::string classification_; - std::string description_; - bool timeSpecified_; - simCore::TimeStamp timeStamp_; - - osg::ref_ptr pngReader_; - osg::ref_ptr jpgReader_; - osg::ref_ptr tifReader_; - osg::ref_ptr rgbReader_; - }; - -} // namespace simVis_db - - -#endif // SIMDIS_PLUGIN_OSGEARTH_DB_DRIVER_H - diff --git a/Plugins/OSGEarthDBDriver/include/RasterCommon.h b/Plugins/OSGEarthDBDriver/include/RasterCommon.h deleted file mode 100644 index e69de29bb..000000000 diff --git a/Plugins/OSGEarthDBDriver/include/swapbytes.h b/Plugins/OSGEarthDBDriver/include/swapbytes.h deleted file mode 100644 index e80d007f4..000000000 --- a/Plugins/OSGEarthDBDriver/include/swapbytes.h +++ /dev/null @@ -1,780 +0,0 @@ -/* -*- mode: c++ -*- */ -/**************************************************************************** - ***** ***** - ***** Classification: UNCLASSIFIED ***** - ***** Classified By: ***** - ***** Declassify On: ***** - ***** ***** - **************************************************************************** - * - * - * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. - * EW Modeling & Simulation, Code 5773 - * 4555 Overlook Ave. - * Washington, D.C. 20375-5339 - * - * License for source code at https://simdis.nrl.navy.mil/License.aspx - * - * The U.S. Government retains all rights to use, duplicate, distribute, - * disclose, or release this software. - * - */ - -#ifndef SIMUTIL_SWAPBYTES_H -#define SIMUTIL_SWAPBYTES_H - -#include -#include -#include -#include -#include "simCore/Common/Common.h" - -#if !defined(SIM_LITTLE_ENDIAN) && !defined(SIM_BIG_ENDIAN) -#if defined(X86) || defined(ALPHA) || defined(__x86_64__) || defined(_WIN64) || defined(WIN32) -#define SIM_LITTLE_ENDIAN -#endif -#endif - -#ifndef BYTESWAPSHIFT - -namespace simVis_db -{ - template - inline - void swap_bytes(T *const value, const size_t nItems = 1) - { - // using namespace std; - for (size_t i = 0; i < nItems; ++i) - { - char *ptr = (char*)&value[i]; // Treat value as an array of bytes - size_t size = sizeof(T); - size_t halfSize = size >> 1; - size_t end = size - 1; - for (size_t i = 0; i < halfSize; ++i) - std::swap(ptr[end - i], ptr[i]); - } - } - - template - inline - T swap_bytes_return(const T &value, const size_t nItems = 1) - { - T temp = value; - swap_bytes(&temp); - return temp; - } - -#ifdef SIM_LITTLE_ENDIAN - - template - inline - void make_little_endian_(T *const value) - { - } - - template - inline - void make_big_endian_(T *const value) - { - swap_bytes(value); - } - -#else - - template - inline - void make_little_endian_(T *const value) - { - swap_bytes(value); - } - - template - inline - void make_big_endian_(T *const value) - { - } - -#endif - - inline - void make_big_endian(char *const val) { make_big_endian_(val); } - inline - void make_big_endian(int8_t *const val) { make_big_endian_(val); } - inline - void make_big_endian(uint8_t *const val) { make_big_endian_(val); } - inline - void make_big_endian(int16_t *const val) { make_big_endian_(val); } - inline - void make_big_endian(uint16_t *const val) { make_big_endian_(val); } - inline - void make_big_endian(int32_t *const val) { make_big_endian_(val); } - inline - void make_big_endian(uint32_t *const val) { make_big_endian_(val); } - inline - void make_big_endian(int64_t *const val) { make_big_endian_(val); } - inline - void make_big_endian(uint64_t *const val) { make_big_endian_(val); } - inline - void make_big_endian(float *const val) { make_big_endian_(val); } - inline - void make_big_endian(double *const val) { make_big_endian_(val); } - - inline - void make_little_endian(char *const val) { make_little_endian_(val); } - inline - void make_little_endian(int8_t *const val) { make_little_endian_(val); } - inline - void make_little_endian(uint8_t *const val) { make_little_endian_(val); } - inline - void make_little_endian(int16_t *const val) { make_little_endian_(val); } - inline - void make_little_endian(uint16_t *const val) { make_little_endian_(val); } - inline - void make_little_endian(int32_t *const val) { make_little_endian_(val); } - inline - void make_little_endian(uint32_t *const val) { make_little_endian_(val); } - inline - void make_little_endian(int64_t *const val) { make_little_endian_(val); } - inline - void make_little_endian(uint64_t *const val) { make_little_endian_(val); } - inline - void make_little_endian(float *const val) { make_little_endian_(val); } - inline - void make_little_endian(double *const val) { make_little_endian_(val); } - - template - inline - void make_big_endian(T *const value, const size_t nItems) - { - for (size_t i = 0; i < nItems; ++i) - { - make_big_endian(&value[i]); - } - } - - template - inline - void make_little_endian(T *const value, const size_t nItems) - { - for (size_t i = 0; i < nItems; ++i) - { - make_little_endian(&value[i]); - } - } - - template - inline - size_t beread(FILE *stream, T *const val, const size_t nItems = 1) - { - size_t nItemsRead = fread(val, sizeof(T), nItems, stream); - make_big_endian(val, nItemsRead); - return nItemsRead; - } - - template - inline - size_t bewrite(FILE *stream, const T *const val, const size_t nItems = 1) - { - size_t nItemsWrote = 0; - for (size_t i = 0; i < nItems; ++i) - { - T temp = val[i]; - make_big_endian(&temp); - if (fwrite(&temp, sizeof(T), 1, stream) != 1) - break; - ++nItemsWrote; - } - return nItemsWrote; - } - - template - inline - size_t leread(FILE *stream, T *const val, const size_t nItems = 1) - { - size_t nItemsRead = fread(val, sizeof(T), nItems, stream); - make_little_endian(val, nItemsRead); - return nItemsRead; - } - - template - inline - size_t lewrite(FILE *stream, const T *const val, const size_t nItems = 1) - { - size_t nItemsWrote = 0; - for (size_t i = 0; i < nItems; ++i) - { - T temp = val[i]; - make_little_endian(&temp); - if (fwrite(&temp, sizeof(T), 1, stream) != 1) - break; - ++nItemsWrote; - } - return nItemsWrote; - } - - template - inline - size_t beread(std::istream &stream, T *const val, const std::streamsize nItems = 1) - { - stream.read((char *)val, nItems * sizeof(T)); - size_t nItemsRead = stream.gcount() / sizeof(T); - make_big_endian(val, nItemsRead); - return nItemsRead; - } - - template - inline - size_t bewrite(std::ostream &stream, const T *const val, const size_t nItems = 1) - { - size_t nItemsWrote = 0; - for (size_t i = 0; i < nItems; ++i) - { - T temp = val[i]; - make_big_endian(&temp); - stream.write((const char *)&temp, sizeof(T)); - if (stream.bad()) - break; - ++nItemsWrote; - } - return nItemsWrote; - } - - template - inline - size_t leread(std::istream &stream, T *const val, const size_t nItems = 1) - { - stream.read((char *)val, nItems * sizeof(T)); - size_t nItemsRead = stream.gcount() / sizeof(T); - make_little_endian(val, nItemsRead); - return nItemsRead; - } - - template - inline - size_t lewrite(std::ostream &stream, const T *const val, const size_t nItems = 1) - { - size_t nItemsWrote = 0; - for (size_t i = 0; i < nItems; ++i) - { - T temp = val[i]; - make_little_endian(&temp); - stream.write((const char *)&temp, sizeof(T)); - if (stream.bad()) - break; - ++nItemsWrote; - } - return nItemsWrote; - } - - template - inline - size_t beread(const void *const stream, T *const val, const size_t nItems = 1) - { - memcpy(val, stream, nItems * sizeof(T)); - make_big_endian(val, nItems); - return nItems; - } - - template - inline - size_t bewrite(void *const stream, const T *const val, const size_t nItems = 1) - { - memcpy(stream, val, nItems * sizeof(T)); - make_big_endian((T *)stream, nItems); - return nItems; - } - - template - inline - size_t leread(const void *const stream, T *const val, const size_t nItems = 1) - { - memcpy(val, stream, nItems * sizeof(T)); - make_little_endian(val, nItems); - return nItems; - } - - template - inline - size_t lewrite(void *const stream, const T *const val, const size_t nItems = 1) - { - memcpy(stream, val, nItems * sizeof(T)); - make_little_endian((T *)stream, nItems); - return nItems; - } - -#else - -// Swap macros. -#define SWAP16(val)((((val)>>8)&0xff)|(((val)<<8)&0xff00)) -#define SWAP32(val)((((val)>>24)&0xff)|(((val)>>8)&0xff00)|(((val)<<8)&0xff0000)|(((val)<<24)&0xff000000)) -#ifdef __GNUC__ -#define SWAP64(val)((((val)>>56)&0xffLL)|(((val)>>40)&0xff00LL)|(((val)>>24)&0xff0000LL)|(((val)>>8)&0xff000000LL)| \ - (((val)<<8)&0xff00000000LL)|(((val)<<24)&0xff0000000000LL)|(((val)<<40)&0xff000000000000LL)|(((val)<<56)&0xff00000000000000LL)) -#else -#define SWAP64(val)((((val)>>56)&0xffL)|(((val)>>40)&0xff00L)|(((val)>>24)&0xff0000L)|(((val)>>8)&0xff000000L)| \ - (((val)<<8)&0xff00000000L)|(((val)<<24)&0xff0000000000L)|(((val)<<40)&0xff000000000000L)|(((val)<<56)&0xff00000000000000L)) -#endif - - // Generic byte swapping routines. - template inline void swap_bytes(T *const value) - { - char *ptr=(char*)(void*)value; // Treat value as an array of bytes - size_t size = sizeof(T); - register size_t halfSize = size >> 1; - register size_t end = size - 1; - for (register size_t i = 0; i < halfSize; ++i) - std::swap(ptr[end - i], ptr[i]); - } - - template inline void swap_bytes(T *const value, register const size_t nItems) - { - for (register size_t i=0;i> 1; - register size_t end = size - 1; - for (register size_t i = 0; i < halfSize; ++i) - std::swap(ptr[end - i], ptr[i]); - } - } - - - // Specialized routines. - - // NULL routines. - template<> inline void swap_bytes(char *const value) {} - template<> inline void swap_bytes(char *const value, register const size_t nItems) {} - template<> inline void swap_bytes(int8_t *const value) {} - template<> inline void swap_bytes(uint8_t *const value) {} - template<> inline void swap_bytes(int8_t *const value, register const size_t nItems) {} - template<> inline void swap_bytes(uint8_t *const value, register const size_t nItems) {} - - // Single item routines. - template<> inline void swap_bytes(int16_t *const value) { *value=SWAP16(*value); } - template<> inline void swap_bytes(uint16_t *const value) { *value=SWAP16(*value); } - template<> inline void swap_bytes(int32_t *const value) { *value=SWAP32(*value); } - template<> inline void swap_bytes(uint32_t *const value) { *value=SWAP32(*value); } - template<> inline void swap_bytes(int64_t *const value) { *value=SWAP64(*value); } - template<> inline void swap_bytes(uint64_t *const value) { *value=SWAP64(*value); } - - template<> inline void swap_bytes(float *const value) - { - int32_t *const pseudo=(int32_t*)(void*)value; - *pseudo=SWAP32(*pseudo); - } - - template<> inline void swap_bytes(double *const value) - { - int64_t *const pseudo=(int64_t*)(void*)value; - *pseudo=SWAP64(*pseudo); - } - - #ifdef SIM_LITTLE_ENDIAN - template inline void make_little_endian(T *const value) {} - - // Generic swap. Has a '_' prefix to prevent it from accidently being used with structs, etc. - template inline void make_little_endian_(T *const value) {} - template inline void make_big_endian_(T *const value) { swap_bytes(value); } - - inline void make_big_endian(char *const value) {} - inline void make_big_endian(int8_t *const value) {} - inline void make_big_endian(uint8_t *const value) {} - inline void make_big_endian(int16_t *const value) { *value=SWAP16(*value); } - inline void make_big_endian(uint16_t *const value) { *value=SWAP16(*value); } - inline void make_big_endian(int32_t *const value) { *value=SWAP32(*value); } - inline void make_big_endian(uint32_t *const value) { *value=SWAP32(*value); } - inline void make_big_endian(int64_t *const value) { *value=SWAP64(*value); } - inline void make_big_endian(uint64_t *const value) { *value=SWAP64(*value); } - - inline void make_big_endian(float *const value) - { - int32_t *const pseudo=(int32_t*)(void*)value; - *pseudo=SWAP32(*pseudo); - } - - inline void make_big_endian(double *const value) - { - int64_t *const pseudo=(int64_t*)(void*)value; - *pseudo=SWAP64(*pseudo); - } -#else - template inline void make_big_endian(T *const value) {} - template inline void make_big_endian_(T *const value) {} - template inline void make_little_endian_(T *const value) { swap_bytes(value); } - - inline void make_little_endian(char *const value) {} - inline void make_little_endian(int8_t *const value) {} - inline void make_little_endian(uint8_t *const value) {} - inline void make_little_endian(int16_t *const value) { *value=SWAP16(*value); } - inline void make_little_endian(uint16_t *const value) { *value=SWAP16(*value); } - inline void make_little_endian(int32_t *const value) { *value=SWAP32(*value); } - inline void make_little_endian(uint32_t *const value) { *value=SWAP32(*value); } - inline void make_little_endian(int64_t *const value) { *value=SWAP64(*value); } - inline void make_little_endian(uint64_t *const value) { *value=SWAP64(*value); } - - inline void make_little_endian(float *const value) - { - int32_t *const pseudo=(int32_t*)(void*)value; - *pseudo=SWAP32(*pseudo); - } - - inline void make_little_endian(double *const value) - { - int64_t *const pseudo=(int64_t*)(void*)value; - *pseudo=SWAP64(*pseudo); - } -#endif - - // Multi item routines. - template<> inline void swap_bytes(int16_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP16(value[i]); - } - - template<> inline void swap_bytes(uint16_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP16(value[i]); - } - - template<> inline void swap_bytes(int32_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP32(value[i]); - } - - template<> inline void swap_bytes(uint32_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP32(value[i]); - } - - template<> inline void swap_bytes(int64_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP64(value[i]); - } - - template<> inline void swap_bytes(uint64_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP64(value[i]); - } - - template<> inline void swap_bytes(float *const value, register const size_t nItems) - { - int32_t *const pseudo=(int32_t*)(void*)value; - for (register size_t i = 0; i < nItems; ++i) pseudo[i] = SWAP32(pseudo[i]); - } - - template<> inline void swap_bytes(double *const value, register const size_t nItems) - { - int64_t *const pseudo=(int64_t*)(void*)value; - for (register size_t i = 0; i < nItems; ++i) pseudo[i] = SWAP64(pseudo[i]); - } - -#ifdef SIM_LITTLE_ENDIAN - template inline void make_little_endian(T *const value, register const size_t nItems) {} - template inline void make_little_endian_(T *const value, register const size_t nItems) {} - template inline void make_big_endian_(T *const value, register const size_t nItems) { swap_bytes(value, nItems); } - - inline void make_big_endian(char *const value, register const size_t nItems) {} - inline void make_big_endian(int8_t *const value, register const size_t nItems) {} - inline void make_big_endian(uint8_t *const value, register const size_t nItems) {} - - inline void make_big_endian(int16_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP16(value[i]); - } - - inline void make_big_endian(uint16_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP16(value[i]); - } - - inline void make_big_endian(int32_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP32(value[i]); - } - - inline void make_big_endian(uint32_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP32(value[i]); - } - - inline void make_big_endian(int64_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP64(value[i]); - } - - inline void make_big_endian(uint64_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP64(value[i]); - } - - inline void make_big_endian(float *const value, register const size_t nItems) - { - int32_t *const pseudo = (int32_t*)(void*)value; - for (register size_t i = 0; i < nItems; ++i) pseudo[i] = SWAP32(pseudo[i]); - } - - inline void make_big_endian(double *const value, register const size_t nItems) - { - int64_t *const pseudo = (int64_t*)(void*)value; - for (register size_t i = 0; i < nItems; ++i) pseudo[i] = SWAP64(pseudo[i]); - } -#else - template inline void make_big_endian(T *const value, register const size_t nItems) {} - template inline void make_big_endian_(T *const value, register const size_t nItems) {} - template inline void make_little_endian_(T *const value, register const size_t nItems) { swap_bytes(value, nItems); } - - inline void make_little_endian(char *const value, register const size_t nItems) {} - inline void make_little_endian(int8_t *const value, register const size_t nItems) {} - inline void make_little_endian(uint8_t *const value, register const size_t nItems) {} - - inline void make_little_endian(int16_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP16(value[i]); - } - - inline void make_little_endian(uint16_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP16(value[i]); - } - - inline void make_little_endian(int32_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP32(value[i]); - } - - inline void make_little_endian(uint32_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP32(value[i]); - } - - inline void make_little_endian(int64_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP64(value[i]); - } - - inline void make_little_endian(uint64_t *const value, register const size_t nItems) - { - for (register size_t i = 0; i < nItems; ++i) value[i] = SWAP64(value[i]); - } - - inline void make_little_endian(float *const value, register const size_t nItems) - { - int32_t *const pseudo = (int32_t*)(void*)value; - for (register size_t i = 0; i < nItems; ++i) pseudo[i] = SWAP32(pseudo[i]); - } - - inline void make_little_endian(double *const value, register const size_t nItems) - { - int64_t *const pseudo = (int64_t*)(void*)value; - for (register size_t i = 0; i < nItems; ++i) pseudo[i] = SWAP64(pseudo[i]); - } - #endif - - - // Swapped I\O - template inline size_t beread(FILE *stream, T *const val) - { - size_t nItemsRead = fread(val, sizeof(T), 1, stream); - make_big_endian(val); - return nItemsRead; - } - - template inline size_t beread(FILE *stream, T *const val, register const size_t nItems) - { - size_t nItemsRead = fread(val, sizeof(T), nItems, stream); - make_big_endian(val, nItemsRead); - return nItemsRead; - } - - template inline size_t bewrite(FILE *stream, const T *const val) - { - T temp = *val; - make_big_endian(&temp); - return fwrite(&temp, sizeof(T), 1, stream); - } - - template inline size_t bewrite(FILE *stream, const T *const val, register const size_t nItems) - { - register size_t nItemsWrit = 0; - while (nItemsWrit < nItems) - { - T temp = val[nItemsWrit++]; - make_big_endian(&temp); - if (fwrite(&temp, sizeof(T), 1, stream)!=1) - break; - } - return nItemsWrit; - } - - template inline size_t leread(FILE *stream, T *const val) - { - size_t nItemsRead = fread(val, sizeof(T), 1, stream); - make_little_endian(val); - return nItemsRead; - } - - template inline size_t leread(FILE *stream, T *const val, register const size_t nItems) - { - size_t nItemsRead = fread(val, sizeof(T), nItems, stream); - make_little_endian(val, nItemsRead); - return nItemsRead; - } - - template inline size_t lewrite(FILE *stream, const T *const val) - { - T temp = *val; - make_little_endian(&temp); - return fwrite(&temp, sizeof(T), 1, stream); - } - - template inline size_t lewrite(FILE *stream, const T *const val, register const size_t nItems) - { - register size_t nItemsWrit = 0; - while (nItemsWrit < nItems) - { - T temp = val[nItemsWrit++]; - make_little_endian(&temp); - if (fwrite(&temp, sizeof(T), 1, stream) != 1) - break; - } - return nItemsWrit; - } - - template inline size_t beread(std::istream &stream, T *const val) - { - stream.read((char *)val, sizeof(T)); - size_t nItemsRead = stream.gcount() / sizeof(T); - make_big_endian(val); - return nItemsRead; - } - - template inline size_t beread(std::istream &stream, T *const val, register const size_t nItems) - { - stream.read((char *)val, nItems * sizeof(T)); - size_t nItemsRead = stream.gcount() / sizeof(T); - make_big_endian(val, nItemsRead); - return nItemsRead; - } - - template inline size_t bewrite(std::ostream &stream, const T *const val) - { - T temp = *val; - make_big_endian(&temp); - stream.write((const char *)&temp, sizeof(T)); - return (stream.bad()) ? 0 : 1; - } - - template inline size_t bewrite(std::ostream &stream, const T *const val, register const size_t nItems) - { - register size_t nItemsWrit = 0; - while (nItemsWrit < nItems) - { - T temp = val[nItemsWrit++]; - make_big_endian(&temp); - stream.write((const char *)&temp, sizeof(T)); - if (stream.bad()) - break; - } - return nItemsWrit; - } - - template inline size_t leread(std::istream &stream, T *const val) - { - stream.read((char *)val, sizeof(T)); - size_t nItemsRead = stream.gcount()/sizeof(T); - make_little_endian(val); - return nItemsRead; - } - - template inline size_t leread(std::istream &stream, T *const val, register const size_t nItems) - { - stream.read((char *)val, nItems * sizeof(T)); - size_t nItemsRead = stream.gcount() / sizeof(T); - make_little_endian(val, nItemsRead); - return nItemsRead; - } - - template inline size_t lewrite(std::ostream &stream, const T *const val) - { - T temp = *val; - make_little_endian(&temp); - stream.write((const char *)&temp, sizeof(T)); - return (stream.bad()) ? 0 : 1; - } - - template inline size_t lewrite(std::ostream &stream, const T *const val, register const size_t nItems) - { - size_t nItemsWrit = 0; - while (nItemsWrit < nItems) - { - T temp = val[nItemsWrit++]; - make_little_endian(&temp); - stream.write((const char *)&temp, sizeof(T)); - if (stream.bad()) - break; - } - return nItemsWrit; - } - - template inline size_t beread(const void *const stream, T *const val) - { - memcpy(val, stream, sizeof(T)); - make_big_endian(val); - return 1; - } - - template inline size_t beread(const void *const stream, T *const val, register const size_t nItems) - { - memcpy(val, stream, nItems * sizeof(T)); - make_big_endian(val, nItems); - return nItems; - } - - template inline size_t bewrite(void *const stream, const T *const val) - { - memcpy(stream, val, sizeof(T)); - make_big_endian((T *)stream); - return 1; - } - - template inline size_t bewrite(void *const stream, const T *const val, register const size_t nItems) - { - memcpy(stream, val, nItems * sizeof(T)); - make_big_endian((T *)stream, nItems); - return nItems; - } - - template inline size_t leread(const void *const stream, T *const val) - { - memcpy(val, stream, sizeof(T)); - make_little_endian(val); - return 1; - } - - template inline size_t leread(const void *const stream, T *const val, register const size_t nItems) - { - memcpy(val, stream, nItems * sizeof(T)); - make_little_endian(val, nItems); - return nItems; - } - - template inline size_t lewrite(void *const stream, const T *const val) - { - memcpy(stream, val, sizeof(T)); - make_little_endian((T *)stream); - return 1; - } - - template inline size_t lewrite(void *const stream, const T *const val, register const size_t nItems) - { - memcpy(stream, val, nItems * sizeof(T)); - make_little_endian((T *)stream, nItems); - return nItems; - } - -#endif - -} // namespace simVis_db - -#endif /* SIMUTIL_SWAPBYTES_H */ diff --git a/Plugins/OSGEarthDBDriver/src/DBTileSource.cpp b/Plugins/OSGEarthDBDriver/src/DBTileSource.cpp deleted file mode 100644 index 7766d3059..000000000 --- a/Plugins/OSGEarthDBDriver/src/DBTileSource.cpp +++ /dev/null @@ -1,667 +0,0 @@ -/* -*- mode: c++ -*- */ -/**************************************************************************** - ***** ***** - ***** Classification: UNCLASSIFIED ***** - ***** Classified By: ***** - ***** Declassify On: ***** - ***** ***** - **************************************************************************** - * - * - * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. - * EW Modeling & Simulation, Code 5773 - * 4555 Overlook Ave. - * Washington, D.C. 20375-5339 - * - * License for source code at https://simdis.nrl.navy.mil/License.aspx - * - * The U.S. Government retains all rights to use, duplicate, distribute, - * disclose, or release this software. - * - */ - -#include "simCore/Calc/Math.h" -#include "osg/ValueObject" -#include "osgEarth/Registry" -#include "osgEarth/FileUtils" -#include "osgEarth/Cube" -#include "osgEarth/HeightFieldUtils" -#include "osgEarth/ImageUtils" -#include "osgEarth/ImageToHeightFieldConverter" -#include "osgEarth/DateTime" -#include "osgDB/FileNameUtils" -#include "osgDB/ObjectWrapper" -#include "osgDB/WriteFile" -#include "osgDB/FileUtils" -#include "simVis/osgEarthVersion.h" -#include "QSCommon.h" -#include "swapbytes.h" -#include "SQLiteDataBaseReadUtil.h" -#include "DBTileSource.h" - -#define LC "[simVis::DBTileSource] " - -namespace simVis_db { - -namespace -{ - bool convertTileKeyToQsKey(const osgEarth::TileKey& key, FaceIndexType& out_faceIndex, QSNodeId& out_nodeId, - osg::Vec2d& out_fmin, osg::Vec2d& out_fmax) - { - QSNodeId zero(0); - QSNodeId one(1); - - const unsigned int maxLevel = key.getLevelOfDetail(); - - QSNodeId nodeId; - - osgEarth::TileKey pkey = key; - - for (unsigned int i = 0; i < maxLevel; ++i) - { - const unsigned int plevel = pkey.getLevelOfDetail(); - const unsigned int level = plevel * 3; - const QSNodeId bit0 = one << level; - const QSNodeId bit1 = one << (level + 1); - const QSNodeId bit2 = one << (level + 2); - - unsigned int tx, ty; - pkey.getTileXY(tx, ty); - - const int xoff = ((tx % 2) == 0) ? 0 : 1; - const int yoff = ((ty % 2) == 0) ? 0 : 1; - - if (xoff == 0 && yoff == 0) - { - nodeId |= bit1; - } - else if (xoff == 1 && yoff == 0) - { - nodeId |= bit0; - } - else if (xoff == 0 && yoff == 1) - { - nodeId |= bit0; - nodeId |= bit1; - } - else if (xoff == 1 && yoff == 1) - { - nodeId |= bit2; - } - - pkey = pkey.createParentKey(); - } - - out_faceIndex = osgEarth::Contrib::UnifiedCubeProfile::getFace(key); - out_nodeId = nodeId; - - double xMin = key.getExtent().xMin(); - double yMin = key.getExtent().yMin(); - double xMax = key.getExtent().xMax(); - double yMax = key.getExtent().yMax(); - int face; - - osgEarth::Contrib::CubeUtils::cubeToFace(xMin, yMin, xMax, yMax, face); - - out_fmin.set(xMin * gQsDMaxLength, yMin * gQsDMaxLength); - out_fmax.set(xMax * gQsDMaxLength, yMax * gQsDMaxLength); - - return true; - } - - bool decompressZLIB(const char* input, int inputLen, std::string& output) - { - osgDB::BaseCompressor* comp = osgDB::Registry::instance()->getObjectWrapperManager()->findCompressor("zlib"); - std::string inString(input, inputLen); - std::istringstream inStream(inString); - return comp->decompress(inStream, output); - } -} - -// -------------------------------------------------------------------------- - -DBTileSource::DBTileSource(const osgEarth::TileSourceOptions& options) - : osgEarth::TileSource(options), - options_(options), - db_(NULL), - rasterFormat_(SPLIT_UNKNOWN), - pixelLength_(128), - shallowLevel_(0), - deepLevel_(32), - timeSpecified_(false), - timeStamp_(simCore::INFINITE_TIME_STAMP) -{ - if (!options_.url().isSet() || options_.url()->empty()) - { - OE_WARN << LC << "No pathname given" << std::endl; - } -} - -DBTileSource::~DBTileSource() -{ - if (db_) - { - sqlite3_close(db_); - } -} - -osgEarth::Status DBTileSource::initialize(const osgDB::Options* dbOptions) -{ - if (options_.url().isSet()) - { - pathname_ = osgDB::findDataFile(options_.url()->full(), dbOptions); - - if (dbUtil_.OpenDataBaseFile(pathname_, &db_, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX) != QS_IS_OK) - { - db_ = NULL; - return osgEarth::Status::Error(osgEarth::Stringify() << "Failed to open DB file at " << options_.url()->full()); - } - else - { - QsErrorType err = dbUtil_.TsGetSetFromListOfSetsTable( - db_, - "default", - rasterFormat_, - pixelLength_, - shallowLevel_, - deepLevel_, - extents_, - source_, - classification_, - description_, - timeSpecified_, - timeStamp_); - - // Limit the deepLevel_ by the passed-in option - if (options_.deepestLevel().isSet()) - { - deepLevel_ = simCore::sdkMin(deepLevel_, static_cast(options_.deepestLevel().get())); - } - - if (err != QS_IS_OK) - { - sqlite3_close(db_); - db_ = NULL; - return osgEarth::Status::Error(osgEarth::Stringify() << "Failed to read metadata for " << pathname_); - } - // Set up as a unified cube: - osgEarth::Profile* profile = new osgEarth::Contrib::UnifiedCubeProfile(); - // DB are expected to be wgs84, which Cube defaults to - setProfile(profile); - - // Lat/long extents (for debugging) - osgEarth::GeoExtent llex[6]; - - // Tell the engine how deep the data actually goes: - for (unsigned int f = 0; f < 6; ++f) - { - if (extents_[f].minX < extents_[f].maxX && extents_[f].minY < extents_[f].maxY) - { - const double x0 = extents_[f].minX / gQsDMaxLength; - const double x1 = extents_[f].maxX / gQsDMaxLength; - const double y0 = extents_[f].minY / gQsDMaxLength; - const double y1 = extents_[f].maxY / gQsDMaxLength; - - osgEarth::GeoExtent cubeEx(profile->getSRS(), f + x0, y0, f + x1, y1); - - // Transform to lat/long for the debugging msgs - cubeEx.transform(profile->getSRS()->getGeodeticSRS(), llex[f]); - - getDataExtents().push_back(osgEarth::DataExtent(cubeEx, shallowLevel_, deepLevel_)); - } - } - - // Set time value of image if a time was found in the db - if (timeStamp_ != simCore::INFINITE_TIME_STAMP) - { - const osgEarth::DateTime osgTime(timeStamp_.secondsSinceRefYear(1970).getSeconds()); - // Set time as a user value since config is not editable from here - setUserValue("time", osgTime.asISO8601()); - } - - OE_INFO << LC - << "Table: " << options_.url()->full() << std::endl - << " Raster format = " << rasterFormat_ << std::endl - << " Tile size = " << pixelLength_ << std::endl - << " Shallow level = " << shallowLevel_ << std::endl - << " Deep level = " << deepLevel_ << std::endl - << " QS Extents = " << std::endl - << " 0: " << extents_[0].minX << "," << extents_[0].minY << "," << extents_[0].maxX << "," << extents_[0].maxY << "(" << (llex[0].isValid() ? llex[0].toString() : "empty") << ")\n" - << " 1: " << extents_[1].minX << "," << extents_[1].minY << "," << extents_[1].maxX << "," << extents_[1].maxY << "(" << (llex[1].isValid() ? llex[1].toString() : "empty") << ")\n" - << " 2: " << extents_[2].minX << "," << extents_[2].minY << "," << extents_[2].maxX << "," << extents_[2].maxY << "(" << (llex[2].isValid() ? llex[2].toString() : "empty") << ")\n" - << " 3: " << extents_[3].minX << "," << extents_[3].minY << "," << extents_[3].maxX << "," << extents_[3].maxY << "(" << (llex[3].isValid() ? llex[3].toString() : "empty") << ")\n" - << " 4: " << extents_[4].minX << "," << extents_[4].minY << "," << extents_[4].maxX << "," << extents_[4].maxY << "(" << (llex[4].isValid() ? llex[4].toString() : "empty") << ")\n" - << " 5: " << extents_[5].minX << "," << extents_[5].minY << "," << extents_[5].maxX << "," << extents_[5].maxY << "(" << (llex[5].isValid() ? llex[5].toString() : "empty") << ")\n"; - - // Line up the native format readers: - pngReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/png"); - jpgReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/jpeg"); - tifReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/tiff"); - rgbReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/x-rgb"); - } - } - - return osgEarth::STATUS_OK; -} - -int DBTileSource::getPixelsPerTile() const -{ - return pixelLength_; -} - -osg::Image* DBTileSource::createImage(const osgEarth::TileKey& key, osgEarth::ProgressCallback* progress) -{ - return createImage_(key, false); -} - -osg::HeightField* DBTileSource::createHeightField(const osgEarth::TileKey& key, osgEarth::ProgressCallback* progress) -{ - if (!db_) return NULL; - - osg::ref_ptr result; - - // Convert osgEarth::TileKey into a QuadKeyID - FaceIndexType faceId; - QSNodeId nodeId; - osg::Vec2d tileMin; - osg::Vec2d tileMax; // Tile extents in QS units - convertTileKeyToQsKey(key, faceId, nodeId, tileMin, tileMax); - - if (!extents_[faceId].Valid()) - { - // If there is no data on that face, return nothing. - OE_DEBUG << LC << "Face " << (int)faceId << " is invalid; returning empty heightfield" << std::endl; - return NULL; - } - - // Query the database - TextureDataType* buf = NULL; - uint32_t bufSize = 0; - uint32_t currentRasterSize = 0; - - QsErrorType err = dbUtil_.TsReadDataBuffer( - db_, - pathname_, - "default", - faceId, - nodeId, - &buf, - &bufSize, - ¤tRasterSize, - false); // AllowLocalDB: no, we created it ourselves - - if (err == QS_IS_OK) - { - if (currentRasterSize > 0) - { - osg::ref_ptr image; - if (decodeRaster_(rasterFormat_, (const char*)buf, currentRasterSize, image)) - { - - // SIMDIS .db elevation data is y-inverted: - image->flipVertical(); - - osgEarth::ImageToHeightFieldConverter i2h; - result = i2h.convert(image.get()); - - // If result is 1x1, skip border processing - if (result->getNumColumns() >= 1 && result->getNumRows() >= 1) - { - // Tile width and height in QS units: - const double tileWidth = tileMax.x() - tileMin.x(); - const double tileHeight = tileMax.y() - tileMin.y(); - - /** - * DB data contains a one-pixel border with undefined data. That border falls - * within the reported extents. We have to fill that with "NO DATA". - * First, calculate the size of a pixel in QS units for this tile: - */ - const double qppx = tileWidth / pixelLength_; - const double qppy = tileHeight / pixelLength_; - - /** - * Adjust the reported extents to remove the border. - * NOTE: This will fail in the (rare?) edge case in which a data extent falls - * exactly on a cube-face boundary. Ignore that for now. - */ - const double xMin = extents_[faceId].minX + qppx; - const double xMax = extents_[faceId].maxX - qppx; - const double yMin = extents_[faceId].minY + qppy; - const double yMax = extents_[faceId].maxY - qppy; - - // Write "no data" to all pixels outside the reported extent. - const double colWidth = tileWidth / (result->getNumColumns() - 1); - const double rowHeight = tileHeight / (result->getNumRows() - 1); - - for (unsigned int row = 0; row < result->getNumRows(); ++row) - { - const double y = tileMin.y() + row * rowHeight; - - for (unsigned int col = 0; col < result->getNumColumns(); ++col) - { - const double x = tileMin.x() + col * colWidth; - - if (x < xMin || x > xMax || y < yMin || y > yMax) - { - result->setHeight(col, row, NO_DATA_VALUE); - } - } - } - } - } - else - { - OE_WARN << "Heightfield decode failed for key " << key.str() << std::endl; - } - } - else - { - // Raster size of 0 means no tile in the db - result = NULL; - } - } - else - { - OE_WARN << "Failed to read heightfield from " << key.str() << std::endl; - } - - delete[] buf; - return result.release(); -} - -osg::Image* DBTileSource::createImage_(const osgEarth::TileKey& key, bool isHeightField) -{ - if (!db_) - return NULL; - - osg::ref_ptr result; - - // Convert osgEarth::TileKey into a QuadKeyID - FaceIndexType faceId; - QSNodeId nodeId; - osg::Vec2d tileMin; - osg::Vec2d tileMax; // Tile extents in QS units - convertTileKeyToQsKey(key, faceId, nodeId, tileMin, tileMax); - - if (!extents_[faceId].Valid()) - { - // If there is no data on that face, return nothing. - OE_DEBUG << LC << "Face " << static_cast(faceId) << " is invalid; returning empty image" << std::endl; - return NULL; - } - - if (key.getLevelOfDetail() > static_cast(deepLevel_)) - { - // Hopefully this doesn't happen since we called setMaxDataLevel, but you never know - return NULL; - } - - // Query the database - TextureDataType* buf = NULL; - uint32_t bufSize = 0; - uint32_t currentRasterSize = 0; - - QsErrorType err = dbUtil_.TsReadDataBuffer( - db_, - pathname_, - "default", - faceId, - nodeId, - &buf, - &bufSize, - ¤tRasterSize, - false, true); // AllowLocalDB: no, we created it ourselves - - if (err == QS_IS_OK) - { - if (currentRasterSize > 0) - { - if (decodeRaster_(rasterFormat_, (const char*)buf, currentRasterSize, result)) - { - // If result is 1x1, skip border processing - if (result->s() >= 1 && result->t() >= 1) - { - const unsigned int resultS = static_cast(result->s()); - const unsigned int resultT = static_cast(result->t()); - - // Tile width and height in QS units: - const double tileWidth = tileMax.x() - tileMin.x(); - const double tileHeight = tileMax.y() - tileMin.y(); - - const double qppx = 0.0; - const double qppy = 0.0; - const double xMin = extents_[faceId].minX + qppx; - const double xMax = extents_[faceId].maxX - qppx; - const double yMin = extents_[faceId].minY + qppy; - const double yMax = extents_[faceId].maxY - qppy; - - osgEarth::ImageUtils::PixelReader read(result.get()); - osgEarth::ImageUtils::PixelWriter write(result.get()); - - // Write "no data" to all pixels outside the reported extent. - const double colw = tileWidth / (resultS - 1); - const double rowh = tileHeight / (resultT - 1); - - for (unsigned int row = 0; row < resultS; ++row) - { - const double y = tileMin.y() + row * rowh; - - for (unsigned int col = 0; col < resultT; ++col) - { - const double x = tileMin.x() + col * colw; - - if (x < xMin || x > xMax || y < yMin || y > yMax) - { - osg::Vec4f pixel = read(col, row); - pixel.a() = 0.0f; - write(pixel, col, row); - } - } - } - } - } - else - { - OE_WARN << "Image decode failed for key " << key.str() << std::endl; - } - } - else - { - // Raster size of 0 means no tile in the db - //OE_DEBUG << "No image in the database for key " << key->str() << std::endl; - result = NULL; - } - } - else - { - std::cerr << GetErrorString(err) << std::endl; - OE_WARN << "Failed to read image from " << key.str() << std::endl; - } - - delete[] buf; - return result.release(); -} - -std::string DBTileSource::getExtension() const -{ - // Image formats with an alpha channel: - if ( - rasterFormat_ == SPLIT_5551_ZLIB_COMPRESS || - rasterFormat_ == SPLIT_5551_GZ || - rasterFormat_ == SPLIT_RGBA_ZLIB_COMPRESS || - rasterFormat_ == SPLIT_INTA_ZLIB_COMPRESS || - rasterFormat_ == SPLIT_SGI_RGBA || - rasterFormat_ == SPLIT_PNG || - rasterFormat_ == SPLIT_TIFF) - { - return "png"; - } - - // Image formats with no alpha channel: - else if ( - rasterFormat_ == SPLIT_SGI_RGB || - rasterFormat_ == SPLIT_JPEG) - { - return "jpg"; - } - - // Elevation formats: - else - { - return "tif"; - } -} - -template -void makeImage(int size, GLenum internalFormat, GLenum pixelFormat, GLenum type, - std::string& buf, osg::ref_ptr& outImage) -{ - unsigned char* data = new unsigned char[buf.length()]; - std::copy(buf.begin(), buf.end(), data); - - // Be sure to cast here to get the right swap function: - make_big_endian((T*)data, size * size); - - outImage = new osg::Image(); - outImage->setImage(size, size, 1, internalFormat, pixelFormat, type, data, osg::Image::USE_NEW_DELETE); -} - -// Uses one of OSG's native ReaderWriter's to read image data from a buffer. -static bool readNativeImage(osgDB::ReaderWriter* reader, const char* inBuf, int inBufLen, osg::ref_ptr& outImage) -{ - std::string inString(inBuf, inBufLen); - std::istringstream inStream(inString); - osgDB::ReaderWriter::ReadResult result = reader->readImage(inStream); - outImage = result.getImage(); - if (result.error() || !outImage.valid()) - { - OE_WARN << LC << "Failed to read JPEG image" << std::endl; - return false; - } - else - return true; -} - -// Decode a raster from an input buffer in one of SIMDIS's .db raster formats. -bool DBTileSource::decodeRaster_(int rasterFormat, const char* inputBuffer, int inputBufferLen, osg::ref_ptr& outImage) -{ - switch (rasterFormat) - { - case SPLIT_5551_ZLIB_COMPRESS: // TESTED OK - case SPLIT_5551_GZ: // UNTESTED - { - std::string buf; - if (decompressZLIB(inputBuffer, inputBufferLen, buf)) - { - // Three component image (red, green, and blue channels) - makeImage( - pixelLength_, GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, buf, outImage); - return true; - } - } - break; - - case SPLIT_8BIT_ZLIB_COMPRESS: // TESTED OK - case SPLIT_8BIT_GZ: // UNTESTED - { - std::string buf; - if (decompressZLIB(inputBuffer, inputBufferLen, buf)) - { - // Single component image (grayscale channel) - makeImage( - pixelLength_, GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE, buf, outImage); - return true; - } - } - break; - case SPLIT_INTA_ZLIB_COMPRESS: // TESTED OK - { - std::string buf; - if (decompressZLIB(inputBuffer, inputBufferLen, buf)) - { - // Two component image (grayscale w/alpha channel) - makeImage( - pixelLength_, GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, buf, outImage); - return true; - } - break; - } - case SPLIT_RGBA_ZLIB_COMPRESS: // TESTED OK - { - std::string buf; - if (decompressZLIB(inputBuffer, inputBufferLen, buf)) - { - // Four component image (red, green, blue and alpha channels) - makeImage( - pixelLength_, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, buf, outImage); - return true; - } - break; - } - case SPLIT_SGI_RGBA: // TESTED - OK (earthColorSGI.db) - { - if (rgbReader_.valid()) - return readNativeImage(rgbReader_.get(), inputBuffer, inputBufferLen, outImage); - else - OE_WARN << LC << "SGI RGBA reader not available" << std::endl; - } - break; - - case SPLIT_SGI_RGB: // UNTESTED - { - if (rgbReader_.valid()) - return readNativeImage(rgbReader_.get(), inputBuffer, inputBufferLen, outImage); - else - OE_WARN << LC << "SGI RGB reader not available" << std::endl; - } - break; - - case SPLIT_FLOAT32_ZLIB_COMPRESS: // TESTED OK; - { - // Single-channel 32-bit float elevation data - std::string buf; - if (decompressZLIB(inputBuffer, inputBufferLen, buf)) - { - makeImage(pixelLength_, GL_LUMINANCE32F_ARB, GL_LUMINANCE, GL_FLOAT, buf, outImage); - return true; - } - } - break; - - case SPLIT_JPEG: // TESTED OK - { - if (jpgReader_.valid()) - return readNativeImage(jpgReader_.get(), inputBuffer, inputBufferLen, outImage); - else - OE_WARN << LC << "JPEG reader not available" << std::endl; - } - break; - - case SPLIT_PNG: // UNTESTED - { - if (pngReader_.valid()) - return readNativeImage(pngReader_.get(), inputBuffer, inputBufferLen, outImage); - else - OE_WARN << LC << "PNG reader not available" << std::endl; - } - break; - - case SPLIT_TIFF: // UNTESTED - { - if (tifReader_.valid()) - return readNativeImage(tifReader_.get(), inputBuffer, inputBufferLen, outImage); - else - OE_WARN << LC << "TIFF reader not available" << std::endl; - } - break; - - default: - { - OE_WARN << "Support for raster format " << rasterFormat << " not implemented" << std::endl; - } - break; - } - return false; -} - -} diff --git a/Plugins/OSGEarthDBDriver/src/Plugin.cpp b/Plugins/OSGEarthDBDriver/src/Plugin.cpp deleted file mode 100644 index b97c685b0..000000000 --- a/Plugins/OSGEarthDBDriver/src/Plugin.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* -*- mode: c++ -*- */ -/**************************************************************************** -***** ***** -***** Classification: UNCLASSIFIED ***** -***** Classified By: ***** -***** Declassify On: ***** -***** ***** -**************************************************************************** -* -* -* Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. -* EW Modeling & Simulation, Code 5773 -* 4555 Overlook Ave. -* Washington, D.C. 20375-5339 -* -* License for source code at https://simdis.nrl.navy.mil/License.aspx -* -* The U.S. Government retains all rights to use, duplicate, distribute, -* disclose, or release this software. -* -*/ -#include "osgEarth/TileSource" -#include "osgDB/FileNameUtils" -#include "osgDB/Registry" -#include -#include "DBTileSource.h" - -class SimSdkOSGEarthDBDriverPlugin : public osgEarth::TileSourceDriver -{ -public: - SimSdkOSGEarthDBDriverPlugin() { } - - const char* className() - { - return "OSGEarth DB Driver"; - } - - bool acceptsExtension(const std::string& extension) const - { - return osgDB::equalCaseInsensitive("osgearth_db", extension); - } - - osgDB::ReaderWriter::ReadResult readObject(const std::string& uri, const osgDB::Options* options) const - { - std::string ext = osgDB::getFileExtension(uri); - if (!acceptsExtension(ext)) - { - return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED; - } - - return osgDB::ReaderWriter::ReadResult( - new simVis_db::DBTileSource(getTileSourceOptions(options))); - } -}; - -REGISTER_OSGPLUGIN(osgearth_db, SimSdkOSGEarthDBDriverPlugin) diff --git a/Plugins/OSGEarthDBDriver/src/QSPosXYExtents.cpp b/Plugins/OSGEarthDBDriver/src/QSPosXYExtents.cpp deleted file mode 100644 index 6f49b8be3..000000000 --- a/Plugins/OSGEarthDBDriver/src/QSPosXYExtents.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* -*- mode: c++ -*- */ -/**************************************************************************** - ***** ***** - ***** Classification: UNCLASSIFIED ***** - ***** Classified By: ***** - ***** Declassify On: ***** - ***** ***** - **************************************************************************** - * - * - * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. - * EW Modeling & Simulation, Code 5773 - * 4555 Overlook Ave. - * Washington, D.C. 20375-5339 - * - * License for source code at https://simdis.nrl.navy.mil/License.aspx - * - * The U.S. Government retains all rights to use, duplicate, distribute, - * disclose, or release this software. - * - */ - -#include -#include "simCore/Calc/Math.h" -#include "swapbytes.h" -#include "QSCommon.h" -#include "QSPosXYExtents.h" - -namespace simVis_db { - -//===================================================================================== -PosXPosYExtents::PosXPosYExtents(QsPosType minXIn, QsPosType maxXIn, QsPosType minYIn, QsPosType maxYIn) - : minX(minXIn), - maxX(maxXIn), - minY(minYIn), - maxY(maxYIn) -{ -} - -void PosXPosYExtents::Initialize() -{ - minX = gQsMaxLength; - maxX = 0; - minY = gQsMaxLength; - maxY = 0; -} - -bool PosXPosYExtents::Valid() const -{ - return ((minX >= maxX) || (minY >= maxY)) ? false : true; -} - -void PosXPosYExtents::SetAll(const PosXPosYExtents& given) -{ - minX = given.minX; - maxX = given.maxX; - minY = given.minY; - maxY = given.maxY; -} - -void PosXPosYExtents::SetAll(const QsPosType& minXIn, const QsPosType& maxXIn, const QsPosType& minYIn, const QsPosType& maxYIn) -{ - minX = minXIn; - maxX = maxXIn; - minY = minYIn; - maxY = maxYIn; -} - -void PosXPosYExtents::Pack(uint8_t* buffer) const -{ - if (buffer == NULL) - return; - bewrite(buffer, &minX); - bewrite(buffer + sizeof(minX), &maxX); - bewrite(buffer + sizeof(minX) + sizeof(maxX), &minY); - bewrite(buffer + sizeof(minX) + sizeof(maxX) + sizeof(minY), &maxY); -} - -void PosXPosYExtents::UnPack(const uint8_t* buffer) -{ - if (buffer == NULL) - return; - beread(buffer, &minX); - beread(buffer + sizeof(minX), &maxX); - beread(buffer + sizeof(minX) + sizeof(maxX), &minY); - beread(buffer + sizeof(minX) + sizeof(maxX) + sizeof(minY), &maxY); -} - -void PosXPosYExtents::Print() -{ - std::cerr << "minX = " << minX << "\n"; - std::cerr << "maxX = " << maxX << "\n"; - std::cerr << "minY = " << minY << "\n"; - std::cerr << "maxY = " << maxY << "\n"; -} - -//===================================================================================== -bool equalTo(const PosXPosYExtents& a, const PosXPosYExtents& b) -{ - if (a.minX != b.minX) return false; - if (a.maxX != b.maxX) return false; - if (a.minY != b.minY) return false; - if (a.maxY != b.maxY) return false; - return true; -} - -bool operator==(const PosXPosYExtents& a, const PosXPosYExtents& b) -{ - return equalTo(a, b); -} - -bool operator!=(const PosXPosYExtents& a, const PosXPosYExtents& b) -{ - return !simVis_db::equalTo(a, b); -} - -//===================================================================================== -void UpdateExtents(const QsPosType& posX, const QsPosType& posY, PosXPosYExtents* extents) -{ - if (extents == NULL) - return; - - extents->minX = simCore::sdkMin(extents->minX, posX); - extents->minY = simCore::sdkMin(extents->minY, posY); - extents->maxX = simCore::sdkMax(extents->maxX, posX); - extents->maxY = simCore::sdkMax(extents->maxY, posY); -} - -bool Copy6Extents(const PosXPosYExtents* copyFrom, PosXPosYExtents* copyTo) -{ - if ((copyFrom == NULL) || (copyTo == NULL)) - return false; - - FaceIndexType faceIndex; - for (faceIndex = 0; faceIndex < 6; ++faceIndex) - copyTo[faceIndex].SetAll(copyFrom[faceIndex]); - - return true; -} - -bool AnyOverlap(const PosXPosYExtents& extA, const PosXPosYExtents& extB) -{ - if ((extA.Valid() == false) || (extB.Valid() == false)) - return false; - - // checks for no x overlap - if ((extA.minX > extB.maxX) || - (extA.maxX < extB.minX)) - return false; - - // checks for no y overlap - if ((extA.minY > extB.maxY) || - (extA.maxY < extB.minY)) - return false; - - return true; -} - -bool AnyOverlap(const QsPosType& posX, const QsPosType& posY, const PosXPosYExtents& extents) -{ - if (extents.Valid() == false) - return false; - - return ((posX < extents.minX) || - (posX > extents.maxX) || - (posY < extents.minY) || - (posY > extents.maxY)) ? false : true; -} - -} diff --git a/Plugins/QtDesignerWidgets/CMakeLists.txt b/Plugins/QtDesignerWidgets/CMakeLists.txt index 538d67a9c..4ae23a864 100644 --- a/Plugins/QtDesignerWidgets/CMakeLists.txt +++ b/Plugins/QtDesignerWidgets/CMakeLists.txt @@ -9,14 +9,12 @@ set(PROJ_HEADERS simQtDesignerPlugins.h CategoryDataBreadcrumbsPlugin.h CategoryFilterWidgetPlugin.h - CategoryFilterWidget2Plugin.h ColorButtonPlugin.h ColorWidgetPlugin.h DataTableComboBoxPlugin.h DirectorySelectorWidgetPlugin.h DockWidgetPlugin.h EntityFilterLineEditPlugin.h - EntityLineEditPlugin.h EntityTreeCompositePlugin.h EntityTypeFilterWidgetPlugin.h FileSelectorWidgetPlugin.h @@ -32,14 +30,12 @@ set(PROJ_HEADERS set(PROJ_SOURCES CategoryDataBreadcrumbsPlugin.cpp CategoryFilterWidgetPlugin.cpp - CategoryFilterWidget2Plugin.cpp ColorButtonPlugin.cpp ColorWidgetPlugin.cpp DataTableComboBoxPlugin.cpp DirectorySelectorWidgetPlugin.cpp DockWidgetPlugin.cpp EntityFilterLineEditPlugin.cpp - EntityLineEditPlugin.cpp EntityTreeCompositePlugin.cpp EntityTypeFilterWidgetPlugin.cpp FileSelectorWidgetPlugin.cpp @@ -57,6 +53,11 @@ if(OSG_FOUND) list(APPEND PROJ_SOURCES ColorGradientWidgetPlugin.cpp) endif() +if(TARGET simVis) + list(APPEND PROJ_HEADERS EntityLineEditPlugin.h) + list(APPEND PROJ_SOURCES EntityLineEditPlugin.cpp) +endif() + source_group(Headers FILES ${PROJ_HEADERS}) source_group(Sources FILES ${PROJ_SOURCES}) @@ -91,12 +92,13 @@ add_library(simQtDesignerWidgets SHARED ${PROJ_SOURCES} ${PROJ_HEADERS} target_link_libraries(simQtDesignerWidgets PRIVATE simQt simData) if(TARGET simVis) target_link_libraries(simQtDesignerWidgets PRIVATE simVis) + target_compile_definitions(simQtDesignerWidgets PRIVATE HAVE_SIMVIS) endif() target_include_directories(simQtDesignerWidgets PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_compile_definitions(simQtDesignerWidgets PRIVATE QDESIGNER_EXPORT_WIDGETS) set_target_properties(simQtDesignerWidgets PROPERTIES FOLDER "Qt Designer Plugins" - PROJECT_LABEL "Plugin - simQt Designer Widgets") + PROJECT_LABEL "simQt Designer Widgets") if(OSG_FOUND) target_compile_definitions(simQtDesignerWidgets PRIVATE HAVE_OSG) diff --git a/Plugins/QtDesignerWidgets/CategoryFilterWidget2Plugin.cpp b/Plugins/QtDesignerWidgets/CategoryFilterWidget2Plugin.cpp deleted file mode 100644 index d1b1d02eb..000000000 --- a/Plugins/QtDesignerWidgets/CategoryFilterWidget2Plugin.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* -*- mode: c++ -*- */ -/**************************************************************************** - ***** ***** - ***** Classification: UNCLASSIFIED ***** - ***** Classified By: ***** - ***** Declassify On: ***** - ***** ***** - **************************************************************************** - * - * - * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. - * EW Modeling & Simulation, Code 5773 - * 4555 Overlook Ave. - * Washington, D.C. 20375-5339 - * - * License for source code at https://simdis.nrl.navy.mil/License.aspx - * - * The U.S. Government retains all rights to use, duplicate, distribute, - * disclose, or release this software. - * - */ -#include -#include "simData/CategoryData/CategoryFilter.h" -#include "simData/CategoryData/CategoryNameManager.h" -#include "simData/MemoryDataStore.h" -#include "simQt/CategoryTreeModel2.h" -#include "CategoryFilterWidget2Plugin.h" - -CategoryFilterWidget2Plugin::CategoryFilterWidget2Plugin(QObject *parent) - : QObject(parent), - dataStore_(NULL) -{ -} - -CategoryFilterWidget2Plugin::~CategoryFilterWidget2Plugin() -{ - delete dataStore_; -} - -void CategoryFilterWidget2Plugin::initialize(QDesignerFormEditorInterface *) -{ - if (dataStore_) - return; - dataStore_ = new simData::MemoryDataStore; - CategoryFilterWidget2Plugin::createDefaultCategories(*dataStore_); -} - -bool CategoryFilterWidget2Plugin::isInitialized() const -{ - return dataStore_ != NULL; -} - -QWidget *CategoryFilterWidget2Plugin::createWidget(QWidget *parent) -{ - simQt::CategoryFilterWidget2* rv = new simQt::CategoryFilterWidget2(parent); - // Create the data store, adding default categories - initialize(NULL); - rv->setDataStore(dataStore_); - - // Create a filter for user to see - simData::CategoryNameManager& nameManager = dataStore_->categoryNameManager(); - simData::CategoryFilter filter(dataStore_); - - // Affinity: Friendly entities only - const int affinityName = nameManager.addCategoryName("Affinity"); - filter.setValue(affinityName, nameManager.addCategoryValue(affinityName, "Friendly"), true); - // Platform Type: Unlisted values on; ignore Ship and Submarine - const int platformTypeName = nameManager.addCategoryName("Platform Type"); - filter.setValue(platformTypeName, nameManager.addCategoryValue(platformTypeName, "Submarine"), false); - filter.setValue(platformTypeName, nameManager.addCategoryValue(platformTypeName, "Surface Ship"), false); - filter.setValue(platformTypeName, simData::CategoryNameManager::UNLISTED_CATEGORY_VALUE, true); - rv->setFilter(filter); - - return rv; -} - -QString CategoryFilterWidget2Plugin::name() const -{ - return "simQt::CategoryFilterWidget2"; -} - -QString CategoryFilterWidget2Plugin::group() const -{ - return "simQt"; -} - -QIcon CategoryFilterWidget2Plugin::icon() const -{ - return QIcon(":/SDKPlugins/images/Categorize.png"); -} - -QString CategoryFilterWidget2Plugin::toolTip() const -{ - return "Filter entities by category"; -} - -QString CategoryFilterWidget2Plugin::whatsThis() const -{ - return toolTip(); -} - -bool CategoryFilterWidget2Plugin::isContainer() const -{ - return false; -} - -QString CategoryFilterWidget2Plugin::domXml() const -{ - return - "" - "\n" - "\n" - ""; -} - -QString CategoryFilterWidget2Plugin::includeFile() const -{ - return "simQt/CategoryTreeModel2.h"; -} - -void CategoryFilterWidget2Plugin::createDefaultCategories(simData::DataStore& dataStore) -{ - // Add some useful category names for display purposes - simData::CategoryNameManager& nameManager = dataStore.categoryNameManager(); - const int affinity = nameManager.addCategoryName("Affinity"); - nameManager.addCategoryValue(affinity, "Friendly"); - nameManager.addCategoryValue(affinity, "Hostile"); - nameManager.addCategoryValue(affinity, "Neutral"); - const int platformType = nameManager.addCategoryName("Platform Type"); - nameManager.addCategoryValue(platformType, "Unknown"); - nameManager.addCategoryValue(platformType, "Surface Ship"); - nameManager.addCategoryValue(platformType, "Submarine"); - nameManager.addCategoryValue(platformType, "Aircraft"); - nameManager.addCategoryValue(platformType, "Satellite"); - nameManager.addCategoryValue(platformType, "Helicopter"); - nameManager.addCategoryValue(platformType, "Missile"); - nameManager.addCategoryValue(platformType, "Decoy"); - nameManager.addCategoryValue(platformType, "Buoy"); - nameManager.addCategoryValue(platformType, "Reference Site"); - nameManager.addCategoryValue(platformType, "Land Site"); - nameManager.addCategoryValue(platformType, "Torpedo"); - nameManager.addCategoryValue(platformType, "Contact"); -} diff --git a/Plugins/QtDesignerWidgets/CategoryFilterWidget2Plugin.h b/Plugins/QtDesignerWidgets/CategoryFilterWidget2Plugin.h deleted file mode 100644 index b017f7a20..000000000 --- a/Plugins/QtDesignerWidgets/CategoryFilterWidget2Plugin.h +++ /dev/null @@ -1,59 +0,0 @@ -/* -*- mode: c++ -*- */ -/**************************************************************************** - ***** ***** - ***** Classification: UNCLASSIFIED ***** - ***** Classified By: ***** - ***** Declassify On: ***** - ***** ***** - **************************************************************************** - * - * - * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. - * EW Modeling & Simulation, Code 5773 - * 4555 Overlook Ave. - * Washington, D.C. 20375-5339 - * - * License for source code at https://simdis.nrl.navy.mil/License.aspx - * - * The U.S. Government retains all rights to use, duplicate, distribute, - * disclose, or release this software. - * - */ -#ifndef CATEGORY_FILTER_WIDGET2_PLUGIN_H -#define CATEGORY_FILTER_WIDGET2_PLUGIN_H - -#include - -namespace simData { class DataStore; } - -// Wrapper class for the CategoryFilterWidget2 to provide QDesignerCustomWidgetInterface -class CategoryFilterWidget2Plugin : public QObject, public QDesignerCustomWidgetInterface -{ - Q_OBJECT - Q_INTERFACES(QDesignerCustomWidgetInterface) - -public: - explicit CategoryFilterWidget2Plugin(QObject *parent = 0); - virtual ~CategoryFilterWidget2Plugin(); - - bool isContainer() const; - bool isInitialized() const; - QIcon icon() const; - QString domXml() const; - QString group() const; - QString includeFile() const; - QString name() const; - QString toolTip() const; - QString whatsThis() const; - QWidget *createWidget(QWidget *parent); - void initialize(QDesignerFormEditorInterface *core); - - /** Creates a set of category names and values for testing/display purposes */ - static void createDefaultCategories(simData::DataStore& dataStore); - -private: - simData::DataStore* dataStore_; -}; - -#endif // CATEGORY_FILTER_WIDGET2_PLUGIN_H - diff --git a/Plugins/QtDesignerWidgets/CategoryFilterWidgetPlugin.cpp b/Plugins/QtDesignerWidgets/CategoryFilterWidgetPlugin.cpp index 2aad88b5e..d2ca14957 100644 --- a/Plugins/QtDesignerWidgets/CategoryFilterWidgetPlugin.cpp +++ b/Plugins/QtDesignerWidgets/CategoryFilterWidgetPlugin.cpp @@ -19,13 +19,11 @@ * disclose, or release this software. * */ -#ifdef USE_DEPRECATED_SIMDISSDK_API - #include #include "simData/CategoryData/CategoryFilter.h" +#include "simData/CategoryData/CategoryNameManager.h" #include "simData/MemoryDataStore.h" -#include "simQt/CategoryFilterWidget.h" -#include "CategoryFilterWidget2Plugin.h" +#include "simQt/CategoryTreeModel.h" #include "CategoryFilterWidgetPlugin.h" CategoryFilterWidgetPlugin::CategoryFilterWidgetPlugin(QObject *parent) @@ -44,7 +42,7 @@ void CategoryFilterWidgetPlugin::initialize(QDesignerFormEditorInterface *) if (dataStore_) return; dataStore_ = new simData::MemoryDataStore; - CategoryFilterWidget2Plugin::createDefaultCategories(*dataStore_); + CategoryFilterWidgetPlugin::createDefaultCategories(*dataStore_); } bool CategoryFilterWidgetPlugin::isInitialized() const @@ -55,10 +53,9 @@ bool CategoryFilterWidgetPlugin::isInitialized() const QWidget *CategoryFilterWidgetPlugin::createWidget(QWidget *parent) { simQt::CategoryFilterWidget* rv = new simQt::CategoryFilterWidget(parent); - // Create the data store, adding default categories initialize(NULL); - rv->setProviders(dataStore_); + rv->setDataStore(dataStore_); // Create a filter for user to see simData::CategoryNameManager& nameManager = dataStore_->categoryNameManager(); @@ -118,6 +115,29 @@ QString CategoryFilterWidgetPlugin::domXml() const QString CategoryFilterWidgetPlugin::includeFile() const { - return "simQt/CategoryFilterWidget.h"; + return "simQt/CategoryTreeModel.h"; +} + +void CategoryFilterWidgetPlugin::createDefaultCategories(simData::DataStore& dataStore) +{ + // Add some useful category names for display purposes + simData::CategoryNameManager& nameManager = dataStore.categoryNameManager(); + const int affinity = nameManager.addCategoryName("Affinity"); + nameManager.addCategoryValue(affinity, "Friendly"); + nameManager.addCategoryValue(affinity, "Hostile"); + nameManager.addCategoryValue(affinity, "Neutral"); + const int platformType = nameManager.addCategoryName("Platform Type"); + nameManager.addCategoryValue(platformType, "Unknown"); + nameManager.addCategoryValue(platformType, "Surface Ship"); + nameManager.addCategoryValue(platformType, "Submarine"); + nameManager.addCategoryValue(platformType, "Aircraft"); + nameManager.addCategoryValue(platformType, "Satellite"); + nameManager.addCategoryValue(platformType, "Helicopter"); + nameManager.addCategoryValue(platformType, "Missile"); + nameManager.addCategoryValue(platformType, "Decoy"); + nameManager.addCategoryValue(platformType, "Buoy"); + nameManager.addCategoryValue(platformType, "Reference Site"); + nameManager.addCategoryValue(platformType, "Land Site"); + nameManager.addCategoryValue(platformType, "Torpedo"); + nameManager.addCategoryValue(platformType, "Contact"); } -#endif // USE_DEPRECATED_SIMDISSDK_API diff --git a/Plugins/QtDesignerWidgets/CategoryFilterWidgetPlugin.h b/Plugins/QtDesignerWidgets/CategoryFilterWidgetPlugin.h index 060fec3bd..e8c79d617 100644 --- a/Plugins/QtDesignerWidgets/CategoryFilterWidgetPlugin.h +++ b/Plugins/QtDesignerWidgets/CategoryFilterWidgetPlugin.h @@ -19,8 +19,6 @@ * disclose, or release this software. * */ -#ifdef USE_DEPRECATED_SIMDISSDK_API - #ifndef CATEGORY_FILTER_WIDGET_PLUGIN_H #define CATEGORY_FILTER_WIDGET_PLUGIN_H @@ -28,7 +26,7 @@ namespace simData { class DataStore; } -// Wrapper class for the FileSelectorWidget to provide QDesignerCustomWidgetInterface +// Wrapper class for the CategoryFilterWidget2 to provide QDesignerCustomWidgetInterface class CategoryFilterWidgetPlugin : public QObject, public QDesignerCustomWidgetInterface { Q_OBJECT @@ -57,5 +55,5 @@ class CategoryFilterWidgetPlugin : public QObject, public QDesignerCustomWidgetI simData::DataStore* dataStore_; }; -#endif // CATEGORY_FILTER_WIDGET_PLUGIN_H -#endif // USE_DEPRECATED_SIMDISSDK_API +#endif // CATEGORY_FILTER_WIDGET2_PLUGIN_H + diff --git a/Plugins/QtDesignerWidgets/EntityTreeCompositePlugin.h b/Plugins/QtDesignerWidgets/EntityTreeCompositePlugin.h index 0964fb676..ba9567f4a 100644 --- a/Plugins/QtDesignerWidgets/EntityTreeCompositePlugin.h +++ b/Plugins/QtDesignerWidgets/EntityTreeCompositePlugin.h @@ -68,6 +68,7 @@ class QtDesignerDisplayTree : public simQt::AbstractEntityTreeModel virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; virtual QModelIndex index(int row, int column, const QModelIndex &parent) const { return QModelIndex(); } virtual QModelIndex index(uint64_t id) const { return QModelIndex(); } + virtual QModelIndex index(uint64_t id) { return QModelIndex(); } virtual uint64_t uniqueId(const QModelIndex &index) const { return 0; } virtual QModelIndex parent(const QModelIndex &index) const { return QModelIndex(); } virtual int rowCount(const QModelIndex &parent) const { return 0; } diff --git a/Plugins/QtDesignerWidgets/simQtDesignerPlugins.cpp b/Plugins/QtDesignerWidgets/simQtDesignerPlugins.cpp index c6738aa18..1a80e8e3f 100644 --- a/Plugins/QtDesignerWidgets/simQtDesignerPlugins.cpp +++ b/Plugins/QtDesignerWidgets/simQtDesignerPlugins.cpp @@ -27,11 +27,12 @@ #include "ColorWidgetPlugin.h" #include "CategoryDataBreadcrumbsPlugin.h" #include "CategoryFilterWidgetPlugin.h" -#include "CategoryFilterWidget2Plugin.h" #include "DataTableComboBoxPlugin.h" #include "DirectorySelectorWidgetPlugin.h" #include "DockWidgetPlugin.h" +#ifdef HAVE_SIMVIS #include "EntityLineEditPlugin.h" +#endif #include "EntityTreeCompositePlugin.h" #include "EntityFilterLineEditPlugin.h" #include "EntityTypeFilterWidgetPlugin.h" @@ -48,10 +49,7 @@ simQtDesignerPlugins::simQtDesignerPlugins(QObject* parent) : QObject(parent) { // Add all plug-in widgets here widgetFactories_.append(new CategoryDataBreadcrumbsPlugin(this)); -#ifdef USE_DEPRECATED_SIMDISSDK_API widgetFactories_.append(new CategoryFilterWidgetPlugin(this)); -#endif - widgetFactories_.append(new CategoryFilterWidget2Plugin(this)); widgetFactories_.append(new ColorButtonPlugin(this)); #ifdef HAVE_OSG widgetFactories_.append(new ColorGradientWidgetPlugin(this)); @@ -61,7 +59,9 @@ simQtDesignerPlugins::simQtDesignerPlugins(QObject* parent) : QObject(parent) widgetFactories_.append(new DirectorySelectorWidgetPlugin(this)); widgetFactories_.append(new DockWidgetPlugin(this)); widgetFactories_.append(new EntityFilterLineEditPlugin(this)); +#ifdef HAVE_SIMVIS widgetFactories_.append(new EntityLineEditPlugin(this)); +#endif widgetFactories_.append(new EntityTreeCompositePlugin(this)); widgetFactories_.append(new EntityTypeFilterWidgetPlugin(this)); widgetFactories_.append(new FileSelectorWidgetPlugin(this)); diff --git a/SDK/genTopLevelHeaders.sh b/SDK/genTopLevelHeaders.sh index 06de575ba..509e39f9e 100755 --- a/SDK/genTopLevelHeaders.sh +++ b/SDK/genTopLevelHeaders.sh @@ -44,14 +44,13 @@ writeFooter() writeHeader simVis.h SIMVIS echo "#include \"simVis/Shaders.h\"" >> simVis.h.inc echo "#include \"simVis/osgEarthVersion.h\"" >> simVis.h.inc -find simVis -name '*.h' | grep -v "simVis/Shaders.h" | sed 's/^/#include "/' | sed 's/$/"/' >> simVis.h.inc +find simVis -name '*.h' | sort -f | grep -v "simVis/Shaders.h" | grep -v "simVis/DBFormat.h" | grep -v "simVis/DBOptions.h" | grep -v "simVis/DB/" | sed 's/^/#include "/' | sed 's/$/"/' >> simVis.h.inc addIncludes simVis.h writeFooter simVis.h SIMVIS - # simUtil writeHeader simUtil.h SIMUTIL -find simUtil -name '*.h' | sort -f | sed 's/^/#include "/' | sed 's/$/"/' >> simUtil.h.inc +find simUtil -name '*.h' | sort -f | grep -v "simUtil/DbConfigurationFile" | sed 's/^/#include "/' | sed 's/$/"/' >> simUtil.h.inc addIncludes simUtil.h writeFooter simUtil.h SIMUTIL diff --git a/SDK/simCore.h b/SDK/simCore.h index ad3ea0418..ad4e7d338 100644 --- a/SDK/simCore.h +++ b/SDK/simCore.h @@ -62,6 +62,7 @@ #include "simCore/LUT/LUT2.h" #include "simCore/String/Angle.h" #include "simCore/String/Constants.h" +#include "simCore/String/CsvReader.h" #include "simCore/String/FilePatterns.h" #include "simCore/String/Format.h" #include "simCore/String/TextFormatter.h" diff --git a/SDK/simCore/CMakeLists.txt b/SDK/simCore/CMakeLists.txt index a8966fcbe..c986afca8 100644 --- a/SDK/simCore/CMakeLists.txt +++ b/SDK/simCore/CMakeLists.txt @@ -52,6 +52,7 @@ set(CORE_CALC_SOURCES ${CORE_CALC_SRC}Angle.cpp ${CORE_CALC_SRC}Calculations.cpp ${CORE_CALC_SRC}CoordinateConverter.cpp + ${CORE_CALC_SRC}CoordinateSystem.cpp ${CORE_CALC_SRC}DatumConvert.cpp ${CORE_CALC_SRC}Gars.cpp ${CORE_CALC_SRC}Geometry.cpp @@ -107,6 +108,7 @@ set(CORE_STRING_SRC String/) set(CORE_STRING_HEADERS ${CORE_STRING_INC}Angle.h ${CORE_STRING_INC}Constants.h + ${CORE_STRING_INC}CsvReader.h ${CORE_STRING_INC}FilePatterns.h ${CORE_STRING_INC}Tokenizer.h ${CORE_STRING_INC}Format.h @@ -118,6 +120,7 @@ set(CORE_STRING_HEADERS ) set(CORE_STRING_SOURCES ${CORE_STRING_SRC}Angle.cpp + ${CORE_STRING_SRC}CsvReader.cpp ${CORE_STRING_SRC}Format.cpp ${CORE_STRING_SRC}Tokenizer.cpp ${CORE_STRING_SRC}TextFormatter.cpp @@ -168,6 +171,11 @@ source_group("" FILES ${CORE_ALL_HEADER}) # ---------------------------------------------------------------------- +# Avoid false MSVC 2017/2019 MSB8027 warning from Unity build on Utils.cpp and Angle.cpp +set_source_files_properties(${CORE_STRING_SRC}Angle.cpp ${CORE_STRING_SRC}Utils.cpp + ${CORE_CALC_SRC}Angle.cpp ${CORE_TIME_SRC}Utils.cpp + PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) + set(CORE_PROJECT_FILES ${CORE_COMMON_HEADERS} ${CORE_COMMON_SOURCES} ${CORE_CALC_HEADERS} ${CORE_CALC_SOURCES} @@ -190,7 +198,7 @@ endif() add_library(simCore ${STATIC_OR_SHARED} ${CORE_PROJECT_FILES}) set_target_properties(simCore PROPERTIES FOLDER "SIMDIS SDK" - PROJECT_LABEL "SIMDIS SDK - Core" + PROJECT_LABEL "simCore" ) ApplySDKVersion(simCore) target_include_directories(simCore PUBLIC diff --git a/SDK/simCore/Calc/Angle.h b/SDK/simCore/Calc/Angle.h index f71386447..bc5468ac2 100644 --- a/SDK/simCore/Calc/Angle.h +++ b/SDK/simCore/Calc/Angle.h @@ -72,7 +72,10 @@ namespace simCore } /** - * Adjusts incoming angle to fit the range [-PI_2, PI_2] + * Clamps incoming angle to fit the range [-PI_2, PI_2] + * This is intended for use with latitude or elevation angle values that are already known to be valid. + * This routine does not ensure that inputs can be validly converted to [-PI_2, PI_2], + * and may have unintended outcomes if input is not validated before this is called. * @param[in ] in Input angle (rad) * @return equivalent angle between -PI_2 and PI_2 (rad) */ @@ -134,7 +137,10 @@ namespace simCore } /** - * Hard limits incoming angle to fit the range [-90, 90] + * Clamps incoming angle to fit the range [-90, 90] + * This is intended for use with latitude or elevation angle values that are already known to be valid. + * This routine does not ensure that inputs can be validly converted to [-90, 90], + * and may have unintended outcomes if input is not validated before this is called. * @param[in ] in angle (deg) * @return equivalent angle between -90 and 90 (deg) */ diff --git a/SDK/simCore/Calc/Calculations.cpp b/SDK/simCore/Calc/Calculations.cpp index 4e548cc79..bb28f808a 100644 --- a/SDK/simCore/Calc/Calculations.cpp +++ b/SDK/simCore/Calc/Calculations.cpp @@ -479,14 +479,14 @@ double calculateAspectAngle(const Vec3 &fromLla, const Vec3 &toLla, const Vec3 & * longitude and latitude and back azimuth given a geodetic reference longitude * and latitude, a geodesic length, a forward azimuth and an ellipsoid definition. */ -void sodanoDirect(const double refLat, const double refLon, const double refAlt, const double dist, const double azfwd, double *lat, double *lon, double *azbck) +void sodanoDirect(const double refLat, const double refLon, const double refAlt, const double dist, const double azfwd, double *latOut, double *lonOut, double *azbck) { // Reference: // E. M. Sodano and T. A. Robinson, // "Direct and Inverse Solutions in Geodesics Technical Report 7" // U.S. Army Map Service, Washington, DC 1963 pp. 15-27. - assert(lat || lon || azbck); - if (!lat && !lon && !azbck) + assert(latOut || lonOut || azbck); + if (!latOut && !lonOut && !azbck) { SIM_ERROR << "sodanoDirect, invalid output params: " << __LINE__ << std::endl; return; @@ -526,8 +526,8 @@ void sodanoDirect(const double refLat, const double refLon, const double refAlt, const double lamda = atan2((sdel*saz), (cbeta1*cdel - sbeta1*sdel*caz)); // Set second latitude and longitude point - if (lat) *lat = atan2(reqtr*sbeta2, rpolr*cbeta2); - if (lon) *lon = refLon + lamda + length; + if (latOut) *latOut = atan2(reqtr*sbeta2, rpolr*cbeta2); + if (lonOut) *lonOut = refLon + lamda + length; // Back azimuth if (azbck) *azbck = atan2(-h, (sbeta1*sdel - g*cdel)); @@ -784,23 +784,11 @@ NumericalSearchType calculateGeodesicDRCR(const Vec3 &fromLla, const double &yaw *crossRng = crsrng; } - // algorithm (delaz/err) assumes we can model the shape formed by points refPt, lat2lon2, latlon as a right triangle. - // where refpt->lat2lon2->latlon forms the right angle; the sum of the other two angles should then be 90. - // but in some cases, the assumption of a right triangle is is incorrect: - // when latlon and lat2lon2 are near a pole, sodanoInverse may return angles that cut across the pole and do not fit the assumption. - // we can identify those cases when the sum of the two angles != 90. - - // angle from lat2lon2, refpt, latlon - const double angle1 = (a2 - a1); - - // angle from lat2lon2, latlon, refpt (where azb is the back azimuth from latlon to lat2lon2, corresponding to the calculated azf) - // angle2 = angFixPI(azb - a2); - // if we approximate azb = -(M_PI - azf), then we don't need to calculate azb at all - const double angle2 = (azf - M_PI) - a2; - const double angleSum = angFixPI(angle1 + angle2); - - // for valid calcs, angleSum should approach +/- M_PI_2 as the err approaches 0 - assert((type == SEARCH_FAILED) || simCore::areEqual(angleSum, M_PI_2, 0.04) || simCore::areEqual(angleSum, -M_PI_2, 0.04)); + // Previous versions of this code incorrectly attempted to verify the calculations by summing up the 3 angles + // of the triangle and comparing the results to 180 degrees. This is invalid for a triangle on the surface of + // a sphere. The 3 angles of a triangle on a surface of a sphere is greater then 180 degrees and the larger + // the triangle the more the angles exceed 180 degrees. See: https://en.wikipedia.org/wiki/Spherical_geometry + // for details. // note that SEARCH_FAILED may occur if tolerance is too tight: instead of iterating to max iterations, may be detected as failure if (type == SEARCH_FAILED) @@ -810,15 +798,11 @@ NumericalSearchType calculateGeodesicDRCR(const Vec3 &fromLla, const double &yaw if (maxrng > 1e7) return type; - // only message on failures that do not involve the condition described above - if (simCore::areEqual(angleSum, M_PI_2, 0.04) || simCore::areEqual(angleSum, -M_PI_2, 0.04)) - { - time_t currtime; - time(&currtime); - // notify when error occurred using current local time - SIM_ERROR << "calculateGeodesicDRCR linear search failed to converge to an answer @ " << ctime(&currtime) << std::endl; - // note that 15 decimal places may be necessary to reproduce a failing case - } + time_t currtime; + time(&currtime); + // notify when error occurred using current local time + SIM_ERROR << "calculateGeodesicDRCR linear search failed to converge to an answer @ " << ctime(&currtime) << std::endl; + // note that 15 decimal places may be necessary to reproduce a failing case } else if (type == SEARCH_MAX_ITER) { @@ -1058,16 +1042,16 @@ void calculateRelAng(const Vec3 &enuVec, const Vec3 &refOri, double *azim, doubl return; } + // compute an inertial pointing vector based on ENU vector + Vec3 pntVec; + calculateBodyUnitX(atan2(enuVec[0], enuVec[1]), atan2(enuVec[2], sqrt(square(enuVec[0]) + square(enuVec[1]))), pntVec); + if (azim || elev) { // compute rotation matrix based on reference geodetic Euler angles double rotMat[3][3]; d3EulertoDCM(refOri, rotMat); - // compute an inertial pointing vector based on ENU vector - Vec3 pntVec; - calculateBodyUnitX(atan2(enuVec[0], enuVec[1]), atan2(enuVec[2], sqrt(square(enuVec[0]) + square(enuVec[1]))), pntVec); - // rotate inertial pointing vector to an body pointing vector Vec3 body; d3Mv3Mult(rotMat, pntVec, body); @@ -1083,14 +1067,13 @@ void calculateRelAng(const Vec3 &enuVec, const Vec3 &refOri, double *azim, doubl *elev = el; } - // compute composite angle between an ENU and a reference vector + // compute composite angle between body pointing vector and inertial pointing vector if (cmp) { - Vec3 pntVec; - pntVec[0] = sin(refOri[0]); - pntVec[1] = cos(refOri[0]); - pntVec[2] = tan(refOri[1]); - *cmp = v3Angle(pntVec, enuVec); + Vec3 bodyPnt; + calculateBodyUnitX(refOri.yaw(), refOri.pitch(), bodyPnt); + + *cmp = v3Angle(bodyPnt, pntVec); } } @@ -1155,7 +1138,7 @@ void calculateBodyUnitZ(const double yaw, const double pitch, const double roll, } /// Decomposes the X component of the unit body vector into yaw and pitch angles -void calculateYawPitchFromBodyUnitX(const Vec3 &vecX, double &yaw, double &pitch) +void calculateYawPitchFromBodyUnitX(const Vec3 &vecX, double &yawOut, double &pitchOut) { // From Aircraft Control and Simulation 2nd Edition // B. Stevens & F. Lewis 2003 @@ -1166,21 +1149,21 @@ void calculateYawPitchFromBodyUnitX(const Vec3 &vecX, double &yaw, double &pitch // magnitude greater than unity if (areEqual(vecX[2], 1.0)) { - yaw = 0.0; - pitch = -M_PI_2; + yawOut = 0.0; + pitchOut = -M_PI_2; } else if (areEqual(vecX[2], -1.0)) { - yaw = 0.0; - pitch = M_PI_2; + yawOut = 0.0; + pitchOut = M_PI_2; } else { // no gimbal lock // atan2 returns in the range -pi to pi // inverseSine returns in the range -pi/2 to pi/2 - yaw = atan2(vecX[1], vecX[0]); - pitch = simCore::inverseSine(-vecX[2]); + yawOut = atan2(vecX[1], vecX[0]); + pitchOut = simCore::inverseSine(-vecX[2]); } } @@ -1477,6 +1460,14 @@ void calculateVelocity(const double speed, const double heading, const double pi */ void calculateAoaSideslipTotalAoa(const Vec3& enuVel, const Vec3& ypr, const bool useRoll, double* aoa, double* ss, double* totalAoa) { + if (v3Length(enuVel) == 0.0) + { + if (aoa) *aoa = 0.0; + if (ss) *ss = 0.0; + if (totalAoa) *totalAoa = 0.0; + return; + } + // aerodynamic version that accounts for roll Vec3 refOri = ypr; if (!useRoll) diff --git a/SDK/simCore/Calc/Calculations.h b/SDK/simCore/Calc/Calculations.h index 8be976899..5619858be 100644 --- a/SDK/simCore/Calc/Calculations.h +++ b/SDK/simCore/Calc/Calculations.h @@ -308,12 +308,12 @@ namespace simCore * @param[in ] refAlt Height above ellipsoid of reference point (m) * @param[in ] dist Geodesic length from reference to second point along the forward azimuth (m) * @param[in ] azfwd Forward azimuth from reference to second point (rad) - * @param[out] lat Geodetic latitude of point 2 (rad) - * @param[out] lon Geodetic longitude of point 2 (rad) + * @param[out] latOut Geodetic latitude of point 2 (rad) + * @param[out] lonOut Geodetic longitude of point 2 (rad) * @param[out] azbck Backward azimuth from second point to reference (rad) * @pre one of the lat, lon or azbck params must be valid */ - SDKCORE_EXPORT void sodanoDirect(const double refLat, const double refLon, const double refAlt, const double dist, const double azfwd, double *lat, double *lon, double *azbck=NULL); + SDKCORE_EXPORT void sodanoDirect(const double refLat, const double refLon, const double refAlt, const double dist, const double azfwd, double *latOut, double *lonOut, double *azbck=NULL); /** * @brief Calculates the geodesic length, forward and backward azimuth using Sodano's indirect solution @@ -424,10 +424,10 @@ namespace simCore * Given the X component of the vehicle's body unit vector and a yaw-pitch-roll (1-2-3) rotation sequence * The yaw and pitch angles are computed * @param[in ] vecX Vec3 representing the X component of the body unit vector - * @param[out] yaw Yaw (psi) rotation in radians - * @param[out] pitch Pitch (theta) rotation in radians + * @param[out] yawOut Yaw (psi) rotation in radians + * @param[out] pitchOut Pitch (theta) rotation in radians */ - SDKCORE_EXPORT void calculateYawPitchFromBodyUnitX(const Vec3 &vecX, double &yaw, double &pitch); + SDKCORE_EXPORT void calculateYawPitchFromBodyUnitX(const Vec3 &vecX, double &yawOut, double &pitchOut); /** * @brief Calculates an ENU geodetic velocity vector based on a local (moving) tangent plane @@ -538,22 +538,22 @@ namespace simCore /** * Calculates the angle of attack, side slip, and total angle of attack from a ENU * geodetic velocity vector and a set of geodetic Euler angles (yaw, pitch, roll) - * @param enuVel East, North, and Up geodetic velocity vector for the vehicle - * @param ypr Yaw, pitch, roll based geodetic Euler angles in radians - * @param useRoll Boolean, true: aerodynamic version, false: rocketry version (Air Ballistic Axis) that does not use roll - * @param aoa Vertical angle of attack for vehicle in radians; measure of difference between velocity and pointing direction - * @param ss Side slip angle for vehicle in radians; measure of difference between velocity and pointing direction - * @param totalAoA Total angle of attack for vehicle in radians + * @param[in ] enuVel East, North, and Up geodetic velocity vector for the vehicle + * @param[in ] ypr Yaw, pitch, roll based geodetic Euler angles in radians + * @param[in ] useRoll Boolean, true: aerodynamic version, false: rocketry version (Air Ballistic Axis) that does not use roll + * @param[out] aoa Vertical angle of attack for vehicle in radians; measure of difference between velocity and pointing direction + * @param[out] ss Side slip angle for vehicle in radians; measure of difference between velocity and pointing direction + * @param[out] totalAoA Total angle of attack for vehicle in radians */ SDKCORE_EXPORT void calculateAoaSideslipTotalAoa(const Vec3& enuVel, const Vec3& ypr, const bool useRoll, double* aoa, double* ss, double* totalAoA); /** * Returns the distance between a line segment and a point. The calculations are done in COORD_SYS_XEAST so the results are only approximate. * The code is taken from SIMDIS 9 code SimVisBeam::GetClosestPoint and generalized for WGS-84. - * @param startLla Start point of the line segment in (rad, rad, m) in WGS-84 - * @param endLla End point of the line segment in (rad, rad, m) in WGS-84 - * @param toLla The point for the distance calculation in (rad, rad, m) in WGS-84 - * @param closestLla The point on the line segment closest to the toLla point + * @param[in ] startLla Start point of the line segment in (rad, rad, m) in WGS-84 + * @param[in ] endLla End point of the line segment in (rad, rad, m) in WGS-84 + * @param[in ] toLla The point for the distance calculation in (rad, rad, m) in WGS-84 + * @param[out] closestLla The point on the line segment closest to the toLla point * @return The distance, in meters, between a line segment and the toLla point. */ SDKCORE_EXPORT double getClosestPoint(const simCore::Vec3& startLla, const simCore::Vec3& endLla, const simCore::Vec3& toLla, simCore::Vec3& closestLla); diff --git a/SDK/simCore/Calc/CoordinateSystem.cpp b/SDK/simCore/Calc/CoordinateSystem.cpp new file mode 100644 index 000000000..ecc020ebc --- /dev/null +++ b/SDK/simCore/Calc/CoordinateSystem.cpp @@ -0,0 +1,95 @@ +/* -*- mode: c++ -*- */ +/**************************************************************************** + ***** ***** + ***** Classification: UNCLASSIFIED ***** + ***** Classified By: ***** + ***** Declassify On: ***** + ***** ***** + **************************************************************************** + * + * + * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. + * EW Modeling & Simulation, Code 5773 + * 4555 Overlook Ave. + * Washington, D.C. 20375-5339 + * + * License for source code at https://simdis.nrl.navy.mil/License.aspx + * + * The U.S. Government retains all rights to use, duplicate, distribute, + * disclose, or release this software. + * + */ +#include +#include "simCore/String/Format.h" +#include "simCore/Calc/CoordinateSystem.h" + +namespace simCore +{ + +// Coordinate system string constants, matching Rule Evaluation names for SIMDIS +static const std::string COORD_SYS_NED_STR = "Topo_NED"; +static const std::string COORD_SYS_NWU_STR = "Topo_NWU"; +static const std::string COORD_SYS_ENU_STR = "Topo_ENU"; +static const std::string COORD_SYS_LLA_STR = "LLA_DD"; +static const std::string COORD_SYS_ECEF_STR = "ECEF_WGS84"; +static const std::string COORD_SYS_XEAST_STR = "TangentPlane_XEast"; +static const std::string COORD_SYS_GTP_STR = "TangentPlane_Generic"; +static const std::string COORD_SYS_ECI_STR = "ECI_WGS84"; + +// We will read these strings as "LLA", but do not write them +static const std::string COORD_SYS_LLA_DMD_STR = "LLA_DMD"; +static const std::string COORD_SYS_LLA_DMS_STR = "LLA_DMS"; + +std::string coordinateSystemToString(simCore::CoordinateSystem coordSystem) +{ + switch (coordSystem) + { + case simCore::COORD_SYS_NED: return COORD_SYS_NED_STR; + case simCore::COORD_SYS_NWU: return COORD_SYS_NWU_STR; + case simCore::COORD_SYS_ENU: return COORD_SYS_ENU_STR; + case simCore::COORD_SYS_LLA: return COORD_SYS_LLA_STR; + case simCore::COORD_SYS_ECEF: return COORD_SYS_ECEF_STR; + case simCore::COORD_SYS_XEAST: return COORD_SYS_XEAST_STR; + case simCore::COORD_SYS_GTP: return COORD_SYS_GTP_STR; + case simCore::COORD_SYS_ECI: return COORD_SYS_ECI_STR; + case simCore::COORD_SYS_NONE: + case simCore::COORD_SYS_MAX: + assert(0); // Not supported + break; + } + // Default to ENU (from legacy code) + return COORD_SYS_ENU_STR; +} + +int coordinateSystemFromString(const std::string& str, simCore::CoordinateSystem& outSystem) +{ + if (simCore::caseCompare(str, COORD_SYS_NED_STR) == 0) + outSystem = simCore::COORD_SYS_NED; + else if (simCore::caseCompare(str, COORD_SYS_NWU_STR) == 0) + outSystem = simCore::COORD_SYS_NWU; + else if (simCore::caseCompare(str, COORD_SYS_LLA_DMS_STR) == 0) + outSystem = simCore::COORD_SYS_LLA; + else if (simCore::caseCompare(str, COORD_SYS_LLA_DMD_STR) == 0) + outSystem = simCore::COORD_SYS_LLA; + else if (simCore::caseCompare(str, COORD_SYS_LLA_STR) == 0) + outSystem = simCore::COORD_SYS_LLA; + else if (simCore::caseCompare(str, COORD_SYS_ECEF_STR) == 0) + outSystem = simCore::COORD_SYS_ECEF; + else if (simCore::caseCompare(str, COORD_SYS_ECI_STR) == 0) + outSystem = simCore::COORD_SYS_ECI; + else if (simCore::caseCompare(str, COORD_SYS_ENU_STR) == 0) + outSystem = simCore::COORD_SYS_ENU; + else if (simCore::caseCompare(str, COORD_SYS_XEAST_STR) == 0) + outSystem = simCore::COORD_SYS_XEAST; + else if (simCore::caseCompare(str, COORD_SYS_GTP_STR) == 0) + outSystem = simCore::COORD_SYS_GTP; + else + { + outSystem = simCore::COORD_SYS_LLA; + return 1; + } + + return 0; +} + +} diff --git a/SDK/simCore/Calc/CoordinateSystem.h b/SDK/simCore/Calc/CoordinateSystem.h index 37cc66425..780cc8aa0 100644 --- a/SDK/simCore/Calc/CoordinateSystem.h +++ b/SDK/simCore/Calc/CoordinateSystem.h @@ -22,6 +22,9 @@ #ifndef SIMCORE_CALC_COORDINATESYSTEM_H #define SIMCORE_CALC_COORDINATESYSTEM_H +#include +#include "simCore/Common/Common.h" + /// Container for enumerations and constants relating to coordinate system calculations and conversion namespace simCore { @@ -73,6 +76,23 @@ namespace simCore const double EARTH_ROTATION_RATE = 7292115.1467e-11; ///< (rad/sec) Earth's rotation rate: International Astronomical Union (IAU) GRS 67 const double LATLON_ERR_TOL_DOUBLE = 1.0e-10; ///< floating point error tolerance for geodetic angle conversions + /** + * Given a coordinate system, returns an appropriate string constant. + * @param coordSystem Coordinate system to get string value; COORD_SYS_MAX and COORD_SYS_NONE not supported. + * @return String representation of the coordinate system + */ + SDKCORE_EXPORT std::string coordinateSystemToString(simCore::CoordinateSystem coordSystem); + + /** + * Given a coordinate system string constant, returns the appropriate system, returning 0 on success. + * Inverse of simCore::coordinateSystemToString(), accepting strings that it returns. + * @param str Coordinate string to process, from output of coordinateSystemToString(). Also accepts, for + * legacy reasons, LLA_DMD and LLA_DMS, returning a valid COORD_SYS_LLA flag. + * @param outSystem Output parameter for the coordinate system string. + * @return 0 on success, non-zero on error. In error conditions, outSystem is initialized to simCore::COORD_SYS_LLA. + */ + SDKCORE_EXPORT int coordinateSystemFromString(const std::string& str, simCore::CoordinateSystem& outSystem); + } // End of namespace simCore #endif /* SIMCORE_CALC_COORDINATESYSTEM_H */ diff --git a/SDK/simCore/Calc/Gars.cpp b/SDK/simCore/Calc/Gars.cpp index 8565a9906..438d771db 100644 --- a/SDK/simCore/Calc/Gars.cpp +++ b/SDK/simCore/Calc/Gars.cpp @@ -43,7 +43,7 @@ namespace { namespace simCore { -bool Gars::isValidGars(const std::string& gars, std::string* err, int* lonBand, int* latPimaryIdx, int* latSecondaryIdx, int* quad15, int* key5) +bool Gars::isValidGars(const std::string& gars, std::string* err, int* lonBand, int* latPrimaryIdx, int* latSecondaryIdx, int* quad15, int* key5) { // Verify length of GARS coordinate if (gars.size() < 5 || gars.size() > 7) @@ -130,8 +130,8 @@ bool Gars::isValidGars(const std::string& gars, std::string* err, int* lonBand, // Assign values only on success if (lonBand) *lonBand = lonBandInt; - if (latPimaryIdx) - *latPimaryIdx = static_cast(latPrimaryIndex); + if (latPrimaryIdx) + *latPrimaryIdx = static_cast(latPrimaryIndex); if (latSecondaryIdx) *latSecondaryIdx = static_cast(latSecondaryIndex); @@ -151,14 +151,11 @@ int Gars::convertGarsToGeodetic(const std::string& gars, double& latRad, double& if (!Gars::isValidGars(gars, err, &lonBand, &latPrimaryIndex, &latSecondaryIndex, &quad15, &key5)) return 1; // Error was set by isValidGars() - double lat = 0.; - double lon = 0.; - // Convert from lonBand integer to longitude value - lon = (lonBand - 360 - 1) * 0.5; + double lon = (lonBand - 360 - 1) * 0.5; // Start latitude at -90 - lat = -90.0; + double lat = -90.0; // Move it up 12 degrees per primary letter lat += (latPrimaryIndex * DEG_PER_PRIMARY_LETTER); // Move it up 0.5 degrees per secondary letter @@ -191,7 +188,7 @@ int Gars::convertGarsToGeodetic(const std::string& gars, double& latRad, double& return 0; } -int Gars::convertGeodeticToGars(double latRad, double lonRad, std::string& gars, Level level, std::string* err) +int Gars::convertGeodeticToGars(double latRad, double lonRad, std::string& garsOut, Level level, std::string* err) { // Conversion algorithm below adapted from osgEarthUtil/GARSGraticule.cpp getGARSLabel() @@ -265,7 +262,7 @@ int Gars::convertGeodeticToGars(double latRad, double lonRad, std::string& gars, } - gars = buf.str(); + garsOut = buf.str(); return 0; } diff --git a/SDK/simCore/Calc/Gars.h b/SDK/simCore/Calc/Gars.h index fca27dd0c..e2d6afd3c 100644 --- a/SDK/simCore/Calc/Gars.h +++ b/SDK/simCore/Calc/Gars.h @@ -48,13 +48,13 @@ class SDKCORE_EXPORT Gars * @param[in ] gars GARS coordinate string to validate * @param[out] err Optional pointer to error string * @param[out] lonBand Optional int pointer set to longitude band of the GARS coordinate on success - * @param[out] latPimaryIdx Optional int pointer set to index [0, 14] of the primary latitudinal band letter + * @param[out] latPrimaryIdx Optional int pointer set to index [0, 14] of the primary latitudinal band letter * @param[out] latSecondaryIdx Optional int pointer set to index [0, 24] of the secondary latitudinal band letter * @param[out] quad15 Optional int pointer set to 15 minute quadrant specified in the GARS coordinate, if available * @param[out] key5 Optional int pointer set to 5 minute key specified in the GARS coordinate, if available * @return true if valid GARS coordinate string, false otherwise */ - static bool isValidGars(const std::string& gars, std::string* err = NULL, int* lonBand = NULL, int* latPimaryIdx = NULL, int* latSecondaryIdx = NULL, int* quad15 = NULL, int* key5 = NULL); + static bool isValidGars(const std::string& gars, std::string* err = NULL, int* lonBand = NULL, int* latPrimaryIdx = NULL, int* latSecondaryIdx = NULL, int* quad15 = NULL, int* key5 = NULL); /** * Converts a GARS coordinate to geodetic coordinates. The resulting latitude/longitude @@ -71,12 +71,12 @@ class SDKCORE_EXPORT Gars * Converts geodetic goordinates to a GARS coordinate. * @param[in ] latRad Latitude to convert in radians * @param[in ] lonRad Longitude to convert in radians - * @param[out] gars Resulting GARS coordinate string + * @param[out] garsOut Resulting GARS coordinate string * @param[in ] level Optional level of detail used when converting * @param[out] err Optional pointer to error string * @return 0 if conversion is successful, non-zero otherwise */ - static int convertGeodeticToGars(double latRad, double lonRad, std::string& gars, Level level = GARS_5, std::string* err = NULL); + static int convertGeodeticToGars(double latRad, double lonRad, std::string& garsOut, Level level = GARS_5, std::string* err = NULL); }; } diff --git a/SDK/simCore/Calc/MagneticVariance.cpp b/SDK/simCore/Calc/MagneticVariance.cpp index c61eab486..ca5a8890d 100644 --- a/SDK/simCore/Calc/MagneticVariance.cpp +++ b/SDK/simCore/Calc/MagneticVariance.cpp @@ -20,6 +20,7 @@ * */ #include +#include "simNotify/Notify.h" #include "simCore/Calc/Vec3.h" #include "simCore/Calc/Angle.h" #include "simCore/Time/TimeClass.h" @@ -428,8 +429,13 @@ class WorldMagneticModel::GeoMag double aor_, ar_, br_, bt_, bp_, bpp_; double otime_, oalt_, olat_, olon_; int oyear_; + + /** Track if we've issued a warning about the WMM bounds */ + static bool tooLateWarned_; }; +bool WorldMagneticModel::GeoMag::tooLateWarned_ = false; + // // // // // // // // // // // // // // // // // // // // // // // // ******************************** @@ -499,9 +505,6 @@ class WorldMagneticModel::GeoMag int WorldMagneticModel::GeoMag::calculateVariance(const simCore::Vec3& lla, int ordinalDay, int refYear, double& variance) { - // convert time to year decimal fraction - const double time = static_cast(refYear)+static_cast(ordinalDay) / 365.25; - // determine appropriate epoch year if (refYear >= 1985 && refYear < 1990) epochYear_ = 1985; @@ -521,6 +524,24 @@ int WorldMagneticModel::GeoMag::calculateVariance(const simCore::Vec3& lla, int // default to last updated WMM epochYear_ = 2020; + // Warn users when their refYear is beyond the available model, but continue with the latest available year + const auto maxYear = (epochYear_ + 5); + if (refYear > maxYear || (refYear == maxYear && ordinalDay > 0)) + { + if (!tooLateWarned_) + { + SIM_ERROR << "calculateVariance encountered a date (" << ordinalDay << " " << refYear << ") which is more than 5 years beyond the last available WMM (" << + epochYear_ << "). Proceeding with date clamped to: 00 " << maxYear << std::endl; + tooLateWarned_ = true; + } + // Calculation extends to 5 years beyond the epoch date, so set day to zero and cap the year + refYear = maxYear; + ordinalDay = 0; + } + + // convert time to year decimal fraction + const double time = static_cast(refYear) + (static_cast(ordinalDay) / 365.25); + const double dt = time - static_cast(epochYear_); // convert alt from m to km @@ -538,12 +559,6 @@ int WorldMagneticModel::GeoMag::calculateVariance(const simCore::Vec3& lla, int return 0; } - if (time > epochYear_ + 5 || time < epochYear_) - { - variance = 0.0; - return 1; - } - double *p = SNORM_COEFF; const double srlon = sin(lla.lon()); const double srlat = sin(lla.lat()); diff --git a/SDK/simCore/Calc/MathConstants.h b/SDK/simCore/Calc/MathConstants.h index eb624e971..208b693b3 100644 --- a/SDK/simCore/Calc/MathConstants.h +++ b/SDK/simCore/Calc/MathConstants.h @@ -22,13 +22,15 @@ #ifndef SIMCORE_CALC_MATH_CONSTANTS_H #define SIMCORE_CALC_MATH_CONSTANTS_H -#include +// use and not here to get the Microsoft #defines consistently. +#include #ifdef WIN32 #if !defined(_MATH_DEFINES_DEFINED) -/* Support useful mathematical constants that maybe defined under windows - * if the _USE_MATH_DEFINES macro is defined. +/* Define useful mathematical constants, when they are not defined by Windows. + * Defining _USE_MATH_DEFINES tells Windows to define them; + * Windows defines _MATH_DEFINES_DEFINED to indicate that it has defined them. */ #ifndef M_E @@ -71,7 +73,7 @@ #define M_SQRT1_2 0.707106781186547524401 /* sqrt(1/2) */ #endif -#endif /* _USE_MATH_DEFINES */ +#endif /* _MATH_DEFINES_DEFINED */ #endif /* WIN32 */ diff --git a/SDK/simCore/Calc/Mgrs.cpp b/SDK/simCore/Calc/Mgrs.cpp index 2769688c9..936a3be56 100644 --- a/SDK/simCore/Calc/Mgrs.cpp +++ b/SDK/simCore/Calc/Mgrs.cpp @@ -67,7 +67,7 @@ int Mgrs::convertMgrsToGeodetic(const std::string& mgrs, double& lat, double& lo { double utmEasting; double utmNorthing; - if (convertMgrstoUtm(zone, gzdLetters, easting, northing, northPole, utmEasting, utmNorthing, err) != 0) + if (convertMgrsToUtm(zone, gzdLetters, easting, northing, northPole, utmEasting, utmNorthing, err) != 0) return 1; if (convertUtmToGeodetic(zone, northPole, utmEasting, utmNorthing, lat, lon, err) != 0) return 1; @@ -186,7 +186,7 @@ int Mgrs::breakMgrsString(const std::string& mgrs, int& zone, std::string& gzdLe return 0; } -int Mgrs::convertMgrstoUtm(int zone, const std::string& gzdLetters, double mgrsEasting, double mgrsNorthing, +int Mgrs::convertMgrsToUtm(int zone, const std::string& gzdLetters, double mgrsEasting, double mgrsNorthing, bool &northPole, double& utmEasting, double& utmNorthing, std::string* err) { const double ONEHT = 100000.; diff --git a/SDK/simCore/Calc/Mgrs.h b/SDK/simCore/Calc/Mgrs.h index 45f79f6bf..a77358e33 100644 --- a/SDK/simCore/Calc/Mgrs.h +++ b/SDK/simCore/Calc/Mgrs.h @@ -83,7 +83,7 @@ class SDKCORE_EXPORT Mgrs * @param[out] err Optional pointer to error string * @return 0 if conversion is successful, non-zero otherwise */ - static int convertMgrstoUtm(int zone, const std::string& gzdLetters, double mgrsEasting, double mgrsNorthing, + static int convertMgrsToUtm(int zone, const std::string& gzdLetters, double mgrsEasting, double mgrsNorthing, bool& northPole, double& utmEasting, double& utmNorthing, std::string* err = NULL); /** diff --git a/SDK/simCore/EM/AntennaPattern.cpp b/SDK/simCore/EM/AntennaPattern.cpp index 165892a76..1d1e6346f 100644 --- a/SDK/simCore/EM/AntennaPattern.cpp +++ b/SDK/simCore/EM/AntennaPattern.cpp @@ -523,6 +523,50 @@ void AntennaPatternPedestal::minMaxGain(float *min, float *max, const AntennaGai /* ************************************************************************** */ +namespace +{ + /** + * @brief Convenience function that returns the gain for a specified angle from an antenna pattern lookup table, interpolating if necessary + * @param[in ] angle angle to lookup (rad) + * @param[in ] angle/gain lookup table + * @return table gain (dB), or SMALL_DB_VAL on invalid input + */ + float gainAtAngle(float angle, const std::map& table) + { + // gets the first element in map with an angle >= input angle + std::map::const_iterator iter = table.lower_bound(angle); + if (iter != table.end()) + { + // checks if the obtained element's angle is equal to the given angle, + // or if the obtained element is the first element in map + if ((iter->first == angle) || (iter == table.begin())) + return iter->second; + + // the obtained element's angle is > the given angle and it is not + // the first element in the map, so an interpolated angle between + // the current element and the previous element will be used + const float hiGain = iter->second; + const float hiAng = iter->first; + --iter; + const float loGain = iter->second; + const float loAng = iter->first; + // linearInterpolate casts to double as needed to avoid loss of precision + return linearInterpolate(loGain, hiGain, loAng, angle, hiAng); + } + + // if not found in table, double-check, possibly missed due to rounding errors due to casting + iter = table.begin(); + if (areEqual(angle, iter->first)) + return iter->second; + + const std::map::const_reverse_iterator riter = table.rbegin(); + if (areEqual(angle, riter->first)) + return riter->second; + + return SMALL_DB_VAL; + } +} + /* This function returns the gain for lookup table-based antennaPatterns */ float calculateGain(const std::map *azimData, @@ -544,18 +588,18 @@ float calculateGain(const std::map *azimData, return SMALL_DB_VAL; } - std::map::const_iterator iter; - if (applyWeight == false) + float gain = SMALL_DB_VAL; + + if (!applyWeight) { - iter = azimData->begin(); - std::map::const_reverse_iterator riter = azimData->rbegin(); - if (azim < iter->first || azim > riter->first) + const float az_gain = gainAtAngle(static_cast(azim), *azimData); + if (az_gain == SMALL_DB_VAL) return SMALL_DB_VAL; - - iter = elevData->begin(); - riter = elevData->rbegin(); - if (elev < iter->first || elev > riter->first) + const float el_gain = gainAtAngle(static_cast(elev), *elevData); + if (el_gain == SMALL_DB_VAL) return SMALL_DB_VAL; + + gain = maxGain + (az_gain + el_gain) / 2.0f; } // Compute angular distance in normalized beam widths @@ -573,130 +617,44 @@ float calculateGain(const std::map *azimData, else lastLobe = ANTENNA_LOBE_BACK; - const double azim_ang = applyWeight ? sdkMin(phi * hbw, M_PI) : azim; - const double elev_ang = applyWeight ? sdkMin(phi * vbw, M_PI_2) : elev; - double az_gain = SMALL_DB_VAL; - double el_gain = SMALL_DB_VAL; + if (!applyWeight) + return gain; - // Azimuth data - // gets the first element in map with an angle >= dazim - iter = azimData->lower_bound(static_cast(azim_ang)); - - // checks if an element with a an angle >= azim_ang was found - if (iter != azimData->end()) - { - // checks if the obtained element's angle is equal to the given angle, - // or if the obtained element is the first element in map - if ((iter->first == azim_ang) || (iter == azimData->begin())) - { - az_gain = iter->second; - } - else - { - // the obtained element's angle is > the given angle and it is not - // the first element in the map, so an interpolated angle between - // the current element and the previous element will be used - double hiGain = iter->second; - double hiAng = iter->first; - --iter; - double loGain = iter->second; - double loAng = iter->first; - - az_gain = linearInterpolate(loGain, hiGain, loAng, azim_ang, hiAng); - } - } - else - { - // check for rounding errors due to casting - iter = azimData->begin(); - std::map::const_reverse_iterator riter = azimData->rbegin(); - if (areEqual(azim_ang, iter->first)) - az_gain = iter->second; - else if (areEqual(azim_ang, riter->first)) - az_gain = riter->second; - else - az_gain = SMALL_DB_VAL; - } - - // Elevation data - // gets the first element in map with an angle >= elev_ang - iter = elevData->lower_bound(static_cast(elev_ang)); - - // checks if an element with a an angle >= elev_ang was found - if (iter != elevData->end()) - { - // checks if the obtained element's angle is equal to the given angle, - // or if the obtained element is the first element in map - if ((iter->first == elev_ang) || (iter == elevData->begin())) - { - el_gain = iter->second; - } - else - { - // the obtained element's angle is > the given angle and it is not - // the first element in the map, so an interpolated angle between - // the current element and the previous element will be used - double hiGain = iter->second; - double hiAng = iter->first; - --iter; - double loGain = iter->second; - double loAng = iter->first; + const double azim_ang = sdkMin(phi * hbw, M_PI); + const float az_gain = gainAtAngle(static_cast(azim_ang), *azimData); + if (az_gain == SMALL_DB_VAL) + return SMALL_DB_VAL; - el_gain = linearInterpolate(loGain, hiGain, loAng, elev_ang, hiAng); - } - } - else - { - // check for rounding errors due to casting - iter = elevData->begin(); - std::map::const_reverse_iterator riter = elevData->rbegin(); - if (areEqual(elev_ang, iter->first)) - el_gain = iter->second; - else if (areEqual(elev_ang, riter->first)) - el_gain = riter->second; - else - el_gain = SMALL_DB_VAL; - } + const double elev_ang = sdkMin(phi * vbw, M_PI_2); + const float el_gain = gainAtAngle(static_cast(elev_ang), *elevData); + if (el_gain == SMALL_DB_VAL) + return SMALL_DB_VAL; // Determine angles (alpha & beta) associated with normalized // azim / elev components. They will be used to obtain a // 'weighted average' antenna loss value - - double gain; - if (applyWeight) - { - double alpha, beta; - if ((azim_bw == 0.0 && elev_bw == 0.0) || vbw == hbw) - { - gain = maxGain + (az_gain + el_gain) / 2.0; - } - else if (azim_bw <= elev_bw) - { - // since atan2 returns values between -pi and pi, - // alpha and beta should be in rad instead of deg - alpha = fabs(atan2(azim_bw, elev_bw)); - if (alpha > M_PI_2) - alpha = M_PI - alpha; - beta = M_PI_2 - alpha; - gain = maxGain + (alpha * az_gain + beta * el_gain) / M_PI_2; - } - else - { - // since atan2 returns values between -pi and pi, - // alpha and beta should be in rad instead of deg - beta = fabs(atan2(elev_bw, azim_bw)); - if (beta > M_PI_2) - beta = M_PI - beta; - alpha = M_PI_2 - beta; - gain = maxGain + (alpha * az_gain + beta * el_gain) / M_PI_2; - } - } - else - { - gain = maxGain + (az_gain + el_gain) / 2.0; - } - - return static_cast(gain); + if ((azim_bw == 0.0 && elev_bw == 0.0) || vbw == hbw) + return maxGain + (az_gain + el_gain) / 2.0f; + + double alpha, beta; + if (azim_bw <= elev_bw) + { + // since atan2 returns values between -pi and pi, + // alpha and beta should be in rad instead of deg + alpha = fabs(atan2(azim_bw, elev_bw)); + if (alpha > M_PI_2) + alpha = M_PI - alpha; + beta = M_PI_2 - alpha; + return static_cast(maxGain + (alpha * az_gain + beta * el_gain) / M_PI_2); + } + + // since atan2 returns values between -pi and pi, + // alpha and beta should be in rad instead of deg + beta = fabs(atan2(elev_bw, azim_bw)); + if (beta > M_PI_2) + beta = M_PI - beta; + alpha = M_PI_2 - beta; + return static_cast(maxGain + (alpha * az_gain + beta * el_gain) / M_PI_2); } // ---------------------------------------------------------------------------- @@ -840,23 +798,38 @@ int AntennaPatternTable::readPat(std::istream& fp) if (beamWidthType_) beamWidthType_ = false; + const float m_pi_2 = static_cast(M_PI_2); + // The antenna symmetry value indicates the number of tables the user is going to provide. switch (symmetry) { - // If the symmetry is 1, then the user will provide the [0, 180] azimuth table. + // If the symmetry is 1, then the user will provide one azimuth table. + // That table typically covers [0, 180], but that is not required. // This table will be reused for the other three tables. + // Elevation data [-PI/2, PI/2] will match azimuth data in [-PI/2, PI/2]. case 1: { for (i = 0; i < tableSize[0]; i++) { - azim = static_cast(angFixPI(value[0][i])); - elev = static_cast(angFixPI2(value[0][i])); - azimData_[azim] = gain[0][i]; - elevData_[elev] = gain[0][i]; - // mirror missing data - azimData_[-azim] = gain[0][i]; - elevData_[-elev] = gain[0][i]; + const float angFixPi = static_cast(angFixPI(value[0][i])); + const float gainVal = gain[0][i]; + azimData_[angFixPi] = gainVal; + azimData_[-angFixPi] = gainVal; + + // transfer only values within (-pi/2, pi/2) to elev table; do not clamp angle values outside of that into elev table. + if (angFixPi > -m_pi_2 && angFixPi < m_pi_2) + { + elevData_[angFixPi] = gainVal; + elevData_[-angFixPi] = gainVal; + } } + // ensure that elev table gets entries for -M_PI_2 and M_PI_2, if those values exist + float azGainVal = gainAtAngle(m_pi_2, azimData_); + if (azGainVal != SMALL_DB_VAL) + elevData_[m_pi_2] = azGainVal; + azGainVal = gainAtAngle(-m_pi_2, azimData_); + if (azGainVal != SMALL_DB_VAL) + elevData_[-m_pi_2] = azGainVal; } break; @@ -874,10 +847,14 @@ int AntennaPatternTable::readPat(std::istream& fp) } for (i = 0; i < tableSize[1]; i++) { - elev = static_cast(angFixPI2(value[1][i])); - elevData_[elev] = gain[1][i]; - // mirror missing data - elevData_[-elev] = gain[1][i]; + elev = static_cast(angFixPI(value[1][i])); + // elevation angles outside of [-PI/2, PI/2] are not valid and are ignored + if (elev >= -m_pi_2 && elev <= m_pi_2) + { + elevData_[elev] = gain[1][i]; + // mirror missing data + elevData_[-elev] = gain[1][i]; + } } } break; @@ -887,23 +864,31 @@ int AntennaPatternTable::readPat(std::istream& fp) { for (i = 0; i < tableSize[0]; i++) { + // this table is supposed to contain the negative azim angle values, but not strictly enforced azim = static_cast(angFixPI(value[0][i])); azimData_[azim] = gain[0][i]; } for (i = 0; i < tableSize[1]; i++) { + // this table is supposed to contain the positive azim angle values, but not strictly enforced azim = static_cast(angFixPI(value[1][i])); azimData_[azim] = gain[1][i]; } for (i = 0; i < tableSize[2]; i++) { - elev = static_cast(angFixPI2(value[2][i])); - elevData_[elev] = gain[2][i]; + // this table is supposed to contain the negative elev angle values, but not strictly enforced + elev = static_cast(angFixPI(value[2][i])); + // elevation angles outside of [-PI/2, PI/2] are not valid and are ignored + if (elev >= -m_pi_2 && elev <= m_pi_2) + elevData_[elev] = gain[2][i]; } for (i = 0; i < tableSize[3]; i++) { - elev = static_cast(angFixPI2(value[3][i])); - elevData_[elev] = gain[3][i]; + // this table is supposed to contain the positive elev angle values, but not strictly enforced + elev = static_cast(angFixPI(value[3][i])); + // elevation angles outside of [-PI/2, PI/2] are not valid and are ignored + if (elev >= -m_pi_2 && elev <= m_pi_2) + elevData_[elev] = gain[3][i]; } } break; @@ -3449,4 +3434,4 @@ int AntennaPatternXFDTD::readPat(const std::string& inFileName) return st; } -} \ No newline at end of file +} diff --git a/SDK/simCore/String/Angle.cpp b/SDK/simCore/String/Angle.cpp index 14f8a22ec..215bdd8b2 100644 --- a/SDK/simCore/String/Angle.cpp +++ b/SDK/simCore/String/Angle.cpp @@ -218,6 +218,9 @@ std::string getAngleString(double radianAngle, GeodeticFormat format, bool allNu } } + // degreeAngle was floor() above so just checking for 360 is OK + if (degreeAngle == 360.0) + degreeAngle = 0.0; degreeAngle = (negative && printNegativeSign) ? -degreeAngle : degreeAngle; std::stringstream strDeg; @@ -257,6 +260,9 @@ std::string getAngleString(double radianAngle, GeodeticFormat format, bool allNu } } + // degreeAngle was floor() above so just checking for 360 is OK + if (degreeAngle == 360.0) + degreeAngle = 0.0; degreeAngle = (negative && printNegativeSign) ? -degreeAngle : degreeAngle; std::stringstream strDeg; @@ -285,6 +291,9 @@ std::string getAngleString(double radianAngle, GeodeticFormat format, bool allNu case FMT_DEGREES: default: { + const double rounding = 5.0 / pow(10.0, precision + 1.0); + if ((degreeAngle + rounding > 360.0) || simCore::areEqual(degreeAngle + rounding, 360.0)) + degreeAngle = 0.0; degreeAngle = (negative && printNegativeSign) ? -degreeAngle : degreeAngle; std::stringstream str; str.setf(std::ios::fixed, std::ios::floatfield); diff --git a/SDK/simCore/String/CsvReader.cpp b/SDK/simCore/String/CsvReader.cpp new file mode 100644 index 000000000..e8051778b --- /dev/null +++ b/SDK/simCore/String/CsvReader.cpp @@ -0,0 +1,94 @@ +/* -*- mode: c++ -*- */ +/**************************************************************************** + ***** ***** + ***** Classification: UNCLASSIFIED ***** + ***** Classified By: ***** + ***** Declassify On: ***** + ***** ***** + **************************************************************************** + * + * + * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. + * EW Modeling & Simulation, Code 5773 + * 4555 Overlook Ave. + * Washington, D.C. 20375-5339 + * + * License for source code at https://simdis.nrl.navy.mil/License.aspx + * + * The U.S. Government retains all rights to use, duplicate, distribute, + * disclose, or release this software. + * + */ +#include "simCore/String/Format.h" +#include "simCore/String/Tokenizer.h" +#include "simCore/String/Utils.h" +#include "simCore/String/CsvReader.h" + +namespace simCore +{ + +CsvReader::CsvReader(std::istream& stream) + : stream_(stream), + commentChar_('#'), + lineNumber_(0) +{ +} + +CsvReader::~CsvReader() +{ +} + +size_t CsvReader::lineNumber() const +{ + return lineNumber_; +} + +void CsvReader::setCommentChar(char commentChar) +{ + commentChar_ = commentChar; +} + +int CsvReader::readLine(std::vector& tokens, bool skipEmptyLines) +{ + tokens.clear(); + std::string line; + while (simCore::getStrippedLine(stream_, line)) + { + lineNumber_++; + + if (line.empty()) + { + if (skipEmptyLines) + continue; + // Not skipping empty lines, return successfully with empty tokens vector + return 0; + } + + // Ignore comments + if (line[0] == commentChar_) + continue; + + simCore::stringTokenizer(tokens, line, ",", true, false); + return 0; + } + return 1; +} + +int CsvReader::readLineTrimmed(std::vector& tokens, bool skipEmptyLines) +{ + const int rv = readLine(tokens, skipEmptyLines); + if (rv != 0) + return rv; + + // Remove leading and trailing whitespace from all tokens + for (size_t i = 0; i < tokens.size(); ++i) + { + const std::string tok = tokens[i]; + if (tok.empty()) + continue; + tokens[i] = simCore::StringUtils::trim(tok); + } + return 0; +} + +} diff --git a/SDK/simCore/String/CsvReader.h b/SDK/simCore/String/CsvReader.h new file mode 100644 index 000000000..9dbeb51d8 --- /dev/null +++ b/SDK/simCore/String/CsvReader.h @@ -0,0 +1,85 @@ +/* -*- mode: c++ -*- */ +/**************************************************************************** + ***** ***** + ***** Classification: UNCLASSIFIED ***** + ***** Classified By: ***** + ***** Declassify On: ***** + ***** ***** + **************************************************************************** + * + * + * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. + * EW Modeling & Simulation, Code 5773 + * 4555 Overlook Ave. + * Washington, D.C. 20375-5339 + * + * License for source code at https://simdis.nrl.navy.mil/License.aspx + * + * The U.S. Government retains all rights to use, duplicate, distribute, + * disclose, or release this software. + * + */ +#ifndef SIMCORE_CSV_READER_H +#define SIMCORE_CSV_READER_H + +#include +#include +#include "simCore/Common/Common.h" + +namespace simCore +{ + +/** + * Simple CSV Reader class. Pass in an istream on construction and read each + * line as needed using readLine(). This class is intended to mirror Python's + * csv reader in that it allows forward-iteration through a csv file and gives + * a vector of tokens for each line as it's read. If functionality similar + * to Python's csv DictReader is desired, a new class will be needed. + */ +class SDKCORE_EXPORT CsvReader +{ +public: + explicit CsvReader(std::istream& stream); + virtual ~CsvReader(); + + /** + * Get the line number of the most recently read line. Line number is incremented + * during line reading and never reset, so if the std::istream& supplied during class + * construction is modified externally to this class, this line number might not be correct. + * @return line number of most recently read line + */ + size_t lineNumber() const; + + /** Set the char that denotes a comment line. Defaults to '#'. */ + void setCommentChar(char commentChar); + + /** + * Read the next line of the stream into the given vector. Will always clear + * the given vector. Will skip empty lines and lines that start with the + * configured comment char. Note that comment detection is rudimentary. + * Inline comments or indented comments will not be detected. + * @param[out] tokens Vector filled with tokens from the next line + * @param[in] skipEmptyLines If true, will skip empty lines when reading. If + * false, will break on empty lines and return 0 with an empty tokens vector. + * @return 0 on successful line read, 1 when the end of the file is reached + */ + int readLine(std::vector& tokens, bool skipEmptyLines = true); + /** + * Reads the next line of the stream into the given vector. This method reads + * identically to readLine(), but trims leading and trailing whitespace from + * each token before returning. + * @param[out] tokens Vector filled with tokens from the next line + * @param[in] skipEmptyLines If true, will skip empty lines when reading. If + * false, will break on empty lines and return 0 with an empty tokens vector. + * @return 0 on successful line read, 1 when the end of the file is reached + */ + int readLineTrimmed(std::vector& tokens, bool skipEmptyLines = true); + +private: + std::istream& stream_; + char commentChar_; + size_t lineNumber_; +}; + +} +#endif /* SIMCORE_CSV_READER_H */ diff --git a/SDK/simCore/Time/Clock.h b/SDK/simCore/Time/Clock.h index 63cb1fec0..25d35b583 100644 --- a/SDK/simCore/Time/Clock.h +++ b/SDK/simCore/Time/Clock.h @@ -162,7 +162,7 @@ class SDKCORE_EXPORT Clock /**@name callbacks for each event type *@{ */ - virtual void onModeChange(Clock::Mode newMode) = 0; + virtual void onModeChange(simCore::Clock::Mode newMode) = 0; virtual void onDirectionChange(simCore::TimeDirection newDirection) = 0; virtual void onScaleChange(double newValue) = 0; virtual void onBoundsChange(const simCore::TimeStamp& start, const simCore::TimeStamp& end) = 0; diff --git a/SDK/simData/CMakeLists.txt b/SDK/simData/CMakeLists.txt index c80af4971..fa42463a5 100644 --- a/SDK/simData/CMakeLists.txt +++ b/SDK/simData/CMakeLists.txt @@ -141,7 +141,7 @@ vsi_protobuf_generate(simData_protobuf_generation "${CMAKE_CURRENT_SOURCE_DIR}/G SIMDATA_PROTO_H SIMDATA_PROTO_CC =dllexport_decl=SDKDATA_EXPORT:.) set_target_properties(simData_protobuf_generation PROPERTIES FOLDER "SIMDIS SDK" - PROJECT_LABEL "SIMDIS SDK - Data Protobuf Generation") + PROJECT_LABEL "simData Protobuf Generation") # ---------------------------------------------------------------------- @@ -159,7 +159,7 @@ endif() add_library(simData ${STATIC_OR_SHARED} ${DATA_PROJECT_FILES}) set_target_properties(simData PROPERTIES FOLDER "SIMDIS SDK" - PROJECT_LABEL "SIMDIS SDK - Data" + PROJECT_LABEL "simData" ) ApplySDKVersion(simData) # Need binary path for generated CMake files diff --git a/SDK/simData/DataStore.h b/SDK/simData/DataStore.h index 16b4baff0..0314e3843 100644 --- a/SDK/simData/DataStore.h +++ b/SDK/simData/DataStore.h @@ -304,10 +304,56 @@ class SDKDATA_EXPORT DataStore /// Types of flushes supported by the flush method enum FlushType { - NON_RECURSIVE, ///< Flush only the supplied entity and keep any static point - NON_RECURSIVE_TSPI_STATIC, ///< Flush only the supplied PLATFORM and flush any static TSPI point - RECURSIVE, ///< Flush the supplied entity and any children and keep any static point - NON_RECURSIVE_TSPI_ONLY ///< Flush TSPI only including static points, keep category data, generic data and data tables + /** + * Flush only the supplied entity and keep any static point + * Flushes Static points: No + * Flushes Commands: Yes + * Flushes Data Tables: No + * Flushes Generic Data: Yes + * Flushes Category Data: Yes + * Applies same operation to Children: No + */ + NON_RECURSIVE, + /** + * Flush only the supplied entity and flush any static point + * Flushes Static points: Yes + * Flushes Commands: Yes + * Flushes Data Tables: No + * Flushes Generic Data: Yes + * Flushes Category Data: Yes + * Applies same operation to Children: No + */ + NON_RECURSIVE_TSPI_STATIC, + /** + * Flush the supplied entity and any children and keep any static point + * Flushes Static points: No + * Flushes Commands: Yes + * Flushes Data Tables: Yes + * Flushes Generic Data: Yes + * Flushes Category Data: Yes + * Applies same operation to Children: Yes + */ + RECURSIVE, + /** + * Flush TSPI only including static points, keep category data, generic data and data tables + * Flushes Static points: Yes + * Flushes Commands: No + * Flushes Data Tables: No + * Flushes Generic Data: No + * Flushes Category Data: No + * Applies same operation to Children: No + */ + NON_RECURSIVE_TSPI_ONLY, + /** + * Flushes points and commands for the supplied entity. Does not flush category data, generic data or data tables. + * Flushes Static points: Yes + * Flushes Commands: Yes + * Flushes Data Tables: No + * Flushes Generic Data: No + * Flushes Category Data: No + * Applies same operation to Children: No + */ + NON_RECURSIVE_DATA }; /** diff --git a/SDK/simData/DataTable.h b/SDK/simData/DataTable.h index 70ecf4765..acc9aa994 100644 --- a/SDK/simData/DataTable.h +++ b/SDK/simData/DataTable.h @@ -419,13 +419,14 @@ class SDKDATA_EXPORT DataTable virtual TableStatus addRow(const TableRow& row) = 0; /** - * Deletes all the data in the data table columns, leaving the columns empty. + * Deletes all the data in the specified data table column, leaving the column empty. + * @param id Column ID of the column to flush or -1 to flush all columns in the table * @return Container to all of the dynamic memory stored in the table. When the * smart pointer falls out of scope, the data gets deleted. This enables a * delayed flush mechanism that can be used to flush in a thread for improved * performance and decreased application latency. */ - virtual DelayedFlushContainerPtr flush() = 0; + virtual DelayedFlushContainerPtr flush(TableColumnId id = -1) = 0; /** diff --git a/SDK/simData/GeneratedCode/simData.proto b/SDK/simData/GeneratedCode/simData.proto index c67538aa1..8c1e48954 100644 --- a/SDK/simData/GeneratedCode/simData.proto +++ b/SDK/simData/GeneratedCode/simData.proto @@ -251,6 +251,34 @@ message CoordinateFrame { optional TangentPlaneOffsets tangentPlaneOffset = 8; }; +// preferences for the display of platform time ticks +message TimeTickPrefs { + enum DrawStyle { + NONE = 0; + POINT = 1; ///< draw ticks as points + LINE = 2; ///< draw ticks as lines + }; + optional DrawStyle drawStyle = 1 [default = NONE]; + /// default color is translucent white + optional fixed32 color = 2 [default = 0xFFFFFF99]; + /// time tick interval in seconds + optional double interval = 3 [default = 10.0]; + /// defines interval to draw the tick larger, draws at multiple of the tick interval; 0 means no large ticks + optional uint32 largeIntervalFactor = 4 [default = 6]; + /// defines interval to draw a label on the tick, draws at a multiple of the tick interval; 0 means no labels + optional uint32 labelIntervalFactor = 5 [default = 6]; + optional string labelFontName = 6 [default = "arial.ttf"]; + optional fixed32 labelFontPointSize = 7 [default = 12]; + /// tick line length in meters + optional double lineLength = 8 [default = 40]; + /// defines how big to draw the large line tick or the point tick, draws large tick as a multiple of lineLength and large point tick as a multiple of lineWidth + optional uint32 largeSizeFactor = 9 [default = 2]; + /// format for the time shown in the label text + optional ElapsedTimeFormat labelTimeFormat = 10 [default = ELAPSED_HOURS]; + /// line and point width for drawing the time ticks + optional double lineWidth = 11 [default = 2]; +}; + /// preferences for the display of platform tracks message TrackPrefs { /// off-white, the default color to use for track history. Track color history at time is defined in a data table, simData::INTERNAL_TRACK_HISTORY_TABLE, in column simData::INTERNAL_TRACK_HISTORY_COLOR_COLUMN @@ -277,6 +305,7 @@ message TrackPrefs { }; optional Mode trackDrawMode = 11 [default = POINT]; + optional TimeTickPrefs timeTicks = 12; }; /// define the text outline style diff --git a/SDK/simData/MemoryDataStore.cpp b/SDK/simData/MemoryDataStore.cpp index 2eb249b60..a3686be88 100644 --- a/SDK/simData/MemoryDataStore.cpp +++ b/SDK/simData/MemoryDataStore.cpp @@ -150,20 +150,21 @@ void updateSparseSlices(EntryListType& entries, double time) * @param id The entity to flush * @param catMap Category Data map * @param genMap Generic Data map -* @param dataOnly If true only remove the data for the update slice +* @param flushCommands If true remove commands +* @param flushCdGd If true remove Category Data and Generic data * @param keepTspiStatic If true static TSPI points are not removed. */ template -void flushEntityData(EntityMap& map, ObjectId id, MemoryDataStore::CategoryDataMap& catMap, MemoryDataStore::GenericDataMap& genMap, bool dataOnly, bool keepTspiStatic = true) +void flushEntityData(EntityMap& map, ObjectId id, MemoryDataStore::CategoryDataMap& catMap, MemoryDataStore::GenericDataMap& genMap, bool flushCommands, bool flushCdGd, bool keepTspiStatic = true) { typename EntityMap::const_iterator i = map.find(id); if (i != map.end()) { (*i).second->updates()->flush(keepTspiStatic); - if (!dataOnly) + if (flushCommands) (*i).second->commands()->flush(); } - if (!dataOnly) + if (flushCdGd) { typename MemoryDataStore::CategoryDataMap::const_iterator ci = catMap.find(id); if (ci != catMap.end()) @@ -251,14 +252,15 @@ class MemoryDataStore::MemoryInternalsMemento : public InternalsMemento public: /** Constructor for a memento for the MemoryDataStore */ explicit MemoryInternalsMemento(const MemoryDataStore &ds) + : interpolator_(ds.interpolator_), + interpolationEnabled_(ds.interpolationEnabled_), + listeners_(ds.listeners_), + scenarioListeners_(ds.scenarioListeners_), + newUpdatesListener_(ds.newUpdatesListener_), + boundClock_(ds.boundClock_) { // fill in everything - interpolator_ = ds.interpolator_; - interpolationEnabled_ = ds.interpolationEnabled_; - listeners_ = ds.listeners_; - scenarioListeners_ = ds.scenarioListeners_; - newUpdatesListener_ = ds.newUpdatesListener_; ds.dataTableManager().getObservers(dtObservers_); ds.categoryNameManager().getListeners(catListeners_); defaultPlatformPrefs_.CopyFrom(ds.defaultPlatformPrefs_); @@ -268,7 +270,6 @@ class MemoryDataStore::MemoryInternalsMemento : public InternalsMemento defaultLobGroupPrefs_.CopyFrom(ds.defaultLobGroupPrefs_); defaultProjectorPrefs_.CopyFrom(ds.defaultProjectorPrefs_); defaultCustomRenderingPrefs_.CopyFrom(ds.defaultCustomRenderingPrefs_); - boundClock_ = ds.boundClock_; } virtual ~MemoryInternalsMemento() @@ -330,7 +331,6 @@ MemoryDataStore::MemoryDataStore() hasChanged_(false), interpolationEnabled_(false), interpolator_(NULL), - timeBounds_(std::numeric_limits::max(), -std::numeric_limits::max()), newUpdatesListener_(new DefaultNewUpdatesListener), dataLimiting_(false), categoryNameManager_(new CategoryNameManager), @@ -352,7 +352,6 @@ MemoryDataStore::MemoryDataStore(const ScenarioProperties &properties) hasChanged_(false), interpolationEnabled_(false), interpolator_(NULL), - timeBounds_(std::numeric_limits::max(), -std::numeric_limits::max()), newUpdatesListener_(new DefaultNewUpdatesListener), dataLimiting_(false), categoryNameManager_(new CategoryNameManager), @@ -811,13 +810,14 @@ void MemoryDataStore::updateCustomRenderings_(double time) void MemoryDataStore::flushEntity_(ObjectId flushId, simData::ObjectType type, FlushType flushType) { bool recursive = (flushType == RECURSIVE); - bool keepTspiStatic = ((flushType != NON_RECURSIVE_TSPI_STATIC) && (flushType != NON_RECURSIVE_TSPI_ONLY)); - bool dataOnly = (flushType == NON_RECURSIVE_TSPI_ONLY); + bool keepTspiStatic = ((flushType != NON_RECURSIVE_TSPI_STATIC) && (flushType != NON_RECURSIVE_TSPI_ONLY) && (flushType != NON_RECURSIVE_DATA)); + bool flushCommands = (flushType != NON_RECURSIVE_TSPI_ONLY); + bool flushCdGd = ((flushType != NON_RECURSIVE_TSPI_ONLY) && (flushType != NON_RECURSIVE_DATA)); IdList ids; switch (type) { case PLATFORM: - flushEntityData(platforms_, flushId, categoryData_, genericData_, dataOnly, keepTspiStatic); + flushEntityData(platforms_, flushId, categoryData_, genericData_, flushCommands, flushCdGd, keepTspiStatic); if (recursive) { beamIdListForHost(flushId, &ids); @@ -838,7 +838,7 @@ void MemoryDataStore::flushEntity_(ObjectId flushId, simData::ObjectType type, F } break; case BEAM: - flushEntityData(beams_, flushId, categoryData_, genericData_, dataOnly); + flushEntityData(beams_, flushId, categoryData_, genericData_, flushCommands, flushCdGd); if (recursive) { gateIdListForHost(flushId, &ids); @@ -847,19 +847,19 @@ void MemoryDataStore::flushEntity_(ObjectId flushId, simData::ObjectType type, F } break; case GATE: - flushEntityData(gates_, flushId, categoryData_, genericData_, dataOnly); + flushEntityData(gates_, flushId, categoryData_, genericData_, flushCommands, flushCdGd); break; case LASER: - flushEntityData(lasers_, flushId, categoryData_, genericData_, dataOnly); + flushEntityData(lasers_, flushId, categoryData_, genericData_, flushCommands, flushCdGd); break; case LOB_GROUP: - flushEntityData(lobGroups_, flushId, categoryData_, genericData_, dataOnly); + flushEntityData(lobGroups_, flushId, categoryData_, genericData_, flushCommands, flushCdGd); break; case PROJECTOR: - flushEntityData(projectors_, flushId, categoryData_, genericData_, dataOnly); + flushEntityData(projectors_, flushId, categoryData_, genericData_, flushCommands, flushCdGd); break; case CUSTOM_RENDERING: - flushEntityData(customRenderings_, flushId, categoryData_, genericData_, dataOnly); + flushEntityData(customRenderings_, flushId, categoryData_, genericData_, flushCommands, flushCdGd); break; case ALL: case NONE: @@ -1508,8 +1508,9 @@ void MemoryDataStore::removeEntity(ObjectId id) Beams::iterator bi = beams_.find(id); if (bi != beams_.end()) { - // also delete any gates + // also delete any gates or projectors; projectorIdListForHost adds to the list gateIdListForHost(id, &ids); + projectorIdListForHost(id, &ids); // we will need to send notifications and recurse on them as well... for (IdList::const_iterator i = ids.begin(); i != ids.end(); ++i) removeEntity(*i); @@ -2653,7 +2654,6 @@ void MemoryDataStore::NewUpdateTransactionImpl::commit() dataStore_->hasChanged_ = true; if (isEntityUpdate_) { - dataStore_->newTimeBound_(updateTime); // Notify the data store's new-update callback dataStore_->newUpdatesListener().onEntityUpdate(dataStore_, id_, updateTime); } @@ -2737,15 +2737,6 @@ MemoryDataStore::NewScenarioGenericUpdateTransactionImpl::~NewScen } //---------------------------------------------------------------------------- -// Updates the scenario time bounds with a new time -void MemoryDataStore::newTimeBound_(double timeVal) -{ - if (timeVal < 0.0) - return; - - timeBounds_.first = simCore::sdkMin(timeVal, timeBounds_.first); - timeBounds_.second = simCore::sdkMax(timeVal, timeBounds_.second); -} /// Helper function to set a pair<> to min/max bounds for an XyzEntry; returns 0 when minMax is changed template MemoryDataStore::timeBounds(ObjectId entityId) const std::pair MemoryDataStore::timeBounds() const { - return timeBounds_; + double min = std::numeric_limits::max(); + double max = -std::numeric_limits::max(); + + for (auto it = platforms_.begin(); it != platforms_.end(); ++it) + { + const auto updates = it->second->updates(); + if ((updates->numItems() == 0) || (updates->firstTime() < 0.0)) + continue; + + min = simCore::sdkMin(min, updates->firstTime()); + max = simCore::sdkMax(max, updates->lastTime()); + } + + return std::pair(min, max); } } diff --git a/SDK/simData/MemoryDataStore.h b/SDK/simData/MemoryDataStore.h index 6261eb499..8503f8bf3 100644 --- a/SDK/simData/MemoryDataStore.h +++ b/SDK/simData/MemoryDataStore.h @@ -652,7 +652,6 @@ class SDKDATA_EXPORT MemoryDataStore : public DataStore CustomRenderings customRenderings_; GenericDataMap genericData_; // Map to hold references for GenericData update slice contained by the DataEntry object with the associated id CategoryDataMap categoryData_; // Map to hold references for CategoryData update slice contained by the DataEntry object with the associated id - std::pair timeBounds_; // First and last time recorded in scenario; might change when adding points or data limiting // default prefs objects PlatformPrefs defaultPlatformPrefs_; @@ -663,9 +662,6 @@ class SDKDATA_EXPORT MemoryDataStore : public DataStore ProjectorPrefs defaultProjectorPrefs_; CustomRenderingPrefs defaultCustomRenderingPrefs_; - // Updates the contents of timeBounds_ - void newTimeBound_(double timeVal); - /// Observers to receive notifications when things change ListenerList listeners_; /// Observers to receive notifications when things change diff --git a/SDK/simData/MemoryTable/DoubleBufferTimeContainer.cpp b/SDK/simData/MemoryTable/DoubleBufferTimeContainer.cpp index 7ff596212..05937bd79 100644 --- a/SDK/simData/MemoryTable/DoubleBufferTimeContainer.cpp +++ b/SDK/simData/MemoryTable/DoubleBufferTimeContainer.cpp @@ -22,6 +22,7 @@ #include #include #include +#include "simCore/Calc/Math.h" #include "simData/MemoryTable/DataColumn.h" #include "simData/MemoryTable/DoubleBufferTimeContainer.h" @@ -700,18 +701,31 @@ void DoubleBufferTimeContainer::limitData(size_t maxPoints, double latestInvalid int DoubleBufferTimeContainer::getTimeRange(double& begin, double& end) const { - if (times_[BIN_FRESH]->empty()) // Need to have some times in the fresh bin + const auto* fresh = times_[BIN_FRESH]; + const auto* stale = times_[BIN_STALE]; + + if (fresh->empty()) { - begin = 0.0; - end = 0.0; - return 1; + if (stale->empty()) + { + begin = 0.0; + end = 0.0; + return 1; + } + begin = stale->front().first; + end = stale->back().first; + return 0; } - begin = times_[BIN_FRESH]->front().first; - if (times_[BIN_STALE]->empty()) - end = times_[BIN_FRESH]->back().first; - else - end = times_[BIN_STALE]->back().first; + if (stale->empty()) + { + begin = fresh->front().first; + end = fresh->back().first; + return 0; + } + + begin = simCore::sdkMin(fresh->front().first, stale->front().first); + end = simCore::sdkMax(fresh->back().first, stale->back().first); return 0; } diff --git a/SDK/simData/MemoryTable/SubTable.cpp b/SDK/simData/MemoryTable/SubTable.cpp index d0cb580f3..06e610480 100644 --- a/SDK/simData/MemoryTable/SubTable.cpp +++ b/SDK/simData/MemoryTable/SubTable.cpp @@ -383,12 +383,52 @@ TableStatus SubTable::removeColumn_(TableColumnId columnId) return TableStatus::Error("Invalid column ID to remove from subtable."); } -simData::DelayedFlushContainerPtr SubTable::flush() +simData::DelayedFlushContainerPtr SubTable::flush(TableColumnId id, SplitObserverPtr splitObserver) { DelayedFlushContainerComposite* deq = new DelayedFlushContainerComposite(); - deq->push_back(timeContainer_->flush()); + // Simple case: Flushing all columns or flushing the only column in the sub table. No need to split + if (id == -1 || (columns_.size() == 1 && columns_.front()->columnId() == id)) + { + deq->push_back(timeContainer_->flush()); + for (std::vector::const_iterator i = columns_.begin(); i != columns_.end(); ++i) + deq->push_back((*i)->flush()); + + return DelayedFlushContainerPtr(deq); + } + + // Complex case: Flushing one column among many + if (splitObserver.get() == NULL) + { + // Split observer is required when flushing a single column. + // Without it, no one will take ownership of the new sub table + assert(0); + return DelayedFlushContainerPtr(deq); + } + + DataColumn* removedCol = NULL; for (std::vector::const_iterator i = columns_.begin(); i != columns_.end(); ++i) - deq->push_back((*i)->flush()); + { + if ((*i)->columnId() == id) + { + deq->push_back((*i)->flush()); + removedCol = *i; + break; + } + } + + // Didn't find the column in this sub table. Nothing to do, return + if (removedCol == NULL) + return DelayedFlushContainerPtr(deq); + + // Split off the flushed column to a new subtable + TimeContainer* newContainer = timeContainer_->clone(); + newContainer->flush(); + SubTable* newTable = new SubTable(newContainer, tableId_); + newTable->takeColumn_(removedCol); + removeColumn_(id); + std::vector idVec; + idVec.push_back(id); + splitObserver->notifySplit(this, newTable, idVec); return DelayedFlushContainerPtr(deq); } @@ -444,4 +484,14 @@ void SubTable::limitData(size_t maxPoints, double latestInvalidTime, DataTable* timeContainer_->limitData(maxPoints, latestInvalidTime, columns_, table, observers); } +void SubTable::takeColumn_(DataColumn* column) +{ + columns_.push_back(column); + // Assertion failure means vector had duplicate column IDs + assert(columnMap_.find((column)->columnId()) == columnMap_.end()); + columnMap_[(column)->columnId()] = column; + // Fix time container ownership so column points to our container and not old owner + column->replaceTimeContainer(timeContainer_); +} + } } diff --git a/SDK/simData/MemoryTable/SubTable.h b/SDK/simData/MemoryTable/SubTable.h index 095a8398a..53929de3e 100644 --- a/SDK/simData/MemoryTable/SubTable.h +++ b/SDK/simData/MemoryTable/SubTable.h @@ -139,7 +139,7 @@ class SDKDATA_EXPORT SubTable /** * Removes all rows from the subtable. */ - simData::DelayedFlushContainerPtr flush(); + simData::DelayedFlushContainerPtr flush(TableColumnId id = -1, SplitObserverPtr splitObserver = SplitObserverPtr()); /** Performs data limiting */ void limitData(size_t maxPoints, double latestInvalidTime, DataTable* table, const std::vector& observers); @@ -208,6 +208,8 @@ class SDKDATA_EXPORT SubTable TableStatus removeColumn_(TableColumnId columnId); /// Fills a row with our contents (but does not set the time), at the specified time void fillRow_(const TimeContainer::IteratorData& timeIdxData, TableRow& row) const; + /// Takes ownership of a previously existing column + void takeColumn_(DataColumn* column); }; } } diff --git a/SDK/simData/MemoryTable/Table.cpp b/SDK/simData/MemoryTable/Table.cpp index 1a5967f26..d3084c83c 100644 --- a/SDK/simData/MemoryTable/Table.cpp +++ b/SDK/simData/MemoryTable/Table.cpp @@ -504,11 +504,24 @@ void Table::limitData_(size_t numToKeep, double timeWindow) } } -simData::DelayedFlushContainerPtr Table::flush() +simData::DelayedFlushContainerPtr Table::flush(TableColumnId id) { DelayedFlushContainerComposite* deq = new DelayedFlushContainerComposite(); - for (std::vector::const_iterator i = subtables_.begin(); i != subtables_.end(); ++i) - deq->push_back((*i)->flush()); + // If flushing all columns, iterate through all subtables + if (id == -1) + { + for (std::vector::const_iterator i = subtables_.begin(); i != subtables_.end(); ++i) + deq->push_back((*i)->flush()); + } + // If flushing only one column, send the flush only to the subtable containing that column. + // Note that the flush can't go straight to the column because the subtable may need to split + else + { + SubTable::SplitObserverPtr splitObserver(new MoveColumnsToNewSubTable(*this)); + auto toFlush = columns_.find(id); + if (toFlush != columns_.end()) + toFlush->second.first->flush(id, splitObserver); + } return DelayedFlushContainerPtr(deq); } diff --git a/SDK/simData/MemoryTable/Table.h b/SDK/simData/MemoryTable/Table.h index 342053a00..a95e706f7 100644 --- a/SDK/simData/MemoryTable/Table.h +++ b/SDK/simData/MemoryTable/Table.h @@ -73,8 +73,8 @@ class SDKDATA_EXPORT Table : public simData::DataTable virtual void accept(DataTable::ColumnVisitor& visitor) const; /** Adds a row to the table. */ virtual TableStatus addRow(const TableRow& row); - /** Clears data out of all columns */ - virtual DelayedFlushContainerPtr flush(); + /** Clears data out of the given column or all columns if given -1 */ + virtual DelayedFlushContainerPtr flush(TableColumnId id = -1); /** Add an observer for notification when rows or columns are added or removed */ virtual void addObserver(TableObserverPtr callback); /** Remove an observer */ diff --git a/SDK/simNotify/CMakeLists.txt b/SDK/simNotify/CMakeLists.txt index 3361ffb57..218ac6551 100644 --- a/SDK/simNotify/CMakeLists.txt +++ b/SDK/simNotify/CMakeLists.txt @@ -36,7 +36,7 @@ endif() add_library(simNotify ${STATIC_OR_SHARED} ${NOTIFY_PROJECT_FILES}) set_target_properties(simNotify PROPERTIES FOLDER "SIMDIS SDK" - PROJECT_LABEL "SIMDIS SDK - Notify" + PROJECT_LABEL "simNotify" ) ApplySDKVersion(simNotify) target_include_directories(simNotify PUBLIC diff --git a/SDK/simQt.h b/SDK/simQt.h index 16d95201a..56cdb3743 100644 --- a/SDK/simQt.h +++ b/SDK/simQt.h @@ -28,9 +28,7 @@ #include "simQt/BoundSettings.h" #include "simQt/CategoryDataBreadcrumbs.h" #include "simQt/CategoryFilterCounter.h" -#include "simQt/CategoryFilterWidget.h" #include "simQt/CategoryTreeModel.h" -#include "simQt/CategoryTreeModel2.h" #include "simQt/CenterEntity.h" #include "simQt/ColorButton.h" #include "simQt/ColorGradient.h" @@ -42,6 +40,7 @@ #include "simQt/DataTableComboBox.h" #include "simQt/DataTableModel.h" #include "simQt/DirectorySelectorWidget.h" +#include "simQt/DndTreeView.h #include "simQt/DockWidget.h" #include "simQt/EntityCategoryFilter.h" #include "simQt/EntityFilter.h" diff --git a/SDK/simQt/AbstractEntityTreeModel.h b/SDK/simQt/AbstractEntityTreeModel.h index 044907086..84e229dbb 100644 --- a/SDK/simQt/AbstractEntityTreeModel.h +++ b/SDK/simQt/AbstractEntityTreeModel.h @@ -58,6 +58,9 @@ namespace simQt { /** Return an Index based on the entity's ID */ virtual QModelIndex index(uint64_t id) const = 0; + /** Return an Index based on the entity's ID; if necessary, process any pending adds */ + virtual QModelIndex index(uint64_t id) = 0; + /** Return the entity's ID for a given index */ virtual uint64_t uniqueId(const QModelIndex &index) const = 0; diff --git a/SDK/simQt/BoundSettings.cpp b/SDK/simQt/BoundSettings.cpp index de2010f3d..f2cf3ca79 100644 --- a/SDK/simQt/BoundSettings.cpp +++ b/SDK/simQt/BoundSettings.cpp @@ -400,10 +400,7 @@ void BoundColorSetting::bindTo(simQt::ColorWidget* colorWidget, bool populateToo { Settings::MetaData metaData; if (settings_.metaData(variableName_, metaData) == 0) - { - if (populateToolTip) - colorWidget->setToolTip(metaData.toolTip()); - } + colorWidget->setToolTip(metaData.toolTip()); } colorWidget->setColor(value_); connect(this, SIGNAL(valueChanged(const QColor&)), colorWidget, SLOT(setColor(const QColor&))); diff --git a/SDK/simQt/CMakeLists.txt b/SDK/simQt/CMakeLists.txt index f6c461ef0..bdd8319b1 100644 --- a/SDK/simQt/CMakeLists.txt +++ b/SDK/simQt/CMakeLists.txt @@ -18,6 +18,7 @@ set(SIMQT_HEADERS_TO_MOC ${SIMQT_INC}ConsoleDataModel.h ${SIMQT_INC}ConsoleLogger.h ${SIMQT_INC}DirectorySelectorWidget.h + ${SIMQT_INC}DndTreeView.h ${SIMQT_INC}DockWidget.h ${SIMQT_INC}FileDescriptorReplacement.h ${SIMQT_INC}FileSelectorWidget.h @@ -65,9 +66,7 @@ if(TARGET simData) list(APPEND SIMQT_HEADERS_TO_MOC ${SIMQT_INC}CategoryDataBreadcrumbs.h ${SIMQT_INC}CategoryFilterCounter.h - ${SIMQT_INC}CategoryFilterWidget.h ${SIMQT_INC}CategoryTreeModel.h - ${SIMQT_INC}CategoryTreeModel2.h ${SIMQT_INC}DataTableComboBox.h ${SIMQT_INC}DataTableModel.h ${SIMQT_INC}EntityCategoryFilter.h @@ -146,6 +145,7 @@ set(SIMQT_SOURCES ${SIMQT_SRC}ConsoleDataModel.cpp ${SIMQT_SRC}ConsoleLogger.cpp ${SIMQT_SRC}DirectorySelectorWidget.cpp + ${SIMQT_SRC}DndTreeView.cpp ${SIMQT_SRC}DockWidget.cpp ${SIMQT_SRC}FileDescriptorReplacement.cpp ${SIMQT_SRC}FileDialog.cpp @@ -187,9 +187,7 @@ if(TARGET simData) list(APPEND SIMQT_SOURCES ${SIMQT_SRC}CategoryDataBreadcrumbs.cpp ${SIMQT_SRC}CategoryFilterCounter.cpp - ${SIMQT_SRC}CategoryFilterWidget.cpp ${SIMQT_SRC}CategoryTreeModel.cpp - ${SIMQT_SRC}CategoryTreeModel2.cpp ${SIMQT_SRC}DataTableComboBox.cpp ${SIMQT_SRC}DataTableModel.cpp ${SIMQT_SRC}EntityCategoryFilter.cpp @@ -291,7 +289,7 @@ endif() add_library(simQt ${STATIC_OR_SHARED} ${SIMQT_PROJECT_FILES}) set_target_properties(simQt PROPERTIES FOLDER "SIMDIS SDK" - PROJECT_LABEL "SIMDIS SDK - Qt" + PROJECT_LABEL "simQt" ) ApplySDKVersion(simQt) target_include_directories(simQt diff --git a/SDK/simQt/CategoryFilterWidget.cpp b/SDK/simQt/CategoryFilterWidget.cpp deleted file mode 100644 index a4669d998..000000000 --- a/SDK/simQt/CategoryFilterWidget.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/* -*- mode: c++ -*- */ -/**************************************************************************** - ***** ***** - ***** Classification: UNCLASSIFIED ***** - ***** Classified By: ***** - ***** Declassify On: ***** - ***** ***** - **************************************************************************** - * - * - * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. - * EW Modeling & Simulation, Code 5773 - * 4555 Overlook Ave. - * Washington, D.C. 20375-5339 - * - * License for source code at https://simdis.nrl.navy.mil/License.aspx - * - * The U.S. Government retains all rights to use, duplicate, distribute, - * disclose, or release this software. - * - */ -#ifdef USE_DEPRECATED_SIMDISSDK_API - // this is deprecated; use simQt::CategoryFilterWidget2 found in CategoryTreeModel2.h - -#include -#include -#include -#include -#include -#include -#include "simData/CategoryData/CategoryNameManager.h" -#include "simData/CategoryData/CategoryFilter.h" -#include "simQt/SearchLineEdit.h" -#include "simQt/CategoryTreeModel.h" -#include "simQt/CategoryTreeModel2.h" -#include "simQt/CategoryFilterWidget.h" - -namespace simQt { - -/** Keep the indexes in the tree so it is not necessary to compare strings */ -static const int CategoryNameRole = Qt::UserRole + 0; -static const int CategoryValueRole = Qt::UserRole + 1; - -CategoryFilterWidget::CategoryFilterWidget(QWidget * parent) - : QWidget(parent), - categoryFilter_(NULL), - dataStore_(NULL), - collapseAction_(NULL), - expandAction_(NULL), - activeFiltering_(false) -{ - setWindowTitle("Category Data Filter:"); - setObjectName("CategoryFilterWidget"); - - treeModel_ = new simQt::CategoryTreeModel(this); - proxy_ = new CategoryProxyModel(); - proxy_->setSortRole(simQt::CategoryTreeModel::SortRole); - proxy_->setSourceModel(treeModel_); - - treeView_ = new QTreeView(); - treeView_->setObjectName("CategoryFilterTree"); - treeView_->setHeaderHidden(true); - treeView_->setRootIsDecorated(false); - treeView_->setSortingEnabled(true); - treeView_->sortByColumn(0, Qt::AscendingOrder); - treeView_->setModel(proxy_); - - collapseAction_ = new QAction(tr("Collapse Values"), this); - connect(collapseAction_, SIGNAL(triggered()), this, SLOT(collapseLeaves_())); - collapseAction_->setIcon(QIcon(":/simQt/images/Collapse.png")); - expandAction_ = new QAction(tr("Expand Values"), this); - connect(expandAction_, SIGNAL(triggered()), treeView_, SLOT(expandAll())); - expandAction_->setIcon(QIcon(":/simQt/images/Expand.png")); - treeView_->setContextMenuPolicy(Qt::ActionsContextMenu); - treeView_->addAction(collapseAction_); - treeView_->addAction(expandAction_); - - search_ = new SearchLineEdit(); - search_->setPlaceholderText(tr("Search Category Data")); - QHBoxLayout* searchLayout = new QHBoxLayout(); - searchLayout->addWidget(search_); - - QVBoxLayout *layout = new QVBoxLayout; - layout->setObjectName("CategoryFilterWidgetVBox"); - layout->setMargin(0); - layout->addItem(searchLayout); - layout->addWidget(treeView_); - setLayout(layout); - - connect(treeModel_, SIGNAL(categoryFilterChanged(simData::CategoryFilter)), this, SIGNAL(categoryFilterChanged(simData::CategoryFilter))); - connect(treeModel_, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(expandDueToModel_(QModelIndex, int, int))); - connect(proxy_, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(expandDueToProxy_(QModelIndex, int, int))); - connect(search_, SIGNAL(textChanged(QString)), this, SLOT(newFilterText_(QString))); - connect(search_, SIGNAL(textChanged(QString)), proxy_, SLOT(setFilterText(QString))); -} - -CategoryFilterWidget::~CategoryFilterWidget() -{ - delete treeView_; - treeView_ = NULL; - delete collapseAction_; - collapseAction_ = NULL; - delete expandAction_; - expandAction_ = NULL; - - delete proxy_; - proxy_ = NULL; - - delete treeModel_; - treeModel_ = NULL; - - delete categoryFilter_; - categoryFilter_ = NULL; -} - -void CategoryFilterWidget::setProviders(simData::DataStore* dataStore) -{ - treeModel_->setProviders(NULL, NULL); - delete categoryFilter_; - categoryFilter_ = NULL; - dataStore_ = dataStore; - if (dataStore_ == NULL) - return; - - categoryFilter_ = new simData::CategoryFilter(dataStore_, true); - - treeModel_->setProviders(dataStore_, categoryFilter_); - treeView_->expandAll(); -} - -void CategoryFilterWidget::setFilter(const simData::CategoryFilter& categoryFilter) -{ - treeModel_->setFilter(categoryFilter); -} - -void CategoryFilterWidget::collapseLeaves_() -{ - treeView_->clearSelection(); - treeView_->collapseAll(); - QModelIndex index = treeModel_->index(0, 0, QModelIndex()); - treeView_->expand(proxy_->mapFromSource(index)); -} - -void CategoryFilterWidget::newFilterText_(const QString& text) -{ - if (text.isEmpty()) - { - // Just remove the last character of a search so expand all to make everything visible - if (activeFiltering_) - treeView_->expandAll(); - - activeFiltering_ = false; - } - else - { - // Just started a search so expand all to make everything visible - if (!activeFiltering_) - treeView_->expandAll(); - - activeFiltering_ = true; - } -} - -void CategoryFilterWidget::expandDueToModel_(const QModelIndex& parentIndex, int to, int from) -{ - if (!activeFiltering_) - return; - - if (!parentIndex.isValid()) - return; - - bool isCategory = !parentIndex.parent().isValid(); - if (isCategory) - return; - - if (!treeView_->isExpanded(parentIndex)) - proxy_->resetFilter(); -} - -void CategoryFilterWidget::expandDueToProxy_(const QModelIndex& parentIndex, int to, int from) -{ - if (!parentIndex.isValid()) - return; - - bool isCategory = !parentIndex.parent().isValid(); - if (isCategory) - { - // check that category name parent (the 'All Categories' node) is expanded - QModelIndex index = treeModel_->index(0, 0, QModelIndex()); - if (!treeView_->isExpanded(index)) - treeView_->expand(proxy_->mapFromSource(index)); - - // The category names are the "to" to "from" and they just showed up, so expand them - for (int ii = to; ii <= from; ++ii) - { - QModelIndex catIndex = parentIndex.model()->index(ii, 0, parentIndex); - treeView_->expand(catIndex); - } - } - else - { - if (activeFiltering_) - { - // Adding a category value; make sure it is visible by expanding its parent - if (!treeView_->isExpanded(parentIndex)) - treeView_->expand(parentIndex); - } - } -} - -} -#endif diff --git a/SDK/simQt/CategoryFilterWidget.h b/SDK/simQt/CategoryFilterWidget.h deleted file mode 100644 index 65d1caecf..000000000 --- a/SDK/simQt/CategoryFilterWidget.h +++ /dev/null @@ -1,121 +0,0 @@ -/* -*- mode: c++ -*- */ -/**************************************************************************** - ***** ***** - ***** Classification: UNCLASSIFIED ***** - ***** Classified By: ***** - ***** Declassify On: ***** - ***** ***** - **************************************************************************** - * - * - * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. - * EW Modeling & Simulation, Code 5773 - * 4555 Overlook Ave. - * Washington, D.C. 20375-5339 - * - * License for source code at https://simdis.nrl.navy.mil/License.aspx - * - * The U.S. Government retains all rights to use, duplicate, distribute, - * disclose, or release this software. - * - */ -#ifdef USE_DEPRECATED_SIMDISSDK_API - // this is deprecated; use simQt::CategoryFilterWidget2 found in CategoryTreeModel2.h - -#ifndef SIMQT_CATEGORY_FILTER_WIDGET_H -#define SIMQT_CATEGORY_FILTER_WIDGET_H - -#include -#include "simCore/Common/Common.h" -#include "simData/CategoryData/CategoryNameManager.h" - -class QLineEdit; -class QString; -class QTreeView; -class QTreeWidgetItem; -class QModelIndex; - -namespace simData { class CategoryFilter; class DataStore; } - -namespace simQt { - -class SearchLineEdit; -class CategoryTreeModel; -class CategoryProxyModel; - -/** -* Widget for displaying the Category Filter in a tree view with check boxes next to the names and values -* call setProviders with a reference to the DataStore, which allows the widget to query the DataStore for -* all possible categories to fill its tree, as well as to listen to the DataStore updates, which will -* adjust the tree. -*/ -class SDKQT_EXPORT CategoryFilterWidget : public QWidget -{ - Q_OBJECT; - -public: - /** Constructor */ - CategoryFilterWidget(QWidget * parent = 0); - - /** Destructor */ - virtual ~CategoryFilterWidget(); - - /** - * Set the reference to the data store, which will update the category tree based on changes to the data store - * @param dataStore - */ - void setProviders(simData::DataStore* dataStore); - - /** - * Returns the current state of all the check boxes in a CategoryFilter object - * @return CategoryFilter - */ - const simData::CategoryFilter& categoryFilter() const { return *categoryFilter_; } - -public slots: - /** - * Updates local categoryFilter and the check boxes on the tree from the categoryFilter supplied - * @param categoryFilter - */ - void setFilter(const simData::CategoryFilter& categoryFilter); - -signals: - /** Emitted whenever the underlying category filter changes */ - void categoryFilterChanged(const simData::CategoryFilter& categoryFilter); - -private slots: - /** Collapse the leaves of the tree */ - void collapseLeaves_(); - /** Expand the given index from the model if filtering */ - void expandDueToModel_(const QModelIndex& parentIndex, int to, int from); - /** Expand the given index from the proxy if filtering */ - void expandDueToProxy_(const QModelIndex& parentIndex, int to, int from); - /** Text for filtering */ - void newFilterText_(const QString& text); - -private: - /** Helper class for managing all the category information */ - simData::CategoryFilter* categoryFilter_; - /** The tree */ - QTreeView* treeView_; - /** Hold the category data */ - CategoryTreeModel* treeModel_; - /** Provides sorting and filtering */ - CategoryProxyModel* proxy_; - /** Category search text */ - SearchLineEdit* search_; - /** The data store */ - simData::DataStore* dataStore_; - /** Action to collapse the leaves in the tree widget */ - QAction* collapseAction_; - /** Action to expand all elements of the tree widget */ - QAction* expandAction_; - /** If true the category values are filtered */ - bool activeFiltering_; -}; - -} - - -#endif -#endif diff --git a/SDK/simQt/CategoryTreeModel.cpp b/SDK/simQt/CategoryTreeModel.cpp index c185f5e65..0db844928 100644 --- a/SDK/simQt/CategoryTreeModel.cpp +++ b/SDK/simQt/CategoryTreeModel.cpp @@ -1,671 +1,2330 @@ /* -*- mode: c++ -*- */ /**************************************************************************** - ***** ***** - ***** Classification: UNCLASSIFIED ***** - ***** Classified By: ***** - ***** Declassify On: ***** - ***** ***** - **************************************************************************** - * - * - * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. - * EW Modeling & Simulation, Code 5773 - * 4555 Overlook Ave. - * Washington, D.C. 20375-5339 - * - * License for source code at https://simdis.nrl.navy.mil/License.aspx - * - * The U.S. Government retains all rights to use, duplicate, distribute, - * disclose, or release this software. - * - */ -#ifdef USE_DEPRECATED_SIMDISSDK_API - // this is deprecated; use simQt::CategoryTreeModel2 -#include +***** ***** +***** Classification: UNCLASSIFIED ***** +***** Classified By: ***** +***** Declassify On: ***** +***** ***** +**************************************************************************** +* +* +* Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. +* EW Modeling & Simulation, Code 5773 +* 4555 Overlook Ave. +* Washington, D.C. 20375-5339 +* +* License for source code at https://simdis.nrl.navy.mil/License.aspx +* +* The U.S. Government retains all rights to use, duplicate, distribute, +* disclose, or release this software. +* +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "simData/CategoryData/CategoryFilter.h" +#include "simData/CategoryData/CategoryNameManager.h" #include "simData/DataStore.h" -#include "simData/DataStoreHelpers.h" +#include "simQt/QtFormatting.h" +#include "simQt/CategoryFilterCounter.h" +#include "simQt/EntityFilterLineEdit.h" +#include "simQt/RegExpImpl.h" +#include "simQt/SearchLineEdit.h" +#include "simQt/Settings.h" #include "simQt/CategoryTreeModel.h" namespace simQt { -static const QString ALL_CATEGORIES = "All Categories"; +/** Lighter than lightGray, matches QPalette::Midlight */ +static const QColor MIDLIGHT_BG_COLOR(227, 227, 227); +/** Breadcrumb's default fill color, used here for background brush on filter items that contribute to filter. */ +static const QColor CONTRIBUTING_BG_COLOR(195, 225, 240); // Light gray with a hint of blue +/** Locked settings keys */ +static const QString LOCKED_SETTING = "LockedCategories"; +/** Locked settings meta data to define it as private */ +static const simQt::Settings::MetaData LOCKED_SETTING_METADATA(Settings::STRING_LIST, "", "", Settings::PRIVATE); -/// Monitors for category data changes + +///////////////////////////////////////////////////////////////////////// + +template +IndexedPointerContainer::IndexedPointerContainer() +{ +} + +template +IndexedPointerContainer::~IndexedPointerContainer() +{ + deleteAll(); +} + +template +T* IndexedPointerContainer::operator[](int index) const +{ + return vec_[index]; +} + +template +int IndexedPointerContainer::indexOf(const T* item) const +{ + // Use a const-cast to help find() to use right signature + const auto i = itemToIndex_.find(const_cast(item)); + return (i == itemToIndex_.end() ? -1 : i->second); +} + +template +int IndexedPointerContainer::size() const +{ + return static_cast(vec_.size()); +} + +template +void IndexedPointerContainer::push_back(T* item) +{ + // Don't add the same item twice + assert(itemToIndex_.find(item) == itemToIndex_.end()); + const int index = size(); + vec_.push_back(item); + itemToIndex_[item] = index; +} + +template +void IndexedPointerContainer::deleteAll() +{ + for (auto i = vec_.begin(); i != vec_.end(); ++i) + delete *i; + vec_.clear(); + itemToIndex_.clear(); +} + +///////////////////////////////////////////////////////////////////////// + +/** +* Base class for an item in the composite pattern of Category Tree Item / Value Tree Item. +* Note that child trees to this class are owned by this class (in the IndexedPointerContainer). +*/ +class TreeItem +{ +public: + TreeItem(); + virtual ~TreeItem(); + + /** Forward from QAbstractItemModel::data() */ + virtual QVariant data(int role) const = 0; + /** Forward from QAbstractItemModel::flags() */ + virtual Qt::ItemFlags flags() const = 0; + /** Returns true if the GUI changed; sets filterChanged if filter edited. */ + virtual bool setData(const QVariant& value, int role, simData::CategoryFilter& filter, bool& filterChanged) = 0; + + /** Retrieves the category name this tree item is associated with */ + virtual QString categoryName() const = 0; + /** Returns the category name integer value for this item or its parent */ + virtual int nameInt() const = 0; + /** Returns true if the UNLISTED VALUE item is checked (i.e. if we are in EXCLUDE mode) */ + virtual bool isUnlistedValueChecked() const = 0; + /** Returns true if the tree item's category is influenced by a regular expression */ + virtual bool isRegExpApplied() const = 0; + + ///@{ Composite Tree Management Methods + TreeItem* parent() const; + int rowInParent() const; + int indexOf(const TreeItem* child) const; + TreeItem* child(int index) const; + int childCount() const; + void addChild(TreeItem* item); + ///@} + +private: + TreeItem* parent_; + simQt::IndexedPointerContainer children_; +}; + +///////////////////////////////////////////////////////////////////////// + +/** Represents a group node in tree, showing a category name and containing children values. */ +class CategoryTreeModel::CategoryItem : public TreeItem +{ +public: + CategoryItem(const simData::CategoryNameManager& nameManager, int nameInt); + + /** TreeItem Overrides */ + virtual Qt::ItemFlags flags() const; + virtual QVariant data(int role) const; + virtual bool setData(const QVariant& value, int role, simData::CategoryFilter& filter, bool& filterChanged); + virtual QString categoryName() const; + virtual int nameInt() const; + virtual bool isUnlistedValueChecked() const; + virtual bool isRegExpApplied() const; + + /** Recalculates the "contributes to filter" flag, returning true if it changes (like setData()) */ + bool recalcContributionTo(const simData::CategoryFilter& filter); + + /** Changes the font to use. */ + void setFont(QFont* font); + /** Sets the state of the GUI to match the state of the filter. Returns 0 if nothing changed. */ + int updateTo(const simData::CategoryFilter& filter); + + /** Sets the ID counts for each value under this category name tree, returning true if there is a change. */ + bool updateCounts(const std::map& valueToCountMap) const; + +private: + /** Checks and unchecks children based on whether they match the filter, returning true if any checks change. */ + bool setChildChecks_(const simData::RegExpFilter* reFilter); + + /** Changes the filter to match the check state of the Value Item. */ + void updateFilter_(const ValueItem& valueItem, simData::CategoryFilter& filter) const; + /** Change the value item to match the state of the checks structure (filter). Returns 0 on no change. */ + int updateValueItem_(ValueItem& valueItem, const simData::CategoryFilter::ValuesCheck& checks) const; + + /** setData() variant that handles the ROLE_EXCLUDE role */ + bool setExcludeData_(const QVariant& value, simData::CategoryFilter& filter, bool& filterChanged); + /** setData() variant that handles ROLE_REGEXP_STRING role */ + bool setRegExpStringData_(const QVariant& value, simData::CategoryFilter& filter, bool& filterChanged); + + /** String representation of NAME. */ + QString categoryName_; + /** Integer representation of NAME. */ + int nameInt_; + /** Cache the state of the UNLISTED VALUE. When TRUE, we're in EXCLUDE mode */ + bool unlistedValue_; + /** Category's Regular Expression string value */ + QString regExpString_; + /** Set to true if this category contributes to the filter. */ + bool contributesToFilter_; + /** Font to use for FontRole (not owned) */ + QFont* font_; + /** Tracks whether this category item is locked */ + bool locked_; +}; + +///////////////////////////////////////////////////////////////////////// + +/** Represents a leaf node in tree, showing a category value. */ +class CategoryTreeModel::ValueItem : public TreeItem +{ +public: + ValueItem(const simData::CategoryNameManager& nameManager, int nameInt, int valueInt); + + /** TreeItem Overrides */ + virtual Qt::ItemFlags flags() const; + virtual QVariant data(int role) const; + virtual bool setData(const QVariant& value, int role, simData::CategoryFilter& filter, bool& filterChanged); + virtual QString categoryName() const; + virtual int nameInt() const; + virtual bool isUnlistedValueChecked() const; + virtual bool isRegExpApplied() const; + + /** Returns the value integer for this item */ + int valueInt() const; + /** Returns the value string for this item; for NO_CATEGORY_VALUE_AT_TIME, empty string is returned. */ + QString valueString() const; + + /** + * Changes the GUI state of whether this item is checked. This does not match 1-for-1 + * with the filter state, and does not directly update any CategoryFilter instance. + */ + void setChecked(bool value); + /** Returns true if the GUI state is such that this item is checked. */ + bool isChecked() const; + + /** Sets the number of entities that match this value. Use -1 to reset. */ + void setNumMatches(int numMatches); + /** Returns number entities that match this particular value in the given filter. */ + int numMatches() const; + +private: + /** setData() that handles Qt::CheckStateRole. Returns true if GUI state changes, and sets filterChanged if filter changes. */ + bool setCheckStateData_(const QVariant& value, simData::CategoryFilter& filter, bool& filterChanged); + + int nameInt_; + int valueInt_; + int numMatches_; + Qt::CheckState checked_; + QString valueString_; +}; + +///////////////////////////////////////////////////////////////////////// + +TreeItem::TreeItem() + : parent_(NULL) +{ +} + +TreeItem::~TreeItem() +{ + children_.deleteAll(); +} + +TreeItem* TreeItem::parent() const +{ + return parent_; +} + +int TreeItem::rowInParent() const +{ + if (parent_ == NULL) + { + // Caller is getting an invalid value + assert(0); + return -1; + } + return parent_->indexOf(this); +} + +int TreeItem::indexOf(const TreeItem* child) const +{ + return children_.indexOf(child); +} + +TreeItem* TreeItem::child(int index) const +{ + return children_[index]; +} + +int TreeItem::childCount() const +{ + return children_.size(); +} + +void TreeItem::addChild(TreeItem* item) +{ + // Assertion failure means developer is doing something weird. + assert(item != NULL); + // Assertion failure means that item is inserted more than once. + assert(item->parent() == NULL); + + // Set the parent and save the item in our children vector. + item->parent_ = this; + children_.push_back(item); +} + +///////////////////////////////////////////////////////////////////////// + +CategoryTreeModel::CategoryItem::CategoryItem(const simData::CategoryNameManager& nameManager, int nameInt) + : categoryName_(QString::fromStdString(nameManager.nameIntToString(nameInt))), + nameInt_(nameInt), + unlistedValue_(false), + contributesToFilter_(false), + font_(NULL), + locked_(false) +{ +} + +bool CategoryTreeModel::CategoryItem::isUnlistedValueChecked() const +{ + return unlistedValue_; +} + +bool CategoryTreeModel::CategoryItem::isRegExpApplied() const +{ + return !regExpString_.isEmpty(); +} + +int CategoryTreeModel::CategoryItem::nameInt() const +{ + return nameInt_; +} + +QString CategoryTreeModel::CategoryItem::categoryName() const +{ + return categoryName_; +} + +Qt::ItemFlags CategoryTreeModel::CategoryItem::flags() const +{ + return Qt::ItemIsEnabled; +} + +QVariant CategoryTreeModel::CategoryItem::data(int role) const +{ + switch (role) + { + case Qt::DisplayRole: + case Qt::EditRole: + case ROLE_SORT_STRING: + case ROLE_CATEGORY_NAME: + return categoryName_; + case ROLE_EXCLUDE: + return unlistedValue_; + case ROLE_REGEXP_STRING: + return regExpString_; + case ROLE_LOCKED_STATE: + return locked_; + case Qt::BackgroundColorRole: + if (contributesToFilter_) + return CONTRIBUTING_BG_COLOR; + return MIDLIGHT_BG_COLOR; + case Qt::FontRole: + if (font_) + return *font_; + break; + default: + break; + } + return QVariant(); +} + +bool CategoryTreeModel::CategoryItem::setData(const QVariant& value, int role, simData::CategoryFilter& filter, bool& filterChanged) +{ + if (role == ROLE_EXCLUDE) + return setExcludeData_(value, filter, filterChanged); + else if (role == ROLE_REGEXP_STRING) + return setRegExpStringData_(value, filter, filterChanged); + else if (role == ROLE_LOCKED_STATE && locked_ != value.toBool()) + { + locked_ = value.toBool(); + filterChanged = true; + return true; + } + filterChanged = false; + return false; +} + +bool CategoryTreeModel::CategoryItem::setExcludeData_(const QVariant& value, simData::CategoryFilter& filter, bool& filterChanged) +{ + filterChanged = false; + // If value does not change, or if disabled, then return early + if (value.toBool() == unlistedValue_ || !flags().testFlag(Qt::ItemIsEnabled)) + return false; + + // Update the value + unlistedValue_ = value.toBool(); + + // If the filter does not include our category, then we do nothing RE: filter + auto values = filter.getCategoryFilter(); + if (values.find(nameInt_) == values.end()) + return true; // True, update our GUI -- but note that the filter did not change + + // Remove the whole name from the filter, then build it from scratch from GUI + filterChanged = true; + filter.removeName(nameInt_); + filter.setValue(nameInt_, simData::CategoryNameManager::UNLISTED_CATEGORY_VALUE, unlistedValue_); + const int count = childCount(); + for (int k = 0; k < count; ++k) + updateFilter_(*static_cast(child(k)), filter); + filter.simplify(nameInt_); + + // Update the flag for contributing to the filter + recalcContributionTo(filter); + return true; +} + +bool CategoryTreeModel::CategoryItem::setRegExpStringData_(const QVariant& value, simData::CategoryFilter& filter, bool& filterChanged) +{ + // Check for easy no-op + filterChanged = false; + if (value.toString() == regExpString_) + return false; + + // Update the value + regExpString_ = value.toString(); + filterChanged = true; + + // Create/set the regular expression + simData::RegExpFilterPtr newRegExpObject; + if (!regExpString_.isEmpty()) + { + // The factory could/should be passed in for maximum flexibility + simQt::RegExpFilterFactoryImpl reFactory; + newRegExpObject = reFactory.createRegExpFilter(regExpString_.toStdString()); + } + + // Set the RegExp, simplify, and update the internal state + filter.setCategoryRegExp(nameInt_, newRegExpObject); + filter.simplify(nameInt_); + recalcContributionTo(filter); + setChildChecks_(newRegExpObject.get()); + return true; +} + +bool CategoryTreeModel::CategoryItem::recalcContributionTo(const simData::CategoryFilter& filter) +{ + // First check the regular expression. If there's a regexp, then this category definitely contributes + const bool newValue = filter.nameContributesToFilter(nameInt_); + if (newValue == contributesToFilter_) + return false; + contributesToFilter_ = newValue; + return true; +} + +void CategoryTreeModel::CategoryItem::setFont(QFont* font) +{ + font_ = font; +} + +bool CategoryTreeModel::CategoryItem::setChildChecks_(const simData::RegExpFilter* reFilter) +{ + bool hasChange = false; + const int count = childCount(); + for (int k = 0; k < count; ++k) + { + // Test the EditRole, which is used because it omits the # count (e.g. "Friendly (1)") + ValueItem* valueItem = static_cast(child(k)); + const bool matches = reFilter != NULL && reFilter->match(valueItem->valueString().toStdString()); + if (matches != valueItem->isChecked()) + { + valueItem->setChecked(matches); + hasChange = true; + } + } + return hasChange; +} + +int CategoryTreeModel::CategoryItem::updateTo(const simData::CategoryFilter& filter) +{ + // Update the category if it has a RegExp + const QString oldRegExp = regExpString_; + const auto* regExpObject = filter.getRegExp(nameInt_); + regExpString_ = (regExpObject != NULL ? QString::fromStdString(filter.getRegExpPattern(nameInt_)) : ""); + // If the RegExp string is different, we definitely have some sort of change + bool hasChange = (regExpString_ != oldRegExp); + + // Case 1: Regular Expression is not empty. Check and uncheck values as needed + if (!regExpString_.isEmpty()) + { + // Synchronize the checks of the children + if (setChildChecks_(regExpObject)) + hasChange = true; + return hasChange ? 1 : 0; + } + + // No RegExp -- pull out the category checks + simData::CategoryFilter::ValuesCheck checks; + filter.getValues(nameInt_, checks); + + // Case 2: Filter doesn't have this category. Uncheck all children + if (checks.empty()) + { + const int count = childCount(); + for (int k = 0; k < count; ++k) + { + ValueItem* valueItem = static_cast(child(k)); + if (valueItem->isChecked()) + { + valueItem->setChecked(false); + hasChange = true; + } + } + + // Fix filter on/off + if (recalcContributionTo(filter)) + hasChange = true; + return hasChange ? 1 : 0; + } + + // Case 3: We are in the filter, so our unlistedValueBool matters + auto i = checks.find(simData::CategoryNameManager::UNLISTED_CATEGORY_VALUE); + if (i != checks.end()) + { + // Unlisted value present means it must be on + assert(i->second); + } + + // Detect change in Unlisted Value state + const bool newUnlistedValue = (i != checks.end() && i->second); + if (unlistedValue_ != newUnlistedValue) + hasChange = true; + unlistedValue_ = newUnlistedValue; + + // Iterate through children and make sure the state matches + const int count = childCount(); + for (int k = 0; k < count; ++k) + { + if (0 != updateValueItem_(*static_cast(child(k)), checks)) + hasChange = true; + } + + // Update the flag for contributing to the filter + if (recalcContributionTo(filter)) + hasChange = true; + + return hasChange ? 1 : 0; +} + +void CategoryTreeModel::CategoryItem::updateFilter_(const ValueItem& valueItem, simData::CategoryFilter& filter) const +{ + const bool filterValue = (valueItem.isChecked() != unlistedValue_); + // NO_VALUE is a special case + if (valueItem.valueInt() == simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME) + { + if (filterValue) + filter.setValue(nameInt_, valueItem.valueInt(), true); + } + else + { + if (filterValue != unlistedValue_) + filter.setValue(nameInt_, valueItem.valueInt(), filterValue); + } +} + +int CategoryTreeModel::CategoryItem::updateValueItem_(ValueItem& valueItem, const simData::CategoryFilter::ValuesCheck& checks) const +{ + // NO VALUE is a special case unfortunately + const auto i = checks.find(valueItem.valueInt()); + bool nextCheckedState = false; + if (valueItem.valueInt() == simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME) + { + // Item is a NO-VALUE item. This does not follow the rules of "unlisted value" + // in CategoryFilter class, so it's a special case, because we DO want to follow + // logical rules for the end user here in this GUI. + const bool showingNoValue = (i != checks.end() && i->second); + // If unlisted value is false, then we show the NO VALUE as checked if its check + // is present and on. If unlisted value is true, then we invert the display + // so that No-Value swaps into No-No-Value, or Has-Value for short. This all + // simplifies into the expression "setChecked(unlisted != showing)". + nextCheckedState = (unlistedValue_ != showingNoValue); + } + else if (unlistedValue_) + { + // "Harder" case. Unlisted Values are checked, so GUI is showing "omit" or "not" + // states. If it's checked, then we're explicitly omitting that value. So the + // only way to omit is if there is an entry in the checks, and it's set false. + nextCheckedState = (i != checks.end() && !i->second); + } + else + { + // "Simple" case. Unlisted Values are unchecked, so we're matching ONLY items + // that are in the filter, that are checked. So to be checked in the GUI, the + // value must have a checkmark + nextCheckedState = (i != checks.end() && i->second); + } + + if (nextCheckedState == valueItem.isChecked()) + return 0; + valueItem.setChecked(nextCheckedState); + return 1; +} + +bool CategoryTreeModel::CategoryItem::updateCounts(const std::map& valueToCountMap) const +{ + const int numValues = childCount(); + bool haveChange = false; + for (int k = 0; k < numValues; ++k) + { + ValueItem* valueItem = dynamic_cast(child(k)); + // All children should be ValueItems + assert(valueItem); + if (!valueItem) + continue; + + // It's entirely possible (through async methods) that the incoming value count map is not + // up to date. This can occur if a count starts and more categories get added before the + // count finishes, and is common. + auto i = valueToCountMap.find(valueItem->valueInt()); + int nextMatch = -1; + if (i != valueToCountMap.end()) + nextMatch = static_cast(i->second); + + // Set the number of matches and record a change + if (valueItem->numMatches() != nextMatch) + { + valueItem->setNumMatches(nextMatch); + haveChange = true; + } + } + + return haveChange; +} + +///////////////////////////////////////////////////////////////////////// + +CategoryTreeModel::ValueItem::ValueItem(const simData::CategoryNameManager& nameManager, int nameInt, int valueInt) + : nameInt_(nameInt), + valueInt_(valueInt), + numMatches_(-1), + checked_(Qt::Unchecked), + valueString_(QString::fromStdString(nameManager.valueIntToString(valueInt))) +{ +} + +bool CategoryTreeModel::ValueItem::isUnlistedValueChecked() const +{ + // Assertion failure means we have orphan value items + assert(parent()); + if (!parent()) + return false; + return parent()->isUnlistedValueChecked(); +} + +bool CategoryTreeModel::ValueItem::isRegExpApplied() const +{ + // Assertion failure means we have orphan value items + assert(parent()); + if (!parent()) + return false; + return parent()->isRegExpApplied(); +} + +int CategoryTreeModel::ValueItem::nameInt() const +{ + return nameInt_; +} + +QString CategoryTreeModel::ValueItem::categoryName() const +{ + // Assertion failure means we have orphan value items + assert(parent()); + if (!parent()) + return ""; + return parent()->data(ROLE_CATEGORY_NAME).toString(); +} + +int CategoryTreeModel::ValueItem::valueInt() const +{ + return valueInt_; +} + +QString CategoryTreeModel::ValueItem::valueString() const +{ + // "No Value" should return empty string here, not user-facing string + if (valueInt_ == simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME) + return ""; + return valueString_; +} + +Qt::ItemFlags CategoryTreeModel::ValueItem::flags() const +{ + if (isRegExpApplied()) + return Qt::NoItemFlags; + return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; +} + +QVariant CategoryTreeModel::ValueItem::data(int role) const +{ + switch (role) + { + case Qt::DisplayRole: + case Qt::EditRole: + { + QString returnString; + if (!isUnlistedValueChecked()) + returnString = valueString_; + else if (valueInt_ == simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME) + returnString = tr("Has Value"); + else + returnString = tr("Not %1").arg(valueString_); + // Append the numeric count if specified -- only if in include mode, and NOT in exclude mode + if (numMatches_ >= 0 && !isUnlistedValueChecked()) + returnString = tr("%1 (%2)").arg(returnString).arg(numMatches_); + return returnString; + } + + case Qt::CheckStateRole: + return checked_; + + case ROLE_SORT_STRING: + if (valueInt_ == simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME) + return QString(""); + return data(Qt::DisplayRole); + + case ROLE_EXCLUDE: + return isUnlistedValueChecked(); + + case ROLE_CATEGORY_NAME: + return categoryName(); + + case ROLE_REGEXP_STRING: + // Parent node holds the RegExp string + if (parent()) + return parent()->data(ROLE_REGEXP_STRING); + break; + + case ROLE_LOCKED_STATE: + // Parent node holds the lock state + if (parent()) + return parent()->data(ROLE_LOCKED_STATE); + break; + + default: + break; + } + return QVariant(); +} + +bool CategoryTreeModel::ValueItem::setData(const QVariant& value, int role, simData::CategoryFilter& filter, bool& filterChanged) +{ + // Internally handle check/uncheck value. For ROLE_REGEXP and ROLE_LOCKED_STATE, rely on category parent + if (role == Qt::CheckStateRole) + return setCheckStateData_(value, filter, filterChanged); + else if (role == ROLE_REGEXP_STRING && parent() != NULL) + return parent()->setData(value, role, filter, filterChanged); + else if (role == ROLE_LOCKED_STATE && parent() != NULL) + return parent()->setData(value, role, filter, filterChanged); + filterChanged = false; + return false; +} + +bool CategoryTreeModel::ValueItem::setCheckStateData_(const QVariant& value, simData::CategoryFilter& filter, bool& filterChanged) +{ + filterChanged = false; + + // If the edit sets us to same state, or disabled, then return early + const Qt::CheckState newChecked = static_cast(value.toInt()); + if (newChecked == checked_ || !flags().testFlag(Qt::ItemIsEnabled)) + return false; + + // Figure out how to translate the GUI state into the filter value + checked_ = newChecked; + const bool unlistedValue = isUnlistedValueChecked(); + const bool checkedBool = (checked_ == Qt::Checked); + const bool filterValue = (unlistedValue != checkedBool); + + // Change the value in the filter. NO VALUE is a special case + if (valueInt_ == simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME) + { + // If the filter value is off, then remove it from the filter; it's always off by default + if (!filterValue) + filter.removeValue(nameInt_, valueInt_); + else + filter.setValue(nameInt_, valueInt_, true); + } + else + { + // Remove items that match unlisted value. Add items that do not. + if (filterValue == unlistedValue) + filter.removeValue(nameInt_, valueInt_); + else + { + // If the filter was previously empty and we're setting a value, we need to + // make sure that the "No Value" check is correctly set in some cases. + if (!filterValue && unlistedValue) + { + simData::CategoryFilter::ValuesCheck checks; + filter.getValues(nameInt_, checks); + if (checks.empty()) + filter.setValue(nameInt_, simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME, true); + } + + filter.setValue(nameInt_, valueInt_, filterValue); + } + } + + // Ensure UNLISTED VALUE is set correctly. + if (unlistedValue) + filter.setValue(nameInt_, simData::CategoryNameManager::UNLISTED_CATEGORY_VALUE, true); + else + filter.removeValue(nameInt_, simData::CategoryNameManager::UNLISTED_CATEGORY_VALUE); + // Make sure the filter is simplified + filter.simplify(nameInt_); + + // Update the parent too, which fixes the GUI for whether it contributes + CategoryItem* parentTree = dynamic_cast(parent()); + parentTree->recalcContributionTo(filter); + + filterChanged = true; + return true; +} + +void CategoryTreeModel::ValueItem::setChecked(bool value) +{ + checked_ = (value ? Qt::Checked : Qt::Unchecked); +} + +bool CategoryTreeModel::ValueItem::isChecked() const +{ + return checked_ == Qt::Checked; +} + +void CategoryTreeModel::ValueItem::setNumMatches(int matches) +{ + numMatches_ = matches; +} + +int CategoryTreeModel::ValueItem::numMatches() const +{ + return numMatches_; +} + +///////////////////////////////////////////////////////////////////////// + +/// Monitors for category data changes, calling methods in CategoryTreeModel. class CategoryTreeModel::CategoryFilterListener : public simData::CategoryNameManager::Listener { public: /// Constructor - explicit CategoryFilterListener(CategoryTreeModel* parent) : parent_(parent) + explicit CategoryFilterListener(CategoryTreeModel& parent) + : parent_(parent) + { + } + + virtual ~CategoryFilterListener() + { + } + + /// Invoked when a new category is added + virtual void onAddCategory(int categoryIndex) { + parent_.addName_(categoryIndex); } - virtual ~CategoryFilterListener() + /// Invoked when a new value is added to a category + virtual void onAddValue(int categoryIndex, int valueIndex) + { + parent_.addValue_(categoryIndex, valueIndex); + } + + /// Invoked when all data is cleared + virtual void onClear() + { + parent_.clearTree_(); + } + + /// Invoked when all listeners have received onClear() + virtual void doneClearing() + { + // noop + } + +private: + CategoryTreeModel& parent_; +}; + +///////////////////////////////////////////////////////////////////////// + +CategoryProxyModel::CategoryProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ +} + +CategoryProxyModel::~CategoryProxyModel() +{ +} + +void CategoryProxyModel::resetFilter() +{ + invalidateFilter(); +} + +void CategoryProxyModel::setFilterText(const QString& filter) +{ + if (filter_ == filter) + return; + + filter_ = filter; + invalidateFilter(); +} + +bool CategoryProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + if (filter_.isEmpty()) + return true; + + const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + const QString itemText = index.data(Qt::DisplayRole).toString(); + + // include items that pass the filter + if (itemText.contains(filter_, Qt::CaseInsensitive)) + return true; + + // include items whose parent passes the filter, but not if parent is root "All Categories" item + if (sourceParent.isValid()) + { + const QString parentText = sourceParent.data(Qt::DisplayRole).toString(); + + if (parentText.contains(filter_, Qt::CaseInsensitive)) + return true; + } + + // include items with any children that pass the filter + const int numChildren = sourceModel()->rowCount(index); + for (int ii = 0; ii < numChildren; ++ii) + { + const QModelIndex childIndex = sourceModel()->index(ii, 0, index); + // Assertion failure means rowCount() was wrong + assert(childIndex.isValid()); + const QString childText = childIndex.data(Qt::DisplayRole).toString(); + if (childText.contains(filter_, Qt::CaseInsensitive)) + return true; + } + return false; +} + +///////////////////////////////////////////////////////////////////////// + +CategoryTreeModel::CategoryTreeModel(QObject* parent) + : QAbstractItemModel(parent), + dataStore_(NULL), + filter_(new simData::CategoryFilter(NULL)), + categoryFont_(new QFont), + settings_(NULL) +{ + listener_.reset(new CategoryFilterListener(*this)); + + // Increase the point size on the category + categoryFont_->setPointSize(categoryFont_->pointSize() + 4); + categoryFont_->setBold(true); +} + +CategoryTreeModel::~CategoryTreeModel() +{ + categories_.deleteAll(); + categoryIntToItem_.clear(); + delete categoryFont_; + categoryFont_ = NULL; + delete filter_; + filter_ = NULL; + if (dataStore_) + dataStore_->categoryNameManager().removeListener(listener_); +} + +QModelIndex CategoryTreeModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + // Category items have no parent in the model + if (!parent.isValid()) + return createIndex(row, column, categories_[row]); + // Has a parent: must be a value item + TreeItem* parentItem = static_cast(parent.internalPointer()); + // Item was not made correctly, check index() + assert(parentItem != NULL); + return createIndex(row, column, parentItem->child(row)); +} + +QModelIndex CategoryTreeModel::parent(const QModelIndex &child) const +{ + if (!child.isValid() || !child.internalPointer()) + return QModelIndex(); + + // Child could be a category (no parent) or a value (category parent) + const TreeItem* childItem = static_cast(child.internalPointer()); + TreeItem* parentItem = childItem->parent(); + if (parentItem == NULL) // child is a category; no parent + return QModelIndex(); + return createIndex(categories_.indexOf(static_cast(parentItem)), 0, parentItem); +} + +int CategoryTreeModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + { + if (parent.column() != 0) + return 0; + TreeItem* parentItem = static_cast(parent.internalPointer()); + return (parentItem == NULL) ? 0 : parentItem->childCount(); + } + return categories_.size(); +} + +int CategoryTreeModel::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +QVariant CategoryTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || !index.internalPointer()) + return QVariant(); + const TreeItem* treeItem = static_cast(index.internalPointer()); + return treeItem->data(role); +} + +QVariant CategoryTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole)) + { + if (section == 0) + return tr("Category"); + + // A column was added and this section was not updated + assert(0); + return QVariant(); + } + + // Isn't the bar across the top -- fall back to whatever QAIM does + return QAbstractItemModel::headerData(section, orientation, role); +} + +Qt::ItemFlags CategoryTreeModel::flags(const QModelIndex& index) const +{ + if (!index.isValid() || !index.internalPointer()) + return Qt::NoItemFlags; + TreeItem* item = static_cast(index.internalPointer()); + return item->flags(); +} + +bool CategoryTreeModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + // Ensure we have a valid index with a valid TreeItem pointer + if (!index.isValid() || !index.internalPointer()) + return QAbstractItemModel::setData(index, value, role); + + // NULL filter means the tree should be empty, so we shouldn't get setData()... + TreeItem* item = static_cast(index.internalPointer()); + assert(filter_ && item); + bool wasEdited = false; + const bool rv = item->setData(value, role, *filter_, wasEdited); + + // update locked setting for this category if it is a category item and this is a locked state update + if (settings_ && item->childCount() > 0 && role == ROLE_LOCKED_STATE) + { + QStringList lockedCategories = settings_->value(settingsKey_, LOCKED_SETTING_METADATA).toStringList(); + lockedCategories.removeOne(item->categoryName()); + if (value.toBool()) + lockedCategories.push_back(item->categoryName()); + settings_->setValue(settingsKey_, lockedCategories); + } + + // Logic below needs to change if this assert triggers. Basically, GUI may + // update without the filter updating, but not vice versa. + assert(rv || !wasEdited); + if (rv) + { + // Update the GUI + emit dataChanged(index, index); + + // Alert users who are listening + if (wasEdited) + { + // Parent index, if it exists, is a category and might have updated its color data() + const QModelIndex parentIndex = index.parent(); + if (parentIndex.isValid()) + emit dataChanged(parentIndex, parentIndex); + + emit filterChanged(*filter_); + emit filterEdited(*filter_); + } + else + { + // Should only happen in cases where EXCLUDE got changed, but no filter was edited + assert(!index.parent().isValid()); + emit excludeEdited(item->nameInt(), item->isUnlistedValueChecked()); + } + } + return rv; +} + +void CategoryTreeModel::setFilter(const simData::CategoryFilter& filter) +{ + // Check the data store; if it's set in filter and different from ours, update + if (filter.getDataStore() && filter.getDataStore() != dataStore_) + setDataStore(filter.getDataStore()); + + // Avoid no-op + simData::CategoryFilter simplified(filter); + simplified.simplify(); + if (filter_ != NULL && simplified == *filter_) + return; + + // Do a two step assignment so that we don't automatically get auto-update + if (filter_ == NULL) + filter_ = new simData::CategoryFilter(filter.getDataStore()); + filter_->assign(simplified, false); + + const int categoriesSize = categories_.size(); + if (categoriesSize == 0) + { + // This means we have a simplified filter that is DIFFERENT from our current + // filter, AND it means we have no items in the GUI. It means we're out of + // sync and something is not right. Check into it. + assert(0); + return; + } + + // Update to the filter, but detect which rows changed so we can simplify dataChanged() + // for performance reasons. This will prevent the display from updating too much. + int firstChangeRow = -1; + int lastChangeRow = -1; + for (int k = 0; k < categoriesSize; ++k) + { + // Detect change and record the row number + if (categories_[k]->updateTo(*filter_) != 0) + { + if (firstChangeRow == -1) + firstChangeRow = k; + lastChangeRow = k; + } + } + // This shouldn't happen because we checked the simplified filters. If this + // assert triggers, then we have a change in filter (detected above) but the + // GUI didn't actually change. Maybe filter compare failed, or updateTo() + // is returning incorrect values. + assert(firstChangeRow != -1 && lastChangeRow != -1); + if (firstChangeRow != -1 && lastChangeRow != -1) + { + emit dataChanged(index(firstChangeRow, 0), index(lastChangeRow, 0)); + } + emit filterChanged(*filter_); +} + +const simData::CategoryFilter& CategoryTreeModel::categoryFilter() const +{ + // Precondition of this method is that data store was set; filter must be non-NULL + assert(filter_); + return *filter_; +} + +void CategoryTreeModel::setDataStore(simData::DataStore* dataStore) +{ + if (dataStore_ == dataStore) + return; + + // Update the listeners on name manager as we change it + if (dataStore_ != NULL) + dataStore_->categoryNameManager().removeListener(listener_); + dataStore_ = dataStore; + if (dataStore_ != NULL) + dataStore_->categoryNameManager().addListener(listener_); + + beginResetModel(); + + // Clear out the internal storage on the tree + categories_.deleteAll(); + categoryIntToItem_.clear(); + + // Clear out the internal filter object + const bool hadFilter = (filter_ != NULL && !filter_->isEmpty()); + delete filter_; + filter_ = NULL; + if (dataStore_) { - } + filter_ = new simData::CategoryFilter(dataStore_); + const simData::CategoryNameManager& nameManager = dataStore_->categoryNameManager(); - /// Invoked when a new category is added - virtual void onAddCategory(int categoryIndex) - { - parent_->addCategoryName_(categoryIndex); - } + // Populate the GUI + std::vector nameInts; + nameManager.allCategoryNameInts(nameInts); - /// Invoked when a new value is added to a category - virtual void onAddValue(int categoryIndex, int valueIndex) - { - parent_->addCategoryValue_(categoryIndex, valueIndex, true); - } + QString settingsKey; + QStringList lockedCategories; + if (settings_) + lockedCategories = settings_->value(settingsKey_, LOCKED_SETTING_METADATA).toStringList(); - /// Invoked when all data is cleared - virtual void onClear() - { - parent_->clear_(); - } + for (auto i = nameInts.begin(); i != nameInts.end(); ++i) + { + // Save the Category item and map it into our quick-search map + CategoryItem* category = new CategoryItem(nameManager, *i); + category->setFont(categoryFont_); + categories_.push_back(category); + categoryIntToItem_[*i] = category; + + // Create an item for "NO VALUE" since it won't be in the list of values we receive + ValueItem* noValueItem = new ValueItem(nameManager, *i, simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME); + category->addChild(noValueItem); + + // Save all the category values + std::vector valueInts; + nameManager.allValueIntsInCategory(*i, valueInts); + for (auto vi = valueInts.begin(); vi != valueInts.end(); ++vi) + { + ValueItem* valueItem = new ValueItem(nameManager, *i, *vi); + category->addChild(valueItem); + } - /// Invoked when all listeners have received onClear() - virtual void doneClearing() - { - // noop + // check settings to determine if newly added categories should be locked + if (settings_) + updateLockedState_(lockedCategories, *category); + } } -private: - CategoryTreeModel* parent_; -}; + // Model reset is done + endResetModel(); -//---------------------------------------------------------------------------- -CategoryTreeItem::CategoryTreeItem(const QString& text, const QString& sortText, int categoryIndex, CategoryTreeItem *parent) - : text_(text), - sortText_(sortText), - categoryIndex_(categoryIndex), - parentItem_(parent), - state_(Qt::Unchecked), - numCheckedChildren_(0) -{ + // Alert listeners if we have a new filter + if (hadFilter && filter_) + emit filterChanged(*filter_); } -CategoryTreeItem::~CategoryTreeItem() +void CategoryTreeModel::setSettings(Settings* settings, const QString& settingsKeyPrefix) { - qDeleteAll(childItems_); -} + settings_ = settings; + settingsKey_ = settingsKeyPrefix + "/" + LOCKED_SETTING; -QString CategoryTreeItem::text() const -{ - return text_; + if (!settings_) + return; + + // check settings to determine if newly added categories should be locked + QStringList lockedCategories = settings_->value(settingsKey_, LOCKED_SETTING_METADATA).toStringList(); + for (int i = 0; i < categories_.size(); ++i) + { + updateLockedState_(lockedCategories, *categories_[i]); + } } -QString CategoryTreeItem::sortText() const +void CategoryTreeModel::clearTree_() { - return sortText_; + beginResetModel(); + categories_.deleteAll(); + categoryIntToItem_.clear(); + // need to manually clear the filter_ since auto update was turned off + filter_->clear(); + endResetModel(); } -int CategoryTreeItem::categoryIndex() const +void CategoryTreeModel::addName_(int nameInt) { - return categoryIndex_; + assert(dataStore_ != NULL); + + // Create the tree item for the category + const auto& nameManager = dataStore_->categoryNameManager(); + CategoryItem* category = new CategoryItem(nameManager, nameInt); + category->setFont(categoryFont_); + // check settings to determine if newly added categories should be locked + if (settings_) + { + QStringList lockedCategories = settings_->value(settingsKey_, LOCKED_SETTING_METADATA).toStringList(); + updateLockedState_(lockedCategories, *category); + } + // Debug mode: Validate that there are no values in that category yet. If this section + // of code fails, then we'll need to add ValueItem entries for the category on creation. +#ifndef NDEBUG + std::vector valuesInCategory; + dataStore_->categoryNameManager().allValueIntsInCategory(nameInt, valuesInCategory); + // Assertion failure means we need to update this code to add the values. + assert(valuesInCategory.empty()); +#endif + + // About to update the GUI by adding a new item at the end + beginInsertRows(QModelIndex(), categories_.size(), categories_.size()); + categories_.push_back(category); + categoryIntToItem_[nameInt] = category; + + // Create an item for "NO VALUE" since it won't be in the list of values we receive + ValueItem* noValueItem = new ValueItem(nameManager, nameInt, simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME); + category->addChild(noValueItem); + + endInsertRows(); } -Qt::CheckState CategoryTreeItem::state() const +CategoryTreeModel::CategoryItem* CategoryTreeModel::findNameTree_(int nameInt) const { - return state_; + auto i = categoryIntToItem_.find(nameInt); + return (i == categoryIntToItem_.end()) ? NULL : i->second; } -void CategoryTreeItem::setState(Qt::CheckState value) +void CategoryTreeModel::updateLockedState_(const QStringList& lockedCategories, CategoryItem& category) { - // don't bother to update if no change - if (state_ == value) + if (!lockedCategories.contains(category.categoryName())) return; - state_ = value; - // let the parent know about our updated state - if (parentItem_ != NULL) - parentItem_->updateNumCheckedChildren_(value); + bool wasChanged = false; + category.setData(true, CategoryTreeModel::ROLE_LOCKED_STATE, *filter_, wasChanged); } -Qt::CheckState CategoryTreeItem::childrenState() const +void CategoryTreeModel::addValue_(int nameInt, int valueInt) { - // This isn't really a good state and there's no good answer if we have no children. Assert - // and the developer can determine if this IS a good state or not, and update appropriately. - assert(!childItems_.empty()); - if (childItems_.empty()) - return Qt::PartiallyChecked; - Qt::CheckState state = childItems_[0]->state(); - for (int k = 1; k < childItems_.count(); ++k) + // Find the parent item + TreeItem* nameItem = findNameTree_(nameInt); + // Means we got a category that we don't know about; shouldn't happen. + assert(nameItem); + if (nameItem == NULL) + return; + + // Create the value item + ValueItem* valueItem = new ValueItem(dataStore_->categoryNameManager(), nameInt, valueInt); + // Value item is unchecked, unless the parent has a regular expression + if (nameItem->isRegExpApplied()) { - if (state != childItems_[k]->state()) - return Qt::PartiallyChecked; + auto* reObject = filter_->getRegExp(nameInt); + if (reObject) + valueItem->setChecked(reObject->match(valueItem->valueString().toStdString())); } - return state; + + // Get the index for the name (parent), and add this new valueItem into the tree + const QModelIndex nameIndex = createIndex(categories_.indexOf(static_cast(nameItem)), 0, nameItem); + beginInsertRows(nameIndex, nameItem->childCount(), nameItem->childCount()); + nameItem->addChild(valueItem); + endInsertRows(); } -void CategoryTreeItem::updateNumCheckedChildren_(Qt::CheckState value) +void CategoryTreeModel::processCategoryCounts(const simQt::CategoryCountResults& results) { - // increment if a child has been checked - if (value == Qt::Checked) + const int numCategories = categories_.size(); + int firstRowChanged = -1; + int lastRowChanged = -1; + const auto& allCats = results.allCategories; + for (int k = 0; k < numCategories; ++k) { - // moving from unchecked to checked state, since one of our children is now checked - if (numCheckedChildren_ == 0) - setState(Qt::Checked); - ++numCheckedChildren_; - } - // decrement down to 0 if a child has been unchecked - else if (numCheckedChildren_ > 0) - { - --numCheckedChildren_; - // moving from checked to unchecked, since none of our children are now checked - if (numCheckedChildren_ == 0) - setState(Qt::Unchecked); + CategoryItem* categoryItem = categories_[k]; + const int nameInt = categoryItem->nameInt(); + + // Might have a category added between when we fired off the call and when it finished + const auto entry = allCats.find(nameInt); + bool haveChange = false; + + // Updates the text for the category and its child values + if (entry == allCats.end()) + haveChange = categoryItem->updateCounts(std::map()); + else + haveChange = categoryItem->updateCounts(entry->second); + + // Record the row for data changed + if (haveChange) + { + if (firstRowChanged == -1) + firstRowChanged = k; + lastRowChanged = k; + } } + + // Emit data changed + if (firstRowChanged != -1) + emit dataChanged(index(firstRowChanged, 0), index(lastRowChanged, 0)); } -void CategoryTreeItem::appendChild(CategoryTreeItem *item) +///////////////////////////////////////////////////////////////////////// + +/** Style options for drawing a toggle switch */ +struct StyleOptionToggleSwitch { - childItems_.append(item); - // Only should updateNumCheckedChildren_() when we're checked. Unchecked does nothing - if (item->state() == Qt::Checked) - updateNumCheckedChildren_(Qt::Checked); -} + /** Rectangle to draw the switch in */ + QRect rect; + /** Vertical space between drawn track and the rect */ + int trackMargin; + /** Font to draw text in */ + QFont font; + + /** State: on (to the right) or off (to the left) */ + bool value; + /** Locked state gives the toggle a disabled look */ + bool locked; + + /** Describes On|Off|Lock styles */ + struct StateStyle { + /** Brush for painting the track */ + QBrush track; + /** Brush for painting the thumb */ + QBrush thumb; + /** Text to draw in the track */ + QString text; + /** Color of text to draw */ + QColor textColor; + }; + + /** Style to use for ON state */ + StateStyle on; + /** Style to use for OFF state */ + StateStyle off; + /** Style to use for LOCK state */ + StateStyle lock; + + /** Initialize to default options */ + StyleOptionToggleSwitch() + : trackMargin(0), + value(false), + locked(false) + { + // Teal colored track and thumb + on.track = QColor(0, 150, 136); + on.thumb = on.track; + on.text = QObject::tr("Exclude"); + on.textColor = Qt::black; + + // Black and grey track and thumb + off.track = Qt::black; + off.thumb = QColor(200, 200, 200); + off.text = QObject::tr("Match"); + off.textColor = Qt::white; + + // Disabled-looking grey track and thumb + lock.track = QColor(100, 100, 100); + lock.thumb = lock.track.color().lighter(); + lock.text = QObject::tr("Locked"); + lock.textColor = Qt::black; + } +}; + +///////////////////////////////////////////////////////////////////////// -void CategoryTreeItem::removeChild(CategoryTreeItem *item) +/** Responsible for internal layout and painting of a Toggle Switch widget */ +class ToggleSwitchPainter { - const bool wasChecked = (item->state() == Qt::Checked); - childItems_.removeOne(item); - delete item; - // Only decrement if this one was previously checked - if (wasChecked) - updateNumCheckedChildren_(Qt::Unchecked); -} +public: + /** Paint the widget using the given options on the painter provided. */ + virtual void paint(const StyleOptionToggleSwitch& option, QPainter* painter) const; + /** Returns a size hint for the toggle switch. Uses option's rectangle height. */ + virtual QSize sizeHint(const StyleOptionToggleSwitch& option) const; -CategoryTreeItem* CategoryTreeItem::child(int row) +private: + /** Stores rectangle zones for sub-elements of switch. */ + struct ChildRects + { + QRect track; + QRect thumb; + QRect text; + }; + + /** Calculates the rectangles for painting for each sub-element of the toggle switch. */ + void calculateRects_(const StyleOptionToggleSwitch& option, ChildRects& rects) const; +}; + +void ToggleSwitchPainter::paint(const StyleOptionToggleSwitch& option, QPainter* painter) const { - return childItems_.value(row); + painter->save(); + + // Adapted from https://stackoverflow.com/questions/14780517 + + // Figure out positions of all subelements + ChildRects r; + calculateRects_(option, r); + + // Priority goes to the locked state style over on/off + const StyleOptionToggleSwitch::StateStyle& valueStyle = (option.locked ? option.lock : (option.value ? option.on : option.off)); + + // Draw the track + painter->setPen(Qt::NoPen); + painter->setBrush(valueStyle.track); + painter->setOpacity(0.45); + painter->setRenderHint(QPainter::Antialiasing, true); + // Newer Qt with newer MSVC renders the rounded rect poorly if the rounding + // pixels argument is half of pixel height or greater; reduce to 0.49 + const double halfHeight = r.track.height() * 0.49; + painter->drawRoundedRect(r.track, halfHeight, halfHeight); + + // Draw the text next + painter->setOpacity(1.0); + painter->setPen(valueStyle.textColor); + painter->setFont(option.font); + painter->drawText(r.text, Qt::AlignHCenter | Qt::AlignVCenter, valueStyle.text); + + // Draw thumb on top of all + painter->setPen(Qt::NoPen); + painter->setBrush(valueStyle.thumb); + painter->drawEllipse(r.thumb); + + painter->restore(); } -int CategoryTreeItem::childCount() const +QSize ToggleSwitchPainter::sizeHint(const StyleOptionToggleSwitch& option) const { - return childItems_.count(); + // Count in the font text for width + int textWidth = 0; + QFontMetrics fontMetrics(option.font); + if (!option.on.text.isEmpty() || !option.off.text.isEmpty()) + { + const int onWidth = fontMetrics.width(option.on.text); + const int offWidth = fontMetrics.width(option.off.text); + const int lockWidth = fontMetrics.width(option.lock.text); + textWidth = qMax(onWidth, offWidth); + textWidth = qMax(lockWidth, textWidth); + } + + // Best width depends on height + int height = option.rect.height(); + if (height == 0) + height = fontMetrics.height(); + + const int desiredWidth = static_cast(1.5 * option.rect.height()) + textWidth; + return QSize(desiredWidth, height); } -CategoryTreeItem* CategoryTreeItem::parent() +void ToggleSwitchPainter::calculateRects_(const StyleOptionToggleSwitch& option, ChildRects& rects) const { - return parentItem_; + // Track is centered about the rectangle + rects.track = QRect(option.rect.adjusted(0, option.trackMargin, 0, -option.trackMargin)); + + // Thumb should be 1 pixel shorter than the track on top and bottom + rects.thumb = QRect(option.rect.adjusted(0, 1, 0, -1)); + rects.thumb.setWidth(rects.thumb.height()); + // Move thumb to the right if on and if category isn't locked + if (option.value && !option.locked) + rects.thumb.translate(rects.track.width() - rects.thumb.height(), 0); + + // Text is inside the rect, excluding the thumb area + rects.text = QRect(option.rect); + if (option.value) + rects.text.setRight(rects.thumb.left()); + else + rects.text.setLeft(rects.thumb.right()); + // Shift the text closer to center (thumb) to avoid being too close to edge + rects.text.translate(option.value ? 1 : -1, 0); } -int CategoryTreeItem::row() const -{ - if (parentItem_) - return parentItem_->childItems_.indexOf(const_cast(this)); +///////////////////////////////////////////////////////////////////////// - return 0; -} +/** Expected tree indentation. Tree takes away parts of delegate for tree painting and we want to undo that. */ +static const int TREE_INDENTATION = 20; -//----------------------------------------------------------------------------------------- +struct CategoryTreeItemDelegate::ChildRects +{ + QRect background; + QRect checkbox; + QRect branch; + QRect text; + QRect excludeToggle; + QRect regExpButton; +}; -CategoryTreeModel::CategoryTreeModel(QObject *parent, simData::DataStore* dataStore, simData::CategoryFilter* categoryFilter) - : QAbstractItemModel(parent), - rootItem_(NULL), - dataStore_(NULL), - categoryFilter_(NULL) +CategoryTreeItemDelegate::CategoryTreeItemDelegate(QObject* parent) + : QStyledItemDelegate(parent) { - // create observers/listeners - listenerPtr_ = simData::CategoryNameManager::ListenerPtr(new CategoryFilterListener(this)); +} - // setting the data store will register our observer and listener - setProviders(dataStore, categoryFilter); +CategoryTreeItemDelegate::~CategoryTreeItemDelegate() +{ } -CategoryTreeModel::~CategoryTreeModel() +void CategoryTreeItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& inOption, const QModelIndex& index) const { - setProviders(NULL, NULL); - delete rootItem_; + // Initialize a new option struct that has data from the QModelIndex + QStyleOptionViewItem opt(inOption); + initStyleOption(&opt, index); + + // Save the painter then draw based on type of node + painter->save(); + if (!index.parent().isValid()) + paintCategory_(painter, opt, index); + else + paintValue_(painter, opt, index); + painter->restore(); } -void CategoryTreeModel::setProviders(simData::DataStore* dataStore, simData::CategoryFilter* categoryFilter) +void CategoryTreeItemDelegate::paintCategory_(QPainter* painter, QStyleOptionViewItem& opt, const QModelIndex& index) const { - if (dataStore == dataStore_) - return; + const QStyle* style = (opt.widget ? opt.widget->style() : qApp->style()); - // Remove the prefs observers on the data store - if (dataStore_) - dataStore_->categoryNameManager().removeListener(listenerPtr_); + // Calculate the rectangles for drawing + ChildRects r; + calculateRects_(opt, index, r); - // Update the pointers - dataStore_ = dataStore; - categoryFilter_ = categoryFilter; + { // Draw a background for the whole row + painter->setBrush(opt.backgroundBrush); + painter->setPen(Qt::NoPen); + painter->drawRect(r.background); + } - // re-add the prefs observers - if (dataStore_) - dataStore->categoryNameManager().addListener(listenerPtr_); + { // Draw the expand/collapse icon on left side + QStyleOptionViewItem branchOpt(opt); + branchOpt.rect = r.branch; + branchOpt.state &= ~QStyle::State_MouseOver; + style->drawPrimitive(QStyle::PE_IndicatorBranch, &branchOpt, painter); + } - // fill the tree model - forceRefresh(); -} + { // Draw the text for the category + opt.rect = r.text; + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter); + } -//nameIndex is a unique index representing the name, managed by the Category Name Manager. -CategoryTreeItem* CategoryTreeModel::findCategoryName_(int nameIndex) -{ - for (int ii = 0; ii < allCategoriesItem_->childCount(); ii++) - { - CategoryTreeItem* child = allCategoriesItem_->child(ii); - if (child->categoryIndex() == nameIndex) - return child; + if (r.excludeToggle.isValid()) + { // Draw the toggle switch for changing EXCLUDE and INCLUDE + StyleOptionToggleSwitch switchOpt; + ToggleSwitchPainter switchPainter; + switchOpt.rect = r.excludeToggle; + switchOpt.locked = index.data(CategoryTreeModel::ROLE_LOCKED_STATE).toBool(); + switchOpt.value = (switchOpt.locked ? false : index.data(CategoryTreeModel::ROLE_EXCLUDE).toBool()); + switchPainter.paint(switchOpt, painter); } - return NULL; + if (r.regExpButton.isValid()) + { // Draw the RegExp text box + QStyleOptionButton buttonOpt; + buttonOpt.rect = r.regExpButton; + buttonOpt.text = tr("RegExp..."); + buttonOpt.state = QStyle::State_Enabled; + if (clickedElement_ == SE_REGEXP_BUTTON && clickedIndex_ == index) + buttonOpt.state |= QStyle::State_Sunken; + else + buttonOpt.state |= QStyle::State_Raised; + style->drawControl(QStyle::CE_PushButton, &buttonOpt, painter); + } } -//valueIndex is a unique index representing the value, managed by the Category Name Manager. -CategoryTreeItem* CategoryTreeModel::findCategoryValue_(CategoryTreeItem* parent, int valueIndex) +void CategoryTreeItemDelegate::paintValue_(QPainter* painter, QStyleOptionViewItem& opt, const QModelIndex& index) const { - if (parent == NULL) - return NULL; + const QStyle* style = (opt.widget ? opt.widget->style() : qApp->style()); + const bool isChecked = (index.data(Qt::CheckStateRole).toInt() == Qt::Checked); + + // Calculate the rectangles for drawing + ChildRects r; + calculateRects_(opt, index, r); + opt.rect = r.text; + + // Draw a checked checkbox on left side of item if the item is checked + if (isChecked) + { + // Move it to left side of widget + QStyleOption checkOpt(opt); + checkOpt.rect = r.checkbox; + // Check the button, then draw + checkOpt.state |= QStyle::State_On; + style->drawPrimitive(QStyle::PE_IndicatorCheckBox, &checkOpt, painter); + + // Checked category values also show up bold + opt.font.setBold(true); + } - for (int ii = 0; ii < parent->childCount(); ii++) + // Category values that are hovered are shown as underlined in link color (blue usually) + if (opt.state.testFlag(QStyle::State_MouseOver) && opt.state.testFlag(QStyle::State_Enabled)) { - CategoryTreeItem* child = parent->child(ii); - if (child->categoryIndex() == valueIndex) - return child; + opt.font.setUnderline(true); + opt.palette.setBrush(QPalette::Text, opt.palette.color(QPalette::Link)); } - return NULL; + // Turn off the check indicator unconditionally, then draw the item + opt.features &= ~QStyleOptionViewItem::HasCheckIndicator; + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter); } -void CategoryTreeModel::addCategoryName_(int nameIndex) +bool CategoryTreeItemDelegate::editorEvent(QEvent* evt, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) { - // must call setProviders first - assert(categoryFilter_ != NULL); - - // Prevent duplication - if (findCategoryName_(nameIndex) != NULL) - return; - - // New category was added to the category name manager. Check to see if the category is present - // in the current filter (it probably isn't) and make sure we have each of those value checked - const simData::CategoryFilter::CategoryCheck& categoryCheck = categoryFilter_->getCategoryFilter(); - simData::CategoryFilter::CategoryCheck::const_iterator iter = categoryCheck.find(nameIndex); - assert(iter != categoryCheck.end()); - if (iter != categoryCheck.end()) - { - QString name = QString::fromStdString(dataStore_->categoryNameManager().nameIntToString(iter->first)); - CategoryTreeItem* catNameItem = new CategoryTreeItem(name, name, iter->first, allCategoriesItem_); - beginInsertRows(createIndex(allCategoriesItem_->row(), 0, allCategoriesItem_), allCategoriesItem_->childCount(), allCategoriesItem_->childCount()); - allCategoriesItem_->appendChild(catNameItem); - endInsertRows(); - for (simData::CategoryFilter::ValuesCheck::const_iterator valIter = iter->second.second.begin(); valIter != iter->second.second.end(); ++valIter) - addCategoryValue_(iter->first, valIter->first, false); - } + if (index.isValid() && !index.parent().isValid()) + return categoryEvent_(evt, model, option, index); + return valueEvent_(evt, model, option, index); } -void CategoryTreeModel::addCategoryValue_(int nameIndex, int valueIndex, bool rebuild) +bool CategoryTreeItemDelegate::categoryEvent_(QEvent* evt, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) { - // Find Category Name in GUI tree - CategoryTreeItem* categoryName = findCategoryName_(nameIndex); - if (categoryName == NULL) - { - // Add Category name to GUI tree - addCategoryName_(nameIndex); - categoryName = findCategoryName_(nameIndex); - } + // Cast may not be valid, depends on evt->type() + const QMouseEvent* me = static_cast(evt); - // Prevent duplicates - if (findCategoryValue_(categoryName, valueIndex)) + switch (evt->type()) { - // The CategoryTreeModel is out of sync - assert(false); - return; - } + case QEvent::MouseButtonPress: + // Only care about left presses. All other presses are ignored. + if (me->button() != Qt::LeftButton) + { + clickedIndex_ = QModelIndex(); + return false; + } + // Ignore event if category is locked + if (index.data(CategoryTreeModel::ROLE_LOCKED_STATE).toBool()) + { + clickedIndex_ = QModelIndex(); + return true; + } - // A new value was added. Give it a valid checked or not checked state as appropriate based on - // the content of the category filter. - const simData::CategoryFilter::CategoryCheck& categoryCheck = categoryFilter_->getCategoryFilter(); - simData::CategoryFilter::CategoryCheck::const_iterator iter = categoryCheck.find(nameIndex); - assert(iter != categoryCheck.end()); - if (iter != categoryCheck.end()) - { - // Found category so look for value - simData::CategoryFilter::ValuesCheck::const_iterator valIter = iter->second.second.find(valueIndex); - assert(valIter != iter->second.second.end()); - if (valIter != iter->second.second.end()) + clickedElement_ = hit_(me->pos(), option, index); + // Eat the branch press and don't do anything on release + if (clickedElement_ == SE_BRANCH) { - QString value = QString::fromStdString(dataStore_->categoryNameManager().valueIntToString(valIter->first)); + clickedIndex_ = QModelIndex(); + emit expandClicked(index); + return true; + } + clickedIndex_ = index; + if (clickedElement_ == SE_REGEXP_BUTTON) + return true; + break; - CategoryTreeItem* catValItem = new CategoryTreeItem(value, valueSortName_(value), valIter->first, categoryName); - catValItem->setState(valIter->second ? Qt::Checked : Qt::Unchecked); - beginInsertRows(createIndex(categoryName->row(), 0, categoryName), categoryName->childCount(), categoryName->childCount()); - categoryName->appendChild(catValItem); - endInsertRows(); + case QEvent::MouseButtonRelease: + { + // Ignore event if category is locked + if (index.data(CategoryTreeModel::ROLE_LOCKED_STATE).toBool()) + { + clickedIndex_ = QModelIndex(); + return true; + } + // Clicking on toggle should save the index to detect release on the toggle + const auto newHit = hit_(me->pos(), option, index); + // Must match button, index, and element clicked + if (me->button() == Qt::LeftButton && clickedIndex_ == index && newHit == clickedElement_) + { + // Toggle button should, well, toggle + if (clickedElement_ == SE_EXCLUDE_TOGGLE) + { + QVariant oldState = index.data(CategoryTreeModel::ROLE_EXCLUDE); + if (index.flags().testFlag(Qt::ItemIsEnabled)) + model->setData(index, !oldState.toBool(), CategoryTreeModel::ROLE_EXCLUDE); + clickedIndex_ = QModelIndex(); + return true; + } + else if (clickedElement_ == SE_REGEXP_BUTTON) + { + // Need to talk to the tree itself to do the input GUI, so pass this off as a signal + emit editRegExpClicked(index); + clickedIndex_ = QModelIndex(); + return true; + } } + clickedIndex_ = QModelIndex(); + break; } -} -void CategoryTreeModel::clear_() -{ - // must call setProviders first - assert(categoryFilter_ != NULL); - buildTree_(); -} + case QEvent::MouseButtonDblClick: + // Ignore event if category is locked + if (index.data(CategoryTreeModel::ROLE_LOCKED_STATE).toBool()) + { + clickedIndex_ = QModelIndex(); + return true; + } -void CategoryTreeModel::forceRefresh() -{ - if (dataStore_ != NULL) - buildTree_(); -} + clickedIndex_ = QModelIndex(); + clickedElement_ = hit_(me->pos(), option, index); + // Ignore double click on the toggle, branch, and RegExp buttons, so that it doesn't cause expand/contract + if (clickedElement_ == SE_EXCLUDE_TOGGLE || clickedElement_ == SE_BRANCH || clickedElement_ == SE_REGEXP_BUTTON) + return true; + break; -int CategoryTreeModel::columnCount(const QModelIndex &parent) const -{ - return 1; + default: // Many potential events not handled + break; + } + + return false; } -QVariant CategoryTreeModel::data(const QModelIndex &index, int role) const +bool CategoryTreeItemDelegate::valueEvent_(QEvent* evt, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) { - if (!index.isValid()) - return QVariant(); - - if ((role == Qt::DisplayRole) && (index.column() == 0)) + if (evt->type() != QEvent::MouseButtonPress && evt->type() != QEvent::MouseButtonRelease) + return false; + // At this stage it's either a press or a release + const QMouseEvent* me = static_cast(evt); + const bool isPress = (evt->type() == QEvent::MouseButtonPress); + const bool isRelease = !isPress; + + // Determine whether we care about the event + bool usefulEvent = true; + if (me->button() != Qt::LeftButton) + usefulEvent = false; + else if (isRelease && clickedIndex_ != index) + usefulEvent = false; + // Should have a check state; if not, that's weird, return out + QVariant checkState = index.data(Qt::CheckStateRole); + if (!checkState.isValid()) + usefulEvent = false; + + // Clear out the model index before returning + if (!usefulEvent) { - CategoryTreeItem *item = static_cast(index.internalPointer()); - return item->text(); + clickedIndex_ = QModelIndex(); + return false; } - if ((role == CategoryTreeModel::SortRole) && (index.column() == 0)) + // If it's a press, save the index for later. Note we don't use clickedElement_ + if (isPress) + clickedIndex_ = index; + else { - CategoryTreeItem *item = static_cast(index.internalPointer()); - return item->sortText(); + // Invert the state and send it as an updated check + Qt::CheckState newState = (checkState.toInt() == Qt::Checked) ? Qt::Unchecked : Qt::Checked; + if (index.flags().testFlag(Qt::ItemIsEnabled)) + model->setData(index, newState, Qt::CheckStateRole); + clickedIndex_ = QModelIndex(); } + return true; +} - if ((role == Qt::CheckStateRole) && (index.column() == 0)) +void CategoryTreeItemDelegate::calculateRects_(const QStyleOptionViewItem& option, const QModelIndex& index, ChildRects& rects) const +{ + rects.background = option.rect; + + const bool isValue = index.isValid() && index.parent().isValid(); + if (isValue) { - CategoryTreeItem *item = static_cast(index.internalPointer()); - return item->state(); + rects.background.setLeft(0); + rects.checkbox = rects.background; + rects.checkbox.setRight(TREE_INDENTATION); + rects.excludeToggle = QRect(); + rects.regExpButton = QRect(); + + // Text takes up everything to the right of the checkbox + rects.text = rects.background.adjusted(TREE_INDENTATION, 0, 0, 0); } - - if ((role == Qt::ToolTipRole) && (index.column() == 0)) + else { - CategoryTreeItem *item = static_cast(index.internalPointer()); - if (item->childCount() > 0) + // Branch is the > or v indicator for expanding + rects.branch = rects.background; + rects.branch.setRight(rects.branch.left() + rects.branch.height()); + + // Calculate the width given the rectangle of height, for the toggle switch + const bool haveRegExp = !index.data(CategoryTreeModel::ROLE_REGEXP_STRING).toString().isEmpty(); + if (haveRegExp) { - return QString(tr("# of Values: %1")).arg(item->childCount()); + rects.excludeToggle = QRect(); + rects.regExpButton = rects.background.adjusted(0, 1, -1, -1); + rects.regExpButton.setLeft(rects.regExpButton.right() - 70); + } + else + { + rects.excludeToggle = rects.background.adjusted(0, 1, -1, -1); + ToggleSwitchPainter switchPainter; + StyleOptionToggleSwitch switchOpt; + switchOpt.rect = rects.excludeToggle; + const QSize toggleSize = switchPainter.sizeHint(switchOpt); + // Set the left side appropriately + rects.excludeToggle.setLeft(rects.excludeToggle.right() - toggleSize.width()); } - } - return QVariant(); + // Text takes up everything to the right of the branch button until the exclude toggle + rects.text = rects.background; + rects.text.setLeft(rects.branch.right()); + if (haveRegExp) + rects.text.setRight(rects.regExpButton.left()); + else + rects.text.setRight(rects.excludeToggle.left()); + } } -int CategoryTreeModel::setState_(CategoryTreeItem* item, Qt::CheckState state) +CategoryTreeItemDelegate::SubElement CategoryTreeItemDelegate::hit_(const QPoint& pos, const QStyleOptionViewItem& option, const QModelIndex& index) const { - int rv = (item->state() == state) ? 0 : 1; - item->setState(state); - for (int ii = 0; ii < item->childCount(); ++ii) - rv += setState_(item->child(ii), state); - return rv; + // Calculate the various rectangles + ChildRects r; + calculateRects_(option, index, r); + + if (r.excludeToggle.isValid() && r.excludeToggle.contains(pos)) + return SE_EXCLUDE_TOGGLE; + if (r.regExpButton.isValid() && r.regExpButton.contains(pos)) + return SE_REGEXP_BUTTON; + if (r.checkbox.isValid() && r.checkbox.contains(pos)) + return SE_CHECKBOX; + if (r.branch.isValid() && r.branch.contains(pos)) + return SE_BRANCH; + if (r.text.isValid() && r.text.contains(pos)) + return SE_TEXT; + // Background encompasses all, so if we're not here we're in NONE + if (r.background.isValid() && r.background.contains(pos)) + return SE_BACKGROUND; + return SE_NONE; } -bool CategoryTreeModel::setData(const QModelIndex& index, const QVariant& value, int role) +bool CategoryTreeItemDelegate::helpEvent(QHelpEvent* evt, QAbstractItemView* view, const QStyleOptionViewItem& option, const QModelIndex& index) { - if ((index.column() == 0) && (role == Qt::CheckStateRole)) + if (evt->type() == QEvent::ToolTip) { - CategoryTreeItem *item = static_cast(index.internalPointer()); - Qt::CheckState state = value.toBool() ? Qt::Checked : Qt::Unchecked; - if (setState_(item, state) > 0) + // Special tooltip for the EXCLUDE filter + const SubElement subElement = hit_(evt->pos(), option, index); + if (subElement == SE_EXCLUDE_TOGGLE) { - emit dataChanged(index, index); - // need to update the category filter - if (item->parent() == rootItem_) - categoryFilter_->updateAll(value.toBool()); - else if (item->parent() == allCategoriesItem_) - categoryFilter_->updateCategoryFilterName(item->categoryIndex(), value.toBool()); - else - categoryFilter_->updateCategoryFilterValue(item->parent()->categoryIndex(), item->categoryIndex(), value.toBool()); - emit(categoryFilterChanged(*categoryFilter_)); + QToolTip::showText(evt->globalPos(), simQt::formatTooltip(tr("Exclude"), + tr("When on, Exclude mode will omit all entities that match your selected values.

When off, the filter will match all entities that have one of your checked category values.

Exclude mode does not show entity counts.")), + view); + return true; + } + else if (subElement == SE_REGEXP_BUTTON) + { + QToolTip::showText(evt->globalPos(), simQt::formatTooltip(tr("Set Regular Expression"), + tr("A regular expression has been set for this category. Use this button to change the category's regular expression.")), + view); + return true; } - return true; } - return QAbstractItemModel::setData(index, value, role); + return QStyledItemDelegate::helpEvent(evt, view, option, index); } -Qt::ItemFlags CategoryTreeModel::flags(const QModelIndex& index) const +///////////////////////////////////////////////////////////////////////// +/** +* Class that listens for entity events in the DataStore, and +* informs the parent when they happen. +*/ +class CategoryFilterWidget::DataStoreListener : public simData::DataStore::Listener { - if (!index.isValid()) - return Qt::NoItemFlags; +public: + explicit DataStoreListener(CategoryFilterWidget& parent) + : parent_(parent) + {}; + + virtual void onAddEntity(simData::DataStore *source, simData::ObjectId newId, simData::ObjectType ot) + { + parent_.countDirty_ = true; + } + virtual void onRemoveEntity(simData::DataStore *source, simData::ObjectId newId, simData::ObjectType ot) + { + parent_.countDirty_ = true; + } + virtual void onCategoryDataChange(simData::DataStore *source, simData::ObjectId changedId, simData::ObjectType ot) + { + parent_.countDirty_ = true; + } + + // Fulfill the interface + virtual void onNameChange(simData::DataStore *source, simData::ObjectId changeId) {} + virtual void onScenarioDelete(simData::DataStore* source) {} + virtual void onPrefsChange(simData::DataStore *source, simData::ObjectId id) {} + virtual void onTimeChange(simData::DataStore *source) {} + virtual void onFlush(simData::DataStore* source, simData::ObjectId id) {} - if (index.column() == 0) - return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable; +private: + CategoryFilterWidget& parent_; +}; - return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +///////////////////////////////////////////////////////////////////////// + +CategoryFilterWidget::CategoryFilterWidget(QWidget* parent) + : QWidget(parent), + activeFiltering_(false), + showEntityCount_(false), + counter_(NULL), + setRegExpAction_(NULL), + countDirty_(true) +{ + setWindowTitle("Category Data Filter"); + setObjectName("CategoryFilterWidget"); + + treeModel_ = new simQt::CategoryTreeModel(this); + proxy_ = new simQt::CategoryProxyModel(this); + proxy_->setSourceModel(treeModel_); + proxy_->setSortRole(simQt::CategoryTreeModel::ROLE_SORT_STRING); + proxy_->sort(0); + + treeView_ = new QTreeView(this); + treeView_->setObjectName("CategoryFilterTree"); + treeView_->setFocusPolicy(Qt::NoFocus); + treeView_->setEditTriggers(QAbstractItemView::NoEditTriggers); + treeView_->setIndentation(0); + treeView_->setAllColumnsShowFocus(true); + treeView_->setHeaderHidden(true); + treeView_->setModel(proxy_); + treeView_->setMouseTracking(true); + + simQt::CategoryTreeItemDelegate* itemDelegate = new simQt::CategoryTreeItemDelegate(this); + treeView_->setItemDelegate(itemDelegate); + + setRegExpAction_ = new QAction(tr("Set Regular Expression..."), this); + connect(setRegExpAction_, SIGNAL(triggered()), this, SLOT(setRegularExpression_())); + clearRegExpAction_ = new QAction(tr("Clear Regular Expression"), this); + connect(clearRegExpAction_, SIGNAL(triggered()), this, SLOT(clearRegularExpression_())); + + QAction* separator1 = new QAction(this); + separator1->setSeparator(true); + + QAction* resetAction = new QAction(tr("Reset"), this); + connect(resetAction, SIGNAL(triggered()), this, SLOT(resetFilter_())); + QAction* separator2 = new QAction(this); + separator2->setSeparator(true); + + toggleLockCategoryAction_ = new QAction(tr("Lock Category"), this); + connect(toggleLockCategoryAction_, SIGNAL(triggered()), this, SLOT(toggleLockCategory_())); + + QAction* separator3 = new QAction(this); + separator3->setSeparator(true); + + QAction* collapseAction = new QAction(tr("Collapse Values"), this); + connect(collapseAction, SIGNAL(triggered()), treeView_, SLOT(collapseAll())); + collapseAction->setIcon(QIcon(":/simQt/images/Collapse.png")); + + QAction* expandAction = new QAction(tr("Expand Values"), this); + connect(expandAction, SIGNAL(triggered()), this, SLOT(expandUnlockedCategories_())); + expandAction->setIcon(QIcon(":/simQt/images/Expand.png")); + + treeView_->setContextMenuPolicy(Qt::CustomContextMenu); + treeView_->addAction(setRegExpAction_); + treeView_->addAction(clearRegExpAction_); + treeView_->addAction(separator1); + treeView_->addAction(resetAction); + treeView_->addAction(separator2); + treeView_->addAction(toggleLockCategoryAction_); + treeView_->addAction(separator3); + treeView_->addAction(collapseAction); + treeView_->addAction(expandAction); + + simQt::SearchLineEdit* search = new simQt::SearchLineEdit(this); + search->setPlaceholderText(tr("Search Category Data")); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setObjectName("CategoryFilterWidgetVBox"); + layout->setMargin(0); + layout->addWidget(search); + layout->addWidget(treeView_); + + connect(treeView_, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu_(QPoint))); + connect(treeModel_, SIGNAL(filterChanged(simData::CategoryFilter)), this, SIGNAL(filterChanged(simData::CategoryFilter))); + connect(treeModel_, SIGNAL(filterEdited(simData::CategoryFilter)), this, SIGNAL(filterEdited(simData::CategoryFilter))); + connect(proxy_, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(expandDueToProxy_(QModelIndex, int, int))); + connect(search, SIGNAL(textChanged(QString)), this, SLOT(expandAfterFilterEdited_(QString))); + connect(search, SIGNAL(textChanged(QString)), proxy_, SLOT(setFilterText(QString))); + connect(itemDelegate, SIGNAL(expandClicked(QModelIndex)), this, SLOT(toggleExpanded_(QModelIndex))); + connect(itemDelegate, SIGNAL(editRegExpClicked(QModelIndex)), this, SLOT(showRegExpEditGui_(QModelIndex))); + + // timer is connected by setShowEntityCount below; it must be constructed before setShowEntityCount + auto recountTimer = new QTimer(this); + recountTimer->setSingleShot(false); + recountTimer->setInterval(3000); + connect(recountTimer, SIGNAL(timeout()), this, SLOT(recountCategories_())); + recountTimer->start(); + + // Entity filtering is on by default + setShowEntityCount(true); + + dsListener_.reset(new CategoryFilterWidget::DataStoreListener(*this)); } -QModelIndex CategoryTreeModel::index(int row, int column, const QModelIndex &parent) const +CategoryFilterWidget::~CategoryFilterWidget() { - if (!hasIndex(row, column, parent)) - return QModelIndex(); + if (categoryFilter().getDataStore()) + categoryFilter().getDataStore()->removeListener(dsListener_); +} - CategoryTreeItem *parentItem; +void CategoryFilterWidget::setDataStore(simData::DataStore* dataStore) +{ + simData::DataStore* prevDataStore = categoryFilter().getDataStore(); + if (prevDataStore == dataStore) + return; - if (!parent.isValid()) - parentItem = rootItem_; - else - parentItem = static_cast(parent.internalPointer()); + if (prevDataStore) + prevDataStore->removeListener(dsListener_); - CategoryTreeItem *childItem = parentItem->child(row); - if (childItem) - return createIndex(row, column, childItem); + treeModel_->setDataStore(dataStore); + counter_->setFilter(categoryFilter()); - return QModelIndex(); + if (dataStore) + dataStore->addListener(dsListener_); } -QString CategoryTreeModel::text(const QModelIndex &index) const +void CategoryFilterWidget::setSettings(Settings* settings, const QString& settingsKeyPrefix) { - if (!index.isValid()) - return 0; - - CategoryTreeItem *childItem = static_cast(index.internalPointer()); - if (childItem == NULL) - return ""; + treeModel_->setSettings(settings, settingsKeyPrefix); +} - return childItem->text(); +const simData::CategoryFilter& CategoryFilterWidget::categoryFilter() const +{ + return treeModel_->categoryFilter(); } -QModelIndex CategoryTreeModel::parent(const QModelIndex &index) const +void CategoryFilterWidget::setFilter(const simData::CategoryFilter& categoryFilter) { - if (!index.isValid()) - return QModelIndex(); + treeModel_->setFilter(categoryFilter); +} - CategoryTreeItem *childItem = static_cast(index.internalPointer()); - if (childItem == NULL) - return QModelIndex(); +void CategoryFilterWidget::processCategoryCounts(const simQt::CategoryCountResults& results) +{ + treeModel_->processCategoryCounts(results); +} - CategoryTreeItem *parentItem = childItem->parent(); +bool CategoryFilterWidget::showEntityCount() const +{ + return showEntityCount_; +} - if (parentItem == NULL) - return QModelIndex(); +void CategoryFilterWidget::setShowEntityCount(bool fl) +{ + if (fl == showEntityCount_) + return; + showEntityCount_ = fl; - if (parentItem == rootItem_) - return QModelIndex(); + // Clear out the old counter + delete counter_; + counter_ = NULL; - return createIndex(parentItem->row(), 0, parentItem); + // Create a new counter and configure it + if (showEntityCount_) + { + counter_ = new simQt::AsyncCategoryCounter(this); + connect(counter_, SIGNAL(resultsReady(simQt::CategoryCountResults)), this, SLOT(processCategoryCounts(simQt::CategoryCountResults))); + connect(treeModel_, SIGNAL(filterChanged(simData::CategoryFilter)), counter_, SLOT(setFilter(simData::CategoryFilter))); + connect(treeModel_, SIGNAL(rowsInserted(QModelIndex, int, int)), counter_, SLOT(asyncCountEntities())); + counter_->setFilter(categoryFilter()); + } + else + { + treeModel_->processCategoryCounts(simQt::CategoryCountResults()); + } } -int CategoryTreeModel::rowCount(const QModelIndex &parent) const +void CategoryFilterWidget::expandAfterFilterEdited_(const QString& filterText) { - if (rootItem_ == NULL) - return 0; - - CategoryTreeItem *parentItem; - if (parent.column() > 0) - return 0; + if (filterText.isEmpty()) + { + // Just removed the last character of a search so collapse all to hide everything + if (activeFiltering_) + treeView_->collapseAll(); - if (!parent.isValid()) - parentItem = rootItem_; + activeFiltering_ = false; + } else - parentItem = static_cast(parent.internalPointer()); + { + // Just started a search so expand all to make everything visible + if (!activeFiltering_) + treeView_->expandAll(); - return parentItem->childCount(); + activeFiltering_ = true; + } } -void CategoryTreeModel::buildTree_() +void CategoryFilterWidget::expandDueToProxy_(const QModelIndex& parentIndex, int to, int from) { - beginResetModel(); - delete rootItem_; - rootItem_ = new CategoryTreeItem("Root", "Root", 0, NULL); - allCategoriesItem_ = new CategoryTreeItem(ALL_CATEGORIES, ALL_CATEGORIES, -1, rootItem_); - rootItem_->appendChild(allCategoriesItem_); + // Only expand when we're actively filtering, because we want + // to see rows that match the active filter as they show up + if (!activeFiltering_) + return; - if (categoryFilter_ != NULL) + bool isCategory = !parentIndex.isValid(); + if (isCategory) { - const simData::CategoryFilter::CategoryCheck& categoryCheck = categoryFilter_->getCategoryFilter(); - for (simData::CategoryFilter::CategoryCheck::const_iterator iter = categoryCheck.begin(); iter != categoryCheck.end(); ++iter) + // The category names are the "to" to "from" and they just showed up, so expand them + for (int ii = to; ii <= from; ++ii) { - QString name = QString::fromStdString(dataStore_->categoryNameManager().nameIntToString(iter->first)); - CategoryTreeItem* catNameItem = new CategoryTreeItem(name, name, iter->first, allCategoriesItem_); - allCategoriesItem_->appendChild(catNameItem); - addAllValues_(iter->second.second, catNameItem, dataStore_->categoryNameManager()); + QModelIndex catIndex = proxy_->index(ii, 0, parentIndex); + treeView_->expand(catIndex); + } + } + else + { + if (activeFiltering_) + { + // Adding a category value; make sure it is visible by expanding its parent + if (!treeView_->isExpanded(parentIndex)) + treeView_->expand(parentIndex); } } - - endResetModel(); } -void CategoryTreeModel::addAllValues_(const simData::CategoryFilter::ValuesCheck& iter, CategoryTreeItem* nameItem, const simData::CategoryNameManager& categoryNameManager) +void CategoryFilterWidget::toggleExpanded_(const QModelIndex& proxyIndex) { - for (simData::CategoryFilter::ValuesCheck::const_iterator valIter = iter.begin(); valIter != iter.end(); ++valIter) - { - QString value = QString::fromStdString(categoryNameManager.valueIntToString(valIter->first)); - CategoryTreeItem* catValItem = new CategoryTreeItem(value, valueSortName_(value), valIter->first, nameItem); - nameItem->appendChild(catValItem); - } + treeView_->setExpanded(proxyIndex, !treeView_->isExpanded(proxyIndex)); } -QString CategoryTreeModel::valueSortName_(const QString& value) const +void CategoryFilterWidget::resetFilter_() { - if (value == "Unlisted Value") - return " "; + // Create a new empty filter using same data store + const simData::CategoryFilter newFilter(treeModel_->categoryFilter().getDataStore()); + treeModel_->setFilter(newFilter); - if (value == "No Value") - return " "; + // Tree would have sent out a changed signal, but not an edited signal (because we are + // doing this programmatically). That's OK, but we need to send out an edited signal. + emit filterEdited(treeModel_->categoryFilter()); +} - return value; +void CategoryFilterWidget::showContextMenu_(const QPoint& point) +{ + QMenu contextMenu(this); + contextMenu.addActions(treeView_->actions()); + + // Mark the RegExp and Lock actions enabled or disabled based on current state + const QModelIndex idx = treeView_->indexAt(point); + const bool emptyRegExp = idx.data(CategoryTreeModel::ROLE_REGEXP_STRING).toString().isEmpty(); + const bool locked = idx.data(CategoryTreeModel::ROLE_LOCKED_STATE).toBool(); + if (locked && !emptyRegExp) + assert(0); // Should not be possible to have a RegExp set on a locked category + setRegExpAction_->setProperty("index", idx); + setRegExpAction_->setEnabled(idx.isValid() && !locked); // RegExp is disabled while locked + // Mark the Clear RegExp action similarly + clearRegExpAction_->setProperty("index", idx); + clearRegExpAction_->setEnabled(idx.isValid() && !emptyRegExp && !locked); // RegExp is disabled while locked + + // Store the index in the Toggle Lock Category action + toggleLockCategoryAction_->setProperty("index", idx); + toggleLockCategoryAction_->setEnabled(idx.isValid() && emptyRegExp); // Locking is disabled while locked + // Update the text based on the current lock state + toggleLockCategoryAction_->setText(locked ? tr("Unlock Category") : tr("Lock Category")); + + // Show the menu + contextMenu.exec(treeView_->mapToGlobal(point)); + + // Clear the index property and disable + setRegExpAction_->setProperty("index", QVariant()); + setRegExpAction_->setEnabled(false); + clearRegExpAction_->setProperty("index", idx); + clearRegExpAction_->setEnabled(false); + toggleLockCategoryAction_->setProperty("index", QVariant()); } -/** - * The passed in categoryFilter may have a subset of the category information. - * If categoryFilter is missing a category name the GUI should show checked for everything - * If categoryFilter is missing a category value the GUI should show the "Unlisted" state - */ -void CategoryTreeModel::setFilter(const simData::CategoryFilter& categoryFilter) +void CategoryFilterWidget::setRegularExpression_() { - // Only update with a different filter - if (&categoryFilter == categoryFilter_ || categoryFilter_ == NULL) + // Make sure we have a sender and can pull out the index. If not, return + QObject* senderObject = sender(); + if (senderObject == NULL) return; + QModelIndex index = senderObject->property("index").toModelIndex(); + if (index.isValid()) + showRegExpEditGui_(index); +} - // Simplify the filter. This will reduce the total number of operations here. - simData::CategoryFilter simplified = categoryFilter; - simplified.simplify(); - - // Do a quick check on the current filter and make sure it doesn't match incoming filter. - // Do it in an artificial scope to prevent temporary filter from lasting long. +void CategoryFilterWidget::showRegExpEditGui_(const QModelIndex& index) +{ + // Grab category name and old regexp, then ask user for new value + const QString oldRegExp = index.data(CategoryTreeModel::ROLE_REGEXP_STRING).toString(); + const QString categoryName = index.data(CategoryTreeModel::ROLE_CATEGORY_NAME).toString(); + + // pop up dialog with a entity filter line edit that supports formatting regexp + QDialog optionsDialog(this); + optionsDialog.setWindowTitle(tr("Set Regular Expression")); + optionsDialog.setWindowFlags(optionsDialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); + + QLayout* layout = new QVBoxLayout(&optionsDialog); + QLabel* label = new QLabel(tr("Set '%1' value regular expression:").arg(categoryName), &optionsDialog); + layout->addWidget(label); + EntityFilterLineEdit* lineEdit = new EntityFilterLineEdit(&optionsDialog); + lineEdit->setRegexOnly(true); + lineEdit->setText(oldRegExp); + lineEdit->setToolTip( + tr("Regular expressions can be applied to categories in a filter. Categories with regular expression filters will match only the values that match the regular expression." + "

This popup changes the regular expression value for the category '%1'." + "

An empty string can be used to clear the regular expression and return to normal matching mode.").arg(categoryName)); + layout->addWidget(lineEdit); + QDialogButtonBox buttons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &optionsDialog); + connect(lineEdit, SIGNAL(isValidChanged(bool)), buttons.button(QDialogButtonBox::Ok), SLOT(setEnabled(bool))); + connect(&buttons, SIGNAL(accepted()), &optionsDialog, SLOT(accept())); + connect(&buttons, SIGNAL(rejected()), &optionsDialog, SLOT(reject())); + layout->addWidget(&buttons); + optionsDialog.setLayout(layout); + if (optionsDialog.exec() == QDialog::Accepted && lineEdit->text() != oldRegExp) { - simData::CategoryFilter localSimplified = *categoryFilter_; - localSimplified.simplify(); - if (localSimplified == simplified) - return; + // index.model() is const because changes to the model might invalidate indices. Since we know this + // and no longer use the index after this call, it is safe to use const_cast here to use setData(). + QAbstractItemModel* model = const_cast(index.model()); + model->setData(index, lineEdit->text(), CategoryTreeModel::ROLE_REGEXP_STRING); } +} + +void CategoryFilterWidget::clearRegularExpression_() +{ + // Make sure we have a sender and can pull out the index. If not, return + QObject* senderObject = sender(); + if (senderObject == NULL) + return; + QModelIndex index = senderObject->property("index").toModelIndex(); + if (!index.isValid()) + return; + // index.model() is const because changes to the model might invalidate indices. Since we know this + // and no longer use the index after this call, it is safe to use const_cast here to use setData(). + QAbstractItemModel* model = const_cast(index.model()); + model->setData(index, QString(""), CategoryTreeModel::ROLE_REGEXP_STRING); +} +void CategoryFilterWidget::toggleLockCategory_() +{ + // Make sure we have a sender and can pull out the index. If not, return + QObject* senderObject = sender(); + if (senderObject == NULL) + return; + QModelIndex index = senderObject->property("index").toModelIndex(); + if (!index.isValid()) + return; - // If the name is in the simplified filter, that means it's got an influence on the filter state. - // If it's absent, then it has no influence; GUI should be checked and all values set true, - // which is the state we're starting with here. Therefore, we only need to update the filter - // state for items that exist in the category filter. - std::vector allNamesVec; - simplified.getNames(allNamesVec); - // Convert this into a set for faster find() - std::set allNamesSet(allNamesVec.begin(), allNamesVec.end()); + const bool locked = index.data(CategoryTreeModel::ROLE_LOCKED_STATE).toBool(); - // First, update the GUI - const int numCategories = allCategoriesItem_->childCount(); - for (int categoryIndex = 0; categoryIndex < numCategories; ++categoryIndex) + if (!locked) { - CategoryTreeItem* categoryItem = allCategoriesItem_->child(categoryIndex); - // Assertion fail means failure in CategoryTreeItem::child() or childCount() - assert(categoryItem); - if (!categoryItem) - continue; - - // Determine if the name is in the simplified filter. If it is, then the item needs - // to be checked. If it's not, then the GUI can be fully checked or fully unchecked, - // since both states are equivalent; don't change anything in that case. - const bool categoryIsInSimplified = (allNamesSet.find(categoryItem->categoryIndex()) != allNamesSet.end()); - if (!categoryIsInSimplified) + // If index is a value, get its category parent + if (index.parent().isValid()) + index = index.parent(); + if (!index.isValid()) { - const Qt::CheckState currentState = categoryItem->childrenState(); - if (currentState == Qt::PartiallyChecked) - setState_(categoryItem, Qt::Checked); - continue; + assert(0); // value index should have a valid parent + return; } - // Item is in the filter. It applies in some way. - categoryItem->setState(Qt::Checked); - - // Get all the checks for this category. This is a simplified listing and we need to - // rely on "Unlisted Value" to tell us entries. - simData::CategoryFilter::ValuesCheck checks; - simplified.getValues(categoryItem->categoryIndex(), checks); - // Determine whether Unlisted Values are checked or unchecked - auto unlistedIter = checks.find(simData::CategoryNameManager::UNLISTED_CATEGORY_VALUE); - // Assertion validates that if "Unlisted Value" is present, that it's set to true (a non-default value) - assert(unlistedIter == checks.end() || unlistedIter->second); - const bool unlistedValues = (unlistedIter != checks.end()); - - // Loop through all children, setting the values correctly - const int numValues = categoryItem->childCount(); - for (int valueIndex = 0; valueIndex < numValues; ++valueIndex) - { - CategoryTreeItem* valueItem = categoryItem->child(valueIndex); - assert(valueItem); // Implies failure in child() or childCount() - - // Find it in the filter - const int valueInt = valueItem->categoryIndex(); - auto simpleIter = checks.find(valueInt); - // Is it present? If so use that value - if (simpleIter != checks.end()) - valueItem->setState(simpleIter->second ? Qt::Checked : Qt::Unchecked); - else - { - // Else, use the unlisted value that we detected, unless it's the NO VALUE special case (always off by default) - if (valueInt == simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME) - valueItem->setState(Qt::Unchecked); - else - valueItem->setState(unlistedValues ? Qt::Checked : Qt::Unchecked); - } - } + // Collapse the category + treeView_->setExpanded(index, false); } - // Next, update the state of the internal filter to match what the GUI shows - bool globalCheck = false; - for (int categoryIndex = 0; categoryIndex < numCategories; ++categoryIndex) - { - CategoryTreeItem* categoryItem = allCategoriesItem_->child(categoryIndex); - if (!categoryItem) - continue; - const int nameInt = categoryItem->categoryIndex(); - categoryFilter_->updateCategoryFilterName(nameInt, categoryItem->state() == Qt::Checked); - - // Keep track of the global flag for check/uncheck on top level - if (categoryItem->state() == Qt::Checked) - globalCheck = true; + // index.model() is const because changes to the model might invalidate indices. Since we know this + // and no longer use the index after this call, it is safe to use const_cast here to use setData(). + QAbstractItemModel* model = const_cast(index.model()); + // Unlock the category + model->setData(index, !locked, CategoryTreeModel::ROLE_LOCKED_STATE); +} - // Set each child value appropriately - const int numValues = categoryItem->childCount(); - for (int valueIndex = 0; valueIndex < numValues; ++valueIndex) - { - // Check the item in the filter if the GUI item is checked - CategoryTreeItem* valueItem = categoryItem->child(valueIndex); - if (valueItem) - categoryFilter_->setValue(nameInt, valueItem->categoryIndex(), valueItem->state() == Qt::Checked); - } +void CategoryFilterWidget::expandUnlockedCategories_() +{ + // Expand each category if it isn't locked + for (int i = 0; i < proxy_->rowCount(); ++i) + { + const QModelIndex& idx = proxy_->index(i, 0); + if (!idx.data(CategoryTreeModel::ROLE_LOCKED_STATE).toBool()) + treeView_->setExpanded(idx, true); } +} - // Synchronize the state on the All Items check - allCategoriesItem_->setState(globalCheck ? Qt::Checked : Qt::Unchecked); - - // Emit dataChanged() to update the GUI - emit dataChanged(createIndex(0, 0), createIndex(allCategoriesItem_->childCount(), 0)); +void CategoryFilterWidget::recountCategories_() +{ + if (countDirty_) + { + if (showEntityCount_ && counter_) + counter_->asyncCountEntities(); + countDirty_ = false; + } } } -#endif /* USE_DEPRECATED_SIMDISSDK_API */ diff --git a/SDK/simQt/CategoryTreeModel.h b/SDK/simQt/CategoryTreeModel.h index 687de7c33..bd8ed9e70 100644 --- a/SDK/simQt/CategoryTreeModel.h +++ b/SDK/simQt/CategoryTreeModel.h @@ -1,170 +1,371 @@ /* -*- mode: c++ -*- */ /**************************************************************************** - ***** ***** - ***** Classification: UNCLASSIFIED ***** - ***** Classified By: ***** - ***** Declassify On: ***** - ***** ***** - **************************************************************************** - * - * - * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. - * EW Modeling & Simulation, Code 5773 - * 4555 Overlook Ave. - * Washington, D.C. 20375-5339 - * - * License for source code at https://simdis.nrl.navy.mil/License.aspx - * - * The U.S. Government retains all rights to use, duplicate, distribute, - * disclose, or release this software. - * - */ -#ifdef USE_DEPRECATED_SIMDISSDK_API - // this is deprecated; use simQt::CategoryTreeModel2 - -#ifndef SIMQT_CATEGORY_TREE_MODEL_H -#define SIMQT_CATEGORY_TREE_MODEL_H +***** ***** +***** Classification: UNCLASSIFIED ***** +***** Classified By: ***** +***** Declassify On: ***** +***** ***** +**************************************************************************** +* +* +* Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. +* EW Modeling & Simulation, Code 5773 +* 4555 Overlook Ave. +* Washington, D.C. 20375-5339 +* +* License for source code at https://simdis.nrl.navy.mil/License.aspx +* +* The U.S. Government retains all rights to use, duplicate, distribute, +* disclose, or release this software. +* +*/ +#ifndef SIMQT_CATEGORYTREEMODEL_H +#define SIMQT_CATEGORYTREEMODEL_H -#include +#include +#include +#include +#include #include - +#include #include "simCore/Common/Common.h" #include "simData/CategoryData/CategoryFilter.h" -#include "simData/CategoryData/CategoryNameManager.h" +#include "simData/ObjectId.h" -namespace simData { class DataStore; } +class QAction; +class QFont; +class QTreeView; +namespace simData { + class CategoryFilter; + class DataStore; +} namespace simQt { -/// represent either one category name or on category value -class SDKQT_EXPORT CategoryTreeItem +class AsyncCategoryCounter; +struct CategoryCountResults; +class Settings; + +/** +* Container class that keeps track of a set of pointers. The container is indexed to +* provide O(lg n) responses to indexOf() while maintaining O(1) on access-by-index. +* The trade-off is a second internal container that maintains a list of indices. +* +* This is a template container. Typename T can be any type that can be deleted. +* +* This class is particularly useful for Abstract Item Models that need to know things like +* the indexOf() for a particular entry. +*/ +template +class SDKQT_EXPORT IndexedPointerContainer { public: - /// constructor - CategoryTreeItem(const QString& text, const QString& sortText, int categoryIndex, CategoryTreeItem *parent = NULL); - virtual ~CategoryTreeItem(); - - /// Returns the category name or category value - QString text() const; - /// For sorting force "Unlisted Value" and "No Value" to the top - QString sortText() const; - /// Returns the index for the category name or category value - int categoryIndex() const; - /// Returns the check box state - Qt::CheckState state() const; - /// Sets the check box state - void setState(Qt::CheckState value); - /// Returns Checked, Unchecked, or PartiallyChecked to indicate whether children are uniformly checked - Qt::CheckState childrenState() const; - - /**@name Tree management routines - *@{ - */ - void appendChild(CategoryTreeItem *item); - void removeChild(CategoryTreeItem *item); - CategoryTreeItem *child(int row); - int childCount() const; - CategoryTreeItem *parent(); - int columnCount() const; - int row() const; - ///@} + IndexedPointerContainer(); + virtual ~IndexedPointerContainer(); -protected: - /// Update the numCheckedChildren_ value based on the provided value, which will increment or decrement the total - void updateNumCheckedChildren_(Qt::CheckState value); - - QString text_; ///< the category name or category value - QString sortText_; ///< If category value is "Unlisted Value" or "No Value" change to " " and " " to force them to the top of the sort - int categoryIndex_; ///< the index for the category name or category value - CategoryTreeItem *parentItem_; ///< parent of the item. Null if top item - Qt::CheckState state_; ///< the check box state - int numCheckedChildren_; ///< keep track of the number of checked children, to manage current check state - QList childItems_; ///< Children of item, if any. If no children, than item is a category value -}; + /** Retrieves the item at the given index. Not range checked. O(1). */ + T* operator[](int index) const; + /** Retrieves the index of the given item. Returns -1 on not-found. O(lg n). */ + int indexOf(const T* ptr) const; + /** Returns the number of items in the container. */ + int size() const; + /** Adds an item into the container. Must be a unique item. */ + void push_back(T* item); + /** Convenience method to delete each item, then clear(). */ + void deleteAll(); +private: + /** Vector of pointers. */ + typename std::vector vec_; + /** Maps pointers to their index in the vector. */ + typename std::map itemToIndex_; +}; -/// model (data representation) for a tree of Entities (Platforms, Beams, Gates, etc.) -class SDKQT_EXPORT CategoryTreeModel : public QAbstractItemModel +/// Used to sort and filter the CategoryTreeModel +class SDKQT_EXPORT CategoryProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - /// constructor - explicit CategoryTreeModel(QObject *parent = NULL, simData::DataStore* dataStore = NULL, simData::CategoryFilter* categoryFilter = NULL); + /// constructor passes parent to QSortFilterProxyModel + explicit CategoryProxyModel(QObject *parent = 0); + virtual ~CategoryProxyModel(); + +public slots: + /// string to filter against + void setFilterText(const QString& filter); + /// Rests the filter by calling invalidateFilter + void resetFilter(); + +protected: + /// filtering function + virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + +private: + /// string to filter against + QString filter_; +}; + +/** Single tier tree model that maintains and allows users to edit a simData::CategoryFilter. */ +class SDKQT_EXPORT CategoryTreeModel : public QAbstractItemModel +{ + Q_OBJECT; +public: + explicit CategoryTreeModel(QObject* parent = NULL); virtual ~CategoryTreeModel(); - /** - * Set providers - * @param dataStore The datastore - * @param categoryFilter The category filter - **/ - void setProviders(simData::DataStore* dataStore, simData::CategoryFilter* categoryFilter); - /** - * Returns the text for the given index - * @param index The index - * @return The text for the given index, will be "" if index is invalid - **/ - QString text(const QModelIndex &index) const; - - /** @copydoc QAbstractItemModel::columnCount() */ - virtual int columnCount(const QModelIndex &parent) const; - /** @copydoc QAbstractItemModel::data() */ - virtual QVariant data(const QModelIndex &index, int role) const; - /** @copydoc QAbstractItemModel::setData() */ - virtual bool setData(const QModelIndex& index, const QVariant& value, int role); - /** @copydoc QAbstractItemModel::flags() */ + /** Changes the data store, updating what categories and values are shown. */ + void setDataStore(simData::DataStore* dataStore); + /** Retrieves the category filter. Only call this if the Data Store has been set. */ + const simData::CategoryFilter& categoryFilter() const; + /** Sets the settings and the key prefix for saving and loading the locked states */ + void setSettings(Settings* settings, const QString& settingsKeyPrefix); + + /** Enumeration of user roles supported by data() */ + enum { + ROLE_SORT_STRING = Qt::UserRole, + ROLE_EXCLUDE, + ROLE_CATEGORY_NAME, + ROLE_REGEXP_STRING, + ROLE_LOCKED_STATE + }; + + // QAbstractItemModel overrides + virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex &child) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; virtual Qt::ItemFlags flags(const QModelIndex& index) const; - /** @copydoc QAbstractItemModel::index() */ - virtual QModelIndex index(int row, int column, const QModelIndex &parent) const; - /** @copydoc QAbstractItemModel::parent() */ - virtual QModelIndex parent(const QModelIndex &index) const; - /** @copydoc QAbstractItemModel::rowCount() */ - virtual int rowCount(const QModelIndex &parent) const; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); /// data role for obtaining names that are remapped to force "Unlisted Value" and "No Value" to the top static const int SortRole = Qt::UserRole + 1; public slots: - /** Updates the contents of the frame */ - virtual void forceRefresh(); - /// Update the filter to the passed in filter - void setFilter(const simData::CategoryFilter& categoryFilter); + /** Changes the model state to match the values in the filter. */ + void setFilter(const simData::CategoryFilter& filter); + /** Given results of a category count, updates the text for each category. */ + void processCategoryCounts(const simQt::CategoryCountResults& results); signals: - /** Emitted whenever the underlying category filter changes */ - void categoryFilterChanged(const simData::CategoryFilter& categoryFilter); + /** The internal filter has changed, possibly from user editing or programmatically. */ + void filterChanged(const simData::CategoryFilter& filter); + /** The internal filter has changed from user editing. */ + void filterEdited(const simData::CategoryFilter& filter); + /** Called when the match/exclude button changes. Only emitted if filterChanged() is not emitted. */ + void excludeEdited(int nameInt = 0, bool excludeMode = true); private: - class CategoryFilterListener; + class CategoryItem; + class ValueItem; + + /** Adds the category name into the tree structure. */ + void addName_(int nameInt); + /** Adds the category value into the tree structure under the given name. */ + void addValue_(int nameInt, int valueInt); + /** Remove all categories and values. */ + void clearTree_(); + /** Retrieve the CategoryItem representing the name provided. */ + CategoryItem* findNameTree_(int nameInt) const; + /** + * Update the locked state of the specified category if its name appears in the lockedCategories list. + * This method should only be called on data that is updating, since it doesn't emit its own signal for a data change + */ + void updateLockedState_(const QStringList& lockedCategories, CategoryItem& category); + + /** Quick-search vector of category tree items */ + simQt::IndexedPointerContainer categories_; + /** Maps category int values to CategoryItem pointers */ + std::map categoryIntToItem_; - /** Helper function to create and add all the category value QTreeWidgetItems to the parent QTreeWidgetItem */ - void addAllValues_(const simData::CategoryFilter::ValuesCheck& iter, CategoryTreeItem* nameItem, const simData::CategoryNameManager& categoryNameManager); - /** Sets the stat for the given item and all its children */ - int setState_(CategoryTreeItem* item, Qt::CheckState state); - /** Updates user interface tree based on passed in value */ - void addCategoryName_(int nameIndex); - /** Updates user interface tree based on passed in value; if rebuild is true then rebuild the category filter */ - void addCategoryValue_(int nameIndex, int valueIndex, bool rebuild); - /** Clear all data */ - void clear_(); - /** Returns the item for the given index; may return NULL */ - CategoryTreeItem* findCategoryName_(int nameIndex); - /** Returns the item for the given parent index; may return NULL */ - CategoryTreeItem* findCategoryValue_(CategoryTreeItem* parent, int valueIndex); - /** Setup the tree */ - void buildTree_(); - /** Convert value into a sortable name */ - QString valueSortName_(const QString& value) const; - - CategoryTreeItem *rootItem_; - CategoryTreeItem *allCategoriesItem_; + /** Data store providing the name manager we depend on */ simData::DataStore* dataStore_; - simData::CategoryFilter* categoryFilter_; - simData::CategoryNameManager::ListenerPtr listenerPtr_; + /** Internal representation of the GUI settings in the form of a simData::CategoryFilter. */ + simData::CategoryFilter* filter_; + + /** Listens to CategoryNameManager to know when new categories and values are added. */ + class CategoryFilterListener; + std::shared_ptr listener_; + + /** Font used for the Category Name tree items */ + QFont* categoryFont_; + + /** Ptr to settings for storing locked states */ + Settings* settings_; + /** Key for accessing the setting */ + QString settingsKey_; }; -} +// Typedef to CategoryTreeModel2 for backwards compatibility +#ifdef USE_DEPRECATED_SIMDISSDK_API +typedef CategoryTreeModel CategoryTreeModel2; +#endif + +/** + * Item delegate that provides custom styling for a QTreeView with a CategoryTreeModel. This + * delegate is required in order to get "Unlisted Value" editing working properly with + * CategoryTreeModel. The Unlisted Value editing is shown as an EXCLUDE flag on the category + * itself, using a toggle switch to draw the on/off state. Clicking on the toggle will change + * the value in the tree model and therefore in the filter. + * + * Because the item delegate does not have direct access to the QTreeView on which it is + * placed, it cannot correctly deal with clicking on expand/collapse icons. Please listen + * for the expandClicked() signal when using this class in order to deal with expanding + * and collapsing trees. + */ +class SDKQT_EXPORT CategoryTreeItemDelegate : public QStyledItemDelegate +{ + Q_OBJECT; +public: + explicit CategoryTreeItemDelegate(QObject* parent = NULL); + virtual ~CategoryTreeItemDelegate(); + + /** Overrides from QStyledItemDelegate */ + virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual bool editorEvent(QEvent* evt, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index); + + /** Overrides from QAbstractItemDelegate */ + virtual bool helpEvent(QHelpEvent* evt, QAbstractItemView* view, const QStyleOptionViewItem& option, const QModelIndex& index); +signals: + /** User clicked on the custom expand button and index needs to be expanded/collapsed. */ + void expandClicked(const QModelIndex& index); + /** User clicked on the custom RegExp edit button and index needs a RegExp assigned. */ + void editRegExpClicked(const QModelIndex& index); + +private: + /** Handles paint() for category name items */ + void paintCategory_(QPainter* painter, QStyleOptionViewItem& option, const QModelIndex& index) const; + /** Handles paint() for value items */ + void paintValue_(QPainter* painter, QStyleOptionViewItem& option, const QModelIndex& index) const; + + /** Handles editorEvent() for category name items */ + bool categoryEvent_(QEvent* evt, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index); + /** Handles editorEvent() for value items. */ + bool valueEvent_(QEvent* evt, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index); + + /** Sub-elements vary depending on the type of index to draw. */ + enum SubElement + { + SE_NONE = 0, + SE_BACKGROUND, + SE_CHECKBOX, + SE_BRANCH, + SE_TEXT, + SE_EXCLUDE_TOGGLE, + SE_REGEXP_BUTTON + }; + /** Contains the rectangles for all sub-elements for an index. */ + struct ChildRects; + /** Calculate the drawn rectangle areas for each sub-element of a given index. */ + void calculateRects_(const QStyleOptionViewItem& option, const QModelIndex& index, ChildRects& rects) const; + /** Determine which sub-element, if any, was hit in a mouse click. See QMouseEvent::pos(). */ + SubElement hit_(const QPoint& pos, const QStyleOptionViewItem& option, const QModelIndex& index) const; + + /** Keeps track of the QModelIndex being clicked. */ + QModelIndex clickedIndex_; + /** Sub-element being clicked */ + SubElement clickedElement_; +}; + +/** + * Widget that includes a QTreeView with a Category Tree Model and a Search Filter + * widget that will display a given category filter. This is an easy-to-use wrapper + * around the CategoryTreeModel class that provides a view widget and search field. + */ +class SDKQT_EXPORT CategoryFilterWidget : public QWidget +{ + Q_OBJECT; + Q_PROPERTY(bool showEntityCount READ showEntityCount WRITE setShowEntityCount); + +public: + explicit CategoryFilterWidget(QWidget* parent = 0); + virtual ~CategoryFilterWidget(); + + /** Sets the data store, updating the category tree based on changes to that data store. */ + void setDataStore(simData::DataStore* dataStore); + /** Retrieves the category filter. Only call this if the Data Store has been set. */ + const simData::CategoryFilter& categoryFilter() const; + /** Sets the settings and the key prefix for saving and loading the locked states */ + void setSettings(Settings* settings, const QString& settingsKeyPrefix); + + /** Returns true if the entity count should be shown next to values. */ + bool showEntityCount() const; + /** Changes whether entity count is shown next to category values. */ + void setShowEntityCount(bool show); + +public slots: + /** Changes the model state to match the values in the filter. */ + void setFilter(const simData::CategoryFilter& filter); + /** Updates the (#) count next to category values with the given category value counts. */ + void processCategoryCounts(const simQt::CategoryCountResults& results); + /** Shows a GUI for editing the regular expression of a given index */ + void showRegExpEditGui_(const QModelIndex& index); + +signals: + /** The internal filter has changed, possibly from user editing or programmatically. */ + void filterChanged(const simData::CategoryFilter& filter); + /** The internal filter has changed from user editing. */ + void filterEdited(const simData::CategoryFilter& filter); + +private slots: + /** Expand the given index from the proxy if filtering */ + void expandDueToProxy_(const QModelIndex& parentIndex, int to, int from); + /** Conditionally expand tree after filter edited. */ + void expandAfterFilterEdited_(const QString& filterText); + + /** Called by delegate to expand an item */ + void toggleExpanded_(const QModelIndex& proxyIndex); + /** Right click occurred on the QTreeView, point relative to QTreeView */ + void showContextMenu_(const QPoint& point); + + /** Reset the active filter, clearing all values */ + void resetFilter_(); + /** Sets the regular expression on the item saved from showContextMenu_ */ + void setRegularExpression_(); + /** Clears the regular expression on the item saved from showContextMenu_ */ + void clearRegularExpression_(); + /** Locks the current category saved from the showContextMenu_ */ + void toggleLockCategory_(); + /** Expands all unlocked categories */ + void expandUnlockedCategories_(); + /** Start a recount of the category values if countDirty_ is true */ + void recountCategories_(); + +private: + class DataStoreListener; + + /** The tree */ + QTreeView* treeView_; + /** Hold the category data */ + simQt::CategoryTreeModel* treeModel_; + /** Provides sorting and filtering */ + simQt::CategoryProxyModel* proxy_; + /** If true the category values are filtered; used to conditionally expand tree. */ + bool activeFiltering_; + /** If true the category values show a (#) count after them. */ + bool showEntityCount_; + /** Counter object that provides values for entity counting. */ + AsyncCategoryCounter* counter_; + /** Action used for setting regular expressions */ + QAction* setRegExpAction_; + /** Action used for clearing regular expressions */ + QAction* clearRegExpAction_; + /** Action used for toggling the lock state of a category */ + QAction* toggleLockCategoryAction_; + /** Listener for datastore entity events */ + std::shared_ptr dsListener_; + /** If true then the category counts need to be redone */ + bool countDirty_; +}; + +// Typedef to CategoryFilterWidget2 for backwards compatibility +#ifdef USE_DEPRECATED_SIMDISSDK_API +typedef CategoryFilterWidget CategoryFilterWidget2; #endif -#endif /* USE_DEPRECATED_SIMDISSDK_API */ +} + +#endif /* SIMQT_CATEGORYTREEMODEL_H */ diff --git a/SDK/simQt/CategoryTreeModel2.cpp b/SDK/simQt/CategoryTreeModel2.cpp deleted file mode 100644 index fe16dc661..000000000 --- a/SDK/simQt/CategoryTreeModel2.cpp +++ /dev/null @@ -1,2369 +0,0 @@ -/* -*- mode: c++ -*- */ -/**************************************************************************** -***** ***** -***** Classification: UNCLASSIFIED ***** -***** Classified By: ***** -***** Declassify On: ***** -***** ***** -**************************************************************************** -* -* -* Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. -* EW Modeling & Simulation, Code 5773 -* 4555 Overlook Ave. -* Washington, D.C. 20375-5339 -* -* License for source code at https://simdis.nrl.navy.mil/License.aspx -* -* The U.S. Government retains all rights to use, duplicate, distribute, -* disclose, or release this software. -* -*/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "simData/CategoryData/CategoryFilter.h" -#include "simData/CategoryData/CategoryNameManager.h" -#include "simData/DataStore.h" -#include "simQt/QtFormatting.h" -#include "simQt/CategoryFilterCounter.h" -#include "simQt/EntityFilterLineEdit.h" -#include "simQt/RegExpImpl.h" -#include "simQt/SearchLineEdit.h" -#include "simQt/Settings.h" -#include "simQt/CategoryTreeModel2.h" - -#ifdef USE_DEPRECATED_SIMDISSDK_API -#include "simQt/CategoryTreeModel.h" -#endif - -namespace simQt { - -/** Lighter than lightGray, matches QPalette::Midlight */ -static const QColor MIDLIGHT_BG_COLOR(227, 227, 227); -/** Breadcrumb's default fill color, used here for background brush on filter items that contribute to filter. */ -static const QColor CONTRIBUTING_BG_COLOR(195, 225, 240); // Light gray with a hint of blue -/** Locked settings keys */ -static const QString LOCKED_SETTING = "LockedCategories"; -/** Locked settings meta data to define it as private */ -static const simQt::Settings::MetaData LOCKED_SETTING_METADATA(Settings::STRING_LIST, "", "", Settings::PRIVATE); - -#ifdef USE_DEPRECATED_SIMDISSDK_API -// Used to differentiate between the old and new CategoryTreeModel in CategoryProxyModel TODO Will be removed with the old model after December 2019 -static const QString ALL_CATEGORIES = "All Categories"; -#endif - - -///////////////////////////////////////////////////////////////////////// - -template -IndexedPointerContainer::IndexedPointerContainer() -{ -} - -template -IndexedPointerContainer::~IndexedPointerContainer() -{ - deleteAll(); -} - -template -T* IndexedPointerContainer::operator[](int index) const -{ - return vec_[index]; -} - -template -int IndexedPointerContainer::indexOf(const T* item) const -{ - // Use a const-cast to help find() to use right signature - const auto i = itemToIndex_.find(const_cast(item)); - return (i == itemToIndex_.end() ? -1 : i->second); -} - -template -int IndexedPointerContainer::size() const -{ - return static_cast(vec_.size()); -} - -template -void IndexedPointerContainer::push_back(T* item) -{ - // Don't add the same item twice - assert(itemToIndex_.find(item) == itemToIndex_.end()); - const int index = size(); - vec_.push_back(item); - itemToIndex_[item] = index; -} - -template -void IndexedPointerContainer::deleteAll() -{ - for (auto i = vec_.begin(); i != vec_.end(); ++i) - delete *i; - vec_.clear(); - itemToIndex_.clear(); -} - -///////////////////////////////////////////////////////////////////////// - -/** -* Base class for an item in the composite pattern of Category Tree Item / Value Tree Item. -* Note that child trees to this class are owned by this class (in the IndexedPointerContainer). -*/ -class TreeItem -{ -public: - TreeItem(); - virtual ~TreeItem(); - - /** Forward from QAbstractItemModel::data() */ - virtual QVariant data(int role) const = 0; - /** Forward from QAbstractItemModel::flags() */ - virtual Qt::ItemFlags flags() const = 0; - /** Returns true if the GUI changed; sets filterChanged if filter edited. */ - virtual bool setData(const QVariant& value, int role, simData::CategoryFilter& filter, bool& filterChanged) = 0; - - /** Retrieves the category name this tree item is associated with */ - virtual QString categoryName() const = 0; - /** Returns the category name integer value for this item or its parent */ - virtual int nameInt() const = 0; - /** Returns true if the UNLISTED VALUE item is checked (i.e. if we are in EXCLUDE mode) */ - virtual bool isUnlistedValueChecked() const = 0; - /** Returns true if the tree item's category is influenced by a regular expression */ - virtual bool isRegExpApplied() const = 0; - - ///@{ Composite Tree Management Methods - TreeItem* parent() const; - int rowInParent() const; - int indexOf(const TreeItem* child) const; - TreeItem* child(int index) const; - int childCount() const; - void addChild(TreeItem* item); - ///@} - -private: - TreeItem* parent_; - simQt::IndexedPointerContainer children_; -}; - -///////////////////////////////////////////////////////////////////////// - -/** Represents a group node in tree, showing a category name and containing children values. */ -class CategoryTreeModel2::CategoryItem : public TreeItem -{ -public: - CategoryItem(const simData::CategoryNameManager& nameManager, int nameInt); - - /** TreeItem Overrides */ - virtual Qt::ItemFlags flags() const; - virtual QVariant data(int role) const; - virtual bool setData(const QVariant& value, int role, simData::CategoryFilter& filter, bool& filterChanged); - virtual QString categoryName() const; - virtual int nameInt() const; - virtual bool isUnlistedValueChecked() const; - virtual bool isRegExpApplied() const; - - /** Recalculates the "contributes to filter" flag, returning true if it changes (like setData()) */ - bool recalcContributionTo(const simData::CategoryFilter& filter); - - /** Changes the font to use. */ - void setFont(QFont* font); - /** Sets the state of the GUI to match the state of the filter. Returns 0 if nothing changed. */ - int updateTo(const simData::CategoryFilter& filter); - - /** Sets the ID counts for each value under this category name tree, returning true if there is a change. */ - bool updateCounts(const std::map& valueToCountMap) const; - -private: - /** Checks and unchecks children based on whether they match the filter, returning true if any checks change. */ - bool setChildChecks_(const simData::RegExpFilter* reFilter); - - /** Changes the filter to match the check state of the Value Item. */ - void updateFilter_(const ValueItem& valueItem, simData::CategoryFilter& filter) const; - /** Change the value item to match the state of the checks structure (filter). Returns 0 on no change. */ - int updateValueItem_(ValueItem& valueItem, const simData::CategoryFilter::ValuesCheck& checks) const; - - /** setData() variant that handles the ROLE_EXCLUDE role */ - bool setExcludeData_(const QVariant& value, simData::CategoryFilter& filter, bool& filterChanged); - /** setData() variant that handles ROLE_REGEXP_STRING role */ - bool setRegExpStringData_(const QVariant& value, simData::CategoryFilter& filter, bool& filterChanged); - - /** String representation of NAME. */ - QString categoryName_; - /** Integer representation of NAME. */ - int nameInt_; - /** Cache the state of the UNLISTED VALUE. When TRUE, we're in EXCLUDE mode */ - bool unlistedValue_; - /** Category's Regular Expression string value */ - QString regExpString_; - /** Set to true if this category contributes to the filter. */ - bool contributesToFilter_; - /** Font to use for FontRole (not owned) */ - QFont* font_; - /** Tracks whether this category item is locked */ - bool locked_; -}; - -///////////////////////////////////////////////////////////////////////// - -/** Represents a leaf node in tree, showing a category value. */ -class CategoryTreeModel2::ValueItem : public TreeItem -{ -public: - ValueItem(const simData::CategoryNameManager& nameManager, int nameInt, int valueInt); - - /** TreeItem Overrides */ - virtual Qt::ItemFlags flags() const; - virtual QVariant data(int role) const; - virtual bool setData(const QVariant& value, int role, simData::CategoryFilter& filter, bool& filterChanged); - virtual QString categoryName() const; - virtual int nameInt() const; - virtual bool isUnlistedValueChecked() const; - virtual bool isRegExpApplied() const; - - /** Returns the value integer for this item */ - int valueInt() const; - /** Returns the value string for this item; for NO_CATEGORY_VALUE_AT_TIME, empty string is returned. */ - QString valueString() const; - - /** - * Changes the GUI state of whether this item is checked. This does not match 1-for-1 - * with the filter state, and does not directly update any CategoryFilter instance. - */ - void setChecked(bool value); - /** Returns true if the GUI state is such that this item is checked. */ - bool isChecked() const; - - /** Sets the number of entities that match this value. Use -1 to reset. */ - void setNumMatches(int numMatches); - /** Returns number entities that match this particular value in the given filter. */ - int numMatches() const; - -private: - /** setData() that handles Qt::CheckStateRole. Returns true if GUI state changes, and sets filterChanged if filter changes. */ - bool setCheckStateData_(const QVariant& value, simData::CategoryFilter& filter, bool& filterChanged); - - int nameInt_; - int valueInt_; - int numMatches_; - Qt::CheckState checked_; - QString valueString_; -}; - -///////////////////////////////////////////////////////////////////////// - -TreeItem::TreeItem() - : parent_(NULL) -{ -} - -TreeItem::~TreeItem() -{ - children_.deleteAll(); -} - -TreeItem* TreeItem::parent() const -{ - return parent_; -} - -int TreeItem::rowInParent() const -{ - if (parent_ == NULL) - { - // Caller is getting an invalid value - assert(0); - return -1; - } - return parent_->indexOf(this); -} - -int TreeItem::indexOf(const TreeItem* child) const -{ - return children_.indexOf(child); -} - -TreeItem* TreeItem::child(int index) const -{ - return children_[index]; -} - -int TreeItem::childCount() const -{ - return children_.size(); -} - -void TreeItem::addChild(TreeItem* item) -{ - // Assertion failure means developer is doing something weird. - assert(item != NULL); - // Assertion failure means that item is inserted more than once. - assert(item->parent() == NULL); - - // Set the parent and save the item in our children vector. - item->parent_ = this; - children_.push_back(item); -} - -///////////////////////////////////////////////////////////////////////// - -CategoryTreeModel2::CategoryItem::CategoryItem(const simData::CategoryNameManager& nameManager, int nameInt) - : categoryName_(QString::fromStdString(nameManager.nameIntToString(nameInt))), - nameInt_(nameInt), - unlistedValue_(false), - contributesToFilter_(false), - font_(NULL), - locked_(false) -{ -} - -bool CategoryTreeModel2::CategoryItem::isUnlistedValueChecked() const -{ - return unlistedValue_; -} - -bool CategoryTreeModel2::CategoryItem::isRegExpApplied() const -{ - return !regExpString_.isEmpty(); -} - -int CategoryTreeModel2::CategoryItem::nameInt() const -{ - return nameInt_; -} - -QString CategoryTreeModel2::CategoryItem::categoryName() const -{ - return categoryName_; -} - -Qt::ItemFlags CategoryTreeModel2::CategoryItem::flags() const -{ - return Qt::ItemIsEnabled; -} - -QVariant CategoryTreeModel2::CategoryItem::data(int role) const -{ - switch (role) - { - case Qt::DisplayRole: - case Qt::EditRole: - case ROLE_SORT_STRING: - case ROLE_CATEGORY_NAME: - return categoryName_; - case ROLE_EXCLUDE: - return unlistedValue_; - case ROLE_REGEXP_STRING: - return regExpString_; - case ROLE_LOCKED_STATE: - return locked_; - case Qt::BackgroundColorRole: - if (contributesToFilter_) - return CONTRIBUTING_BG_COLOR; - return MIDLIGHT_BG_COLOR; - case Qt::FontRole: - if (font_) - return *font_; - break; - default: - break; - } - return QVariant(); -} - -bool CategoryTreeModel2::CategoryItem::setData(const QVariant& value, int role, simData::CategoryFilter& filter, bool& filterChanged) -{ - if (role == ROLE_EXCLUDE) - return setExcludeData_(value, filter, filterChanged); - else if (role == ROLE_REGEXP_STRING) - return setRegExpStringData_(value, filter, filterChanged); - else if (role == ROLE_LOCKED_STATE && locked_ != value.toBool()) - { - locked_ = value.toBool(); - filterChanged = true; - return true; - } - filterChanged = false; - return false; -} - -bool CategoryTreeModel2::CategoryItem::setExcludeData_(const QVariant& value, simData::CategoryFilter& filter, bool& filterChanged) -{ - filterChanged = false; - // If value does not change, or if disabled, then return early - if (value.toBool() == unlistedValue_ || !flags().testFlag(Qt::ItemIsEnabled)) - return false; - - // Update the value - unlistedValue_ = value.toBool(); - - // If the filter does not include our category, then we do nothing RE: filter - auto values = filter.getCategoryFilter(); - if (values.find(nameInt_) == values.end()) - return true; // True, update our GUI -- but note that the filter did not change - - // Remove the whole name from the filter, then build it from scratch from GUI - filterChanged = true; - filter.removeName(nameInt_); - filter.setValue(nameInt_, simData::CategoryNameManager::UNLISTED_CATEGORY_VALUE, unlistedValue_); - const int count = childCount(); - for (int k = 0; k < count; ++k) - updateFilter_(*static_cast(child(k)), filter); - filter.simplify(nameInt_); - - // Update the flag for contributing to the filter - recalcContributionTo(filter); - return true; -} - -bool CategoryTreeModel2::CategoryItem::setRegExpStringData_(const QVariant& value, simData::CategoryFilter& filter, bool& filterChanged) -{ - // Check for easy no-op - filterChanged = false; - if (value.toString() == regExpString_) - return false; - - // Update the value - regExpString_ = value.toString(); - filterChanged = true; - - // Create/set the regular expression - simData::RegExpFilterPtr newRegExpObject; - if (!regExpString_.isEmpty()) - { - // The factory could/should be passed in for maximum flexibility - simQt::RegExpFilterFactoryImpl reFactory; - newRegExpObject = reFactory.createRegExpFilter(regExpString_.toStdString()); - } - - // Set the RegExp, simplify, and update the internal state - filter.setCategoryRegExp(nameInt_, newRegExpObject); - filter.simplify(nameInt_); - recalcContributionTo(filter); - setChildChecks_(newRegExpObject.get()); - return true; -} - -bool CategoryTreeModel2::CategoryItem::recalcContributionTo(const simData::CategoryFilter& filter) -{ - // First check the regular expression. If there's a regexp, then this category definitely contributes - const bool newValue = filter.nameContributesToFilter(nameInt_); - if (newValue == contributesToFilter_) - return false; - contributesToFilter_ = newValue; - return true; -} - -void CategoryTreeModel2::CategoryItem::setFont(QFont* font) -{ - font_ = font; -} - -bool CategoryTreeModel2::CategoryItem::setChildChecks_(const simData::RegExpFilter* reFilter) -{ - bool hasChange = false; - const int count = childCount(); - for (int k = 0; k < count; ++k) - { - // Test the EditRole, which is used because it omits the # count (e.g. "Friendly (1)") - ValueItem* valueItem = static_cast(child(k)); - const bool matches = reFilter != NULL && reFilter->match(valueItem->valueString().toStdString()); - if (matches != valueItem->isChecked()) - { - valueItem->setChecked(matches); - hasChange = true; - } - } - return hasChange; -} - -int CategoryTreeModel2::CategoryItem::updateTo(const simData::CategoryFilter& filter) -{ - // Update the category if it has a RegExp - const QString oldRegExp = regExpString_; - const auto* regExpObject = filter.getRegExp(nameInt_); - regExpString_ = (regExpObject != NULL ? QString::fromStdString(filter.getRegExpPattern(nameInt_)) : ""); - // If the RegExp string is different, we definitely have some sort of change - bool hasChange = (regExpString_ != oldRegExp); - - // Case 1: Regular Expression is not empty. Check and uncheck values as needed - if (!regExpString_.isEmpty()) - { - // Synchronize the checks of the children - if (setChildChecks_(regExpObject)) - hasChange = true; - return hasChange ? 1 : 0; - } - - // No RegExp -- pull out the category checks - simData::CategoryFilter::ValuesCheck checks; - filter.getValues(nameInt_, checks); - - // Case 2: Filter doesn't have this category. Uncheck all children - if (checks.empty()) - { - const int count = childCount(); - for (int k = 0; k < count; ++k) - { - ValueItem* valueItem = static_cast(child(k)); - if (valueItem->isChecked()) - { - valueItem->setChecked(false); - hasChange = true; - } - } - - // Fix filter on/off - if (recalcContributionTo(filter)) - hasChange = true; - return hasChange ? 1 : 0; - } - - // Case 3: We are in the filter, so our unlistedValueBool matters - auto i = checks.find(simData::CategoryNameManager::UNLISTED_CATEGORY_VALUE); - if (i != checks.end()) - { - // Unlisted value present means it must be on - assert(i->second); - } - - // Detect change in Unlisted Value state - const bool newUnlistedValue = (i != checks.end() && i->second); - if (unlistedValue_ != newUnlistedValue) - hasChange = true; - unlistedValue_ = newUnlistedValue; - - // Iterate through children and make sure the state matches - const int count = childCount(); - for (int k = 0; k < count; ++k) - { - if (0 != updateValueItem_(*static_cast(child(k)), checks)) - hasChange = true; - } - - // Update the flag for contributing to the filter - if (recalcContributionTo(filter)) - hasChange = true; - - return hasChange ? 1 : 0; -} - -void CategoryTreeModel2::CategoryItem::updateFilter_(const ValueItem& valueItem, simData::CategoryFilter& filter) const -{ - const bool filterValue = (valueItem.isChecked() != unlistedValue_); - // NO_VALUE is a special case - if (valueItem.valueInt() == simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME) - { - if (filterValue) - filter.setValue(nameInt_, valueItem.valueInt(), true); - } - else - { - if (filterValue != unlistedValue_) - filter.setValue(nameInt_, valueItem.valueInt(), filterValue); - } -} - -int CategoryTreeModel2::CategoryItem::updateValueItem_(ValueItem& valueItem, const simData::CategoryFilter::ValuesCheck& checks) const -{ - // NO VALUE is a special case unfortunately - const auto i = checks.find(valueItem.valueInt()); - bool nextCheckedState = false; - if (valueItem.valueInt() == simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME) - { - // Item is a NO-VALUE item. This does not follow the rules of "unlisted value" - // in CategoryFilter class, so it's a special case, because we DO want to follow - // logical rules for the end user here in this GUI. - const bool showingNoValue = (i != checks.end() && i->second); - // If unlisted value is false, then we show the NO VALUE as checked if its check - // is present and on. If unlisted value is true, then we invert the display - // so that No-Value swaps into No-No-Value, or Has-Value for short. This all - // simplifies into the expression "setChecked(unlisted != showing)". - nextCheckedState = (unlistedValue_ != showingNoValue); - } - else if (unlistedValue_) - { - // "Harder" case. Unlisted Values are checked, so GUI is showing "omit" or "not" - // states. If it's checked, then we're explicitly omitting that value. So the - // only way to omit is if there is an entry in the checks, and it's set false. - nextCheckedState = (i != checks.end() && !i->second); - } - else - { - // "Simple" case. Unlisted Values are unchecked, so we're matching ONLY items - // that are in the filter, that are checked. So to be checked in the GUI, the - // value must have a checkmark - nextCheckedState = (i != checks.end() && i->second); - } - - if (nextCheckedState == valueItem.isChecked()) - return 0; - valueItem.setChecked(nextCheckedState); - return 1; -} - -bool CategoryTreeModel2::CategoryItem::updateCounts(const std::map& valueToCountMap) const -{ - const int numValues = childCount(); - bool haveChange = false; - for (int k = 0; k < numValues; ++k) - { - ValueItem* valueItem = dynamic_cast(child(k)); - // All children should be ValueItems - assert(valueItem); - if (!valueItem) - continue; - - // It's entirely possible (through async methods) that the incoming value count map is not - // up to date. This can occur if a count starts and more categories get added before the - // count finishes, and is common. - auto i = valueToCountMap.find(valueItem->valueInt()); - int nextMatch = -1; - if (i != valueToCountMap.end()) - nextMatch = static_cast(i->second); - - // Set the number of matches and record a change - if (valueItem->numMatches() != nextMatch) - { - valueItem->setNumMatches(nextMatch); - haveChange = true; - } - } - - return haveChange; -} - -///////////////////////////////////////////////////////////////////////// - -CategoryTreeModel2::ValueItem::ValueItem(const simData::CategoryNameManager& nameManager, int nameInt, int valueInt) - : nameInt_(nameInt), - valueInt_(valueInt), - numMatches_(-1), - checked_(Qt::Unchecked), - valueString_(QString::fromStdString(nameManager.valueIntToString(valueInt))) -{ -} - -bool CategoryTreeModel2::ValueItem::isUnlistedValueChecked() const -{ - // Assertion failure means we have orphan value items - assert(parent()); - if (!parent()) - return false; - return parent()->isUnlistedValueChecked(); -} - -bool CategoryTreeModel2::ValueItem::isRegExpApplied() const -{ - // Assertion failure means we have orphan value items - assert(parent()); - if (!parent()) - return false; - return parent()->isRegExpApplied(); -} - -int CategoryTreeModel2::ValueItem::nameInt() const -{ - return nameInt_; -} - -QString CategoryTreeModel2::ValueItem::categoryName() const -{ - // Assertion failure means we have orphan value items - assert(parent()); - if (!parent()) - return ""; - return parent()->data(ROLE_CATEGORY_NAME).toString(); -} - -int CategoryTreeModel2::ValueItem::valueInt() const -{ - return valueInt_; -} - -QString CategoryTreeModel2::ValueItem::valueString() const -{ - // "No Value" should return empty string here, not user-facing string - if (valueInt_ == simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME) - return ""; - return valueString_; -} - -Qt::ItemFlags CategoryTreeModel2::ValueItem::flags() const -{ - if (isRegExpApplied()) - return Qt::NoItemFlags; - return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; -} - -QVariant CategoryTreeModel2::ValueItem::data(int role) const -{ - switch (role) - { - case Qt::DisplayRole: - case Qt::EditRole: - { - QString returnString; - if (!isUnlistedValueChecked()) - returnString = valueString_; - else if (valueInt_ == simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME) - returnString = tr("Has Value"); - else - returnString = tr("Not %1").arg(valueString_); - // Append the numeric count if specified -- only if in include mode, and NOT in exclude mode - if (numMatches_ >= 0 && !isUnlistedValueChecked()) - returnString = tr("%1 (%2)").arg(returnString).arg(numMatches_); - return returnString; - } - - case Qt::CheckStateRole: - return checked_; - - case ROLE_SORT_STRING: - if (valueInt_ == simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME) - return QString(""); - return data(Qt::DisplayRole); - - case ROLE_EXCLUDE: - return isUnlistedValueChecked(); - - case ROLE_CATEGORY_NAME: - return categoryName(); - - case ROLE_REGEXP_STRING: - // Parent node holds the RegExp string - if (parent()) - return parent()->data(ROLE_REGEXP_STRING); - break; - - case ROLE_LOCKED_STATE: - // Parent node holds the lock state - if (parent()) - return parent()->data(ROLE_LOCKED_STATE); - break; - - default: - break; - } - return QVariant(); -} - -bool CategoryTreeModel2::ValueItem::setData(const QVariant& value, int role, simData::CategoryFilter& filter, bool& filterChanged) -{ - // Internally handle check/uncheck value. For ROLE_REGEXP and ROLE_LOCKED_STATE, rely on category parent - if (role == Qt::CheckStateRole) - return setCheckStateData_(value, filter, filterChanged); - else if (role == ROLE_REGEXP_STRING && parent() != NULL) - return parent()->setData(value, role, filter, filterChanged); - else if (role == ROLE_LOCKED_STATE && parent() != NULL) - return parent()->setData(value, role, filter, filterChanged); - filterChanged = false; - return false; -} - -bool CategoryTreeModel2::ValueItem::setCheckStateData_(const QVariant& value, simData::CategoryFilter& filter, bool& filterChanged) -{ - filterChanged = false; - - // If the edit sets us to same state, or disabled, then return early - const Qt::CheckState newChecked = static_cast(value.toInt()); - if (newChecked == checked_ || !flags().testFlag(Qt::ItemIsEnabled)) - return false; - - // Figure out how to translate the GUI state into the filter value - checked_ = newChecked; - const bool unlistedValue = isUnlistedValueChecked(); - const bool checkedBool = (checked_ == Qt::Checked); - const bool filterValue = (unlistedValue != checkedBool); - - // Change the value in the filter. NO VALUE is a special case - if (valueInt_ == simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME) - { - // If the filter value is off, then remove it from the filter; it's always off by default - if (!filterValue) - filter.removeValue(nameInt_, valueInt_); - else - filter.setValue(nameInt_, valueInt_, true); - } - else - { - // Remove items that match unlisted value. Add items that do not. - if (filterValue == unlistedValue) - filter.removeValue(nameInt_, valueInt_); - else - { - // If the filter was previously empty and we're setting a value, we need to - // make sure that the "No Value" check is correctly set in some cases. - if (!filterValue && unlistedValue) - { - simData::CategoryFilter::ValuesCheck checks; - filter.getValues(nameInt_, checks); - if (checks.empty()) - filter.setValue(nameInt_, simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME, true); - } - - filter.setValue(nameInt_, valueInt_, filterValue); - } - } - - // Ensure UNLISTED VALUE is set correctly. - if (unlistedValue) - filter.setValue(nameInt_, simData::CategoryNameManager::UNLISTED_CATEGORY_VALUE, true); - else - filter.removeValue(nameInt_, simData::CategoryNameManager::UNLISTED_CATEGORY_VALUE); - // Make sure the filter is simplified - filter.simplify(nameInt_); - - // Update the parent too, which fixes the GUI for whether it contributes - CategoryItem* parentTree = dynamic_cast(parent()); - parentTree->recalcContributionTo(filter); - - filterChanged = true; - return true; -} - -void CategoryTreeModel2::ValueItem::setChecked(bool value) -{ - checked_ = (value ? Qt::Checked : Qt::Unchecked); -} - -bool CategoryTreeModel2::ValueItem::isChecked() const -{ - return checked_ == Qt::Checked; -} - -void CategoryTreeModel2::ValueItem::setNumMatches(int matches) -{ - numMatches_ = matches; -} - -int CategoryTreeModel2::ValueItem::numMatches() const -{ - return numMatches_; -} - -///////////////////////////////////////////////////////////////////////// - -/// Monitors for category data changes, calling methods in CategoryTreeModel2. -class CategoryTreeModel2::CategoryFilterListener : public simData::CategoryNameManager::Listener -{ -public: - /// Constructor - explicit CategoryFilterListener(CategoryTreeModel2& parent) - : parent_(parent) - { - } - - virtual ~CategoryFilterListener() - { - } - - /// Invoked when a new category is added - virtual void onAddCategory(int categoryIndex) - { - parent_.addName_(categoryIndex); - } - - /// Invoked when a new value is added to a category - virtual void onAddValue(int categoryIndex, int valueIndex) - { - parent_.addValue_(categoryIndex, valueIndex); - } - - /// Invoked when all data is cleared - virtual void onClear() - { - parent_.clearTree_(); - } - - /// Invoked when all listeners have received onClear() - virtual void doneClearing() - { - // noop - } - -private: - CategoryTreeModel2& parent_; -}; - -///////////////////////////////////////////////////////////////////////// - -CategoryProxyModel::CategoryProxyModel(QObject *parent) - : QSortFilterProxyModel(parent), - hasAllCategories_(true) -{ -} - -CategoryProxyModel::~CategoryProxyModel() -{ -} - -void CategoryProxyModel::setSourceModel(QAbstractItemModel* sourceModel) -{ -#ifdef USE_DEPRECATED_SIMDISSDK_API - // simQt::CategoryTreeModel has a top level "All Categories" item. This item affects - // some of the way filtering works. Detect whether we're using a CategoryTreeModel - // and change our internal flag appropriately. Note that another possible choice - // is simQt::CategoryTreeModel2, which does not have an All Categories item. - hasAllCategories_ = (dynamic_cast(sourceModel) != NULL); -#else - hasAllCategories_ = false; -#endif - QSortFilterProxyModel::setSourceModel(sourceModel); -} - -void CategoryProxyModel::resetFilter() -{ - invalidateFilter(); -} - -void CategoryProxyModel::setFilterText(const QString& filter) -{ - if (filter_ == filter) - return; - - filter_ = filter; - invalidateFilter(); -} - -bool CategoryProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const -{ - if (filter_.isEmpty()) - return true; - - // Always accept top level "All Categories" item - if (hasAllCategories_ && !sourceParent.isValid()) - return true; - - const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); - const QString itemText = index.data(Qt::DisplayRole).toString(); - - // include items that pass the filter - if (itemText.contains(filter_, Qt::CaseInsensitive)) - return true; - - // include items whose parent passes the filter, but not if parent is root "All Categories" item - if (sourceParent.isValid()) - { - const QString parentText = sourceParent.data(Qt::DisplayRole).toString(); - -#ifdef USE_DEPRECATED_SIMDISSDK_API - // We only care about matching "All Categories" for the old model type. TODO Will be removed with the old model after December 2019 - if (hasAllCategories_) - { - if (parentText != ALL_CATEGORIES && parentText.contains(filter_, Qt::CaseInsensitive)) - return true; - } - else -#endif - { - if (parentText.contains(filter_, Qt::CaseInsensitive)) - return true; - } - } - - // include items with any children that pass the filter - const int numChildren = sourceModel()->rowCount(index); - for (int ii = 0; ii < numChildren; ++ii) - { - const QModelIndex childIndex = sourceModel()->index(ii, 0, index); - // Assertion failure means rowCount() was wrong - assert(childIndex.isValid()); - const QString childText = childIndex.data(Qt::DisplayRole).toString(); - if (childText.contains(filter_, Qt::CaseInsensitive)) - return true; - } - return false; -} - -///////////////////////////////////////////////////////////////////////// - -CategoryTreeModel2::CategoryTreeModel2(QObject* parent) - : QAbstractItemModel(parent), - dataStore_(NULL), - filter_(new simData::CategoryFilter(NULL)), - categoryFont_(new QFont), - settings_(NULL) -{ - listener_.reset(new CategoryFilterListener(*this)); - - // Increase the point size on the category - categoryFont_->setPointSize(categoryFont_->pointSize() + 4); - categoryFont_->setBold(true); -} - -CategoryTreeModel2::~CategoryTreeModel2() -{ - categories_.deleteAll(); - categoryIntToItem_.clear(); - delete categoryFont_; - categoryFont_ = NULL; - delete filter_; - filter_ = NULL; - if (dataStore_) - dataStore_->categoryNameManager().removeListener(listener_); -} - -QModelIndex CategoryTreeModel2::index(int row, int column, const QModelIndex &parent) const -{ - if (!hasIndex(row, column, parent)) - return QModelIndex(); - // Category items have no parent in the model - if (!parent.isValid()) - return createIndex(row, column, categories_[row]); - // Has a parent: must be a value item - TreeItem* parentItem = static_cast(parent.internalPointer()); - // Item was not made correctly, check index() - assert(parentItem != NULL); - return createIndex(row, column, parentItem->child(row)); -} - -QModelIndex CategoryTreeModel2::parent(const QModelIndex &child) const -{ - if (!child.isValid() || !child.internalPointer()) - return QModelIndex(); - - // Child could be a category (no parent) or a value (category parent) - const TreeItem* childItem = static_cast(child.internalPointer()); - TreeItem* parentItem = childItem->parent(); - if (parentItem == NULL) // child is a category; no parent - return QModelIndex(); - return createIndex(categories_.indexOf(static_cast(parentItem)), 0, parentItem); -} - -int CategoryTreeModel2::rowCount(const QModelIndex &parent) const -{ - if (parent.isValid()) - { - if (parent.column() != 0) - return 0; - TreeItem* parentItem = static_cast(parent.internalPointer()); - return (parentItem == NULL) ? 0 : parentItem->childCount(); - } - return categories_.size(); -} - -int CategoryTreeModel2::columnCount(const QModelIndex &parent) const -{ - return 1; -} - -QVariant CategoryTreeModel2::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || !index.internalPointer()) - return QVariant(); - const TreeItem* treeItem = static_cast(index.internalPointer()); - return treeItem->data(role); -} - -QVariant CategoryTreeModel2::headerData(int section, Qt::Orientation orientation, int role) const -{ - if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole)) - { - if (section == 0) - return tr("Category"); - - // A column was added and this section was not updated - assert(0); - return QVariant(); - } - - // Isn't the bar across the top -- fall back to whatever QAIM does - return QAbstractItemModel::headerData(section, orientation, role); -} - -Qt::ItemFlags CategoryTreeModel2::flags(const QModelIndex& index) const -{ - if (!index.isValid() || !index.internalPointer()) - return Qt::NoItemFlags; - TreeItem* item = static_cast(index.internalPointer()); - return item->flags(); -} - -bool CategoryTreeModel2::setData(const QModelIndex& index, const QVariant& value, int role) -{ - // Ensure we have a valid index with a valid TreeItem pointer - if (!index.isValid() || !index.internalPointer()) - return QAbstractItemModel::setData(index, value, role); - - // NULL filter means the tree should be empty, so we shouldn't get setData()... - TreeItem* item = static_cast(index.internalPointer()); - assert(filter_ && item); - bool wasEdited = false; - const bool rv = item->setData(value, role, *filter_, wasEdited); - - // update locked setting for this category if it is a category item and this is a locked state update - if (settings_ && item->childCount() > 0 && role == ROLE_LOCKED_STATE) - { - QStringList lockedCategories = settings_->value(settingsKey_, LOCKED_SETTING_METADATA).toStringList(); - lockedCategories.removeOne(item->categoryName()); - if (value.toBool()) - lockedCategories.push_back(item->categoryName()); - settings_->setValue(settingsKey_, lockedCategories); - } - - // Logic below needs to change if this assert triggers. Basically, GUI may - // update without the filter updating, but not vice versa. - assert(rv || !wasEdited); - if (rv) - { - // Update the GUI - emit dataChanged(index, index); - - // Alert users who are listening - if (wasEdited) - { - // Parent index, if it exists, is a category and might have updated its color data() - const QModelIndex parentIndex = index.parent(); - if (parentIndex.isValid()) - emit dataChanged(parentIndex, parentIndex); - - emit filterChanged(*filter_); - emit filterEdited(*filter_); - } - else - { - // Should only happen in cases where EXCLUDE got changed, but no filter was edited - assert(!index.parent().isValid()); - emit excludeEdited(item->nameInt(), item->isUnlistedValueChecked()); - } - } - return rv; -} - -void CategoryTreeModel2::setFilter(const simData::CategoryFilter& filter) -{ - // Check the data store; if it's set in filter and different from ours, update - if (filter.getDataStore() && filter.getDataStore() != dataStore_) - setDataStore(filter.getDataStore()); - - // Avoid no-op - simData::CategoryFilter simplified(filter); - simplified.simplify(); - if (filter_ != NULL && simplified == *filter_) - return; - - // Do a two step assignment so that we don't automatically get auto-update - if (filter_ == NULL) - filter_ = new simData::CategoryFilter(filter.getDataStore()); - filter_->assign(simplified, false); - - const int categoriesSize = categories_.size(); - if (categoriesSize == 0) - { - // This means we have a simplified filter that is DIFFERENT from our current - // filter, AND it means we have no items in the GUI. It means we're out of - // sync and something is not right. Check into it. - assert(0); - return; - } - - // Update to the filter, but detect which rows changed so we can simplify dataChanged() - // for performance reasons. This will prevent the display from updating too much. - int firstChangeRow = -1; - int lastChangeRow = -1; - for (int k = 0; k < categoriesSize; ++k) - { - // Detect change and record the row number - if (categories_[k]->updateTo(*filter_) != 0) - { - if (firstChangeRow == -1) - firstChangeRow = k; - lastChangeRow = k; - } - } - // This shouldn't happen because we checked the simplified filters. If this - // assert triggers, then we have a change in filter (detected above) but the - // GUI didn't actually change. Maybe filter compare failed, or updateTo() - // is returning incorrect values. - assert(firstChangeRow != -1 && lastChangeRow != -1); - if (firstChangeRow != -1 && lastChangeRow != -1) - { - emit dataChanged(index(firstChangeRow, 0), index(lastChangeRow, 0)); - } - emit filterChanged(*filter_); -} - -const simData::CategoryFilter& CategoryTreeModel2::categoryFilter() const -{ - // Precondition of this method is that data store was set; filter must be non-NULL - assert(filter_); - return *filter_; -} - -void CategoryTreeModel2::setDataStore(simData::DataStore* dataStore) -{ - if (dataStore_ == dataStore) - return; - - // Update the listeners on name manager as we change it - if (dataStore_ != NULL) - dataStore_->categoryNameManager().removeListener(listener_); - dataStore_ = dataStore; - if (dataStore_ != NULL) - dataStore_->categoryNameManager().addListener(listener_); - - beginResetModel(); - - // Clear out the internal storage on the tree - categories_.deleteAll(); - categoryIntToItem_.clear(); - - // Clear out the internal filter object - const bool hadFilter = (filter_ != NULL && !filter_->isEmpty()); - delete filter_; - filter_ = NULL; - if (dataStore_) - { - filter_ = new simData::CategoryFilter(dataStore_); - const simData::CategoryNameManager& nameManager = dataStore_->categoryNameManager(); - - // Populate the GUI - std::vector nameInts; - nameManager.allCategoryNameInts(nameInts); - - QString settingsKey; - QStringList lockedCategories; - if (settings_) - lockedCategories = settings_->value(settingsKey_, LOCKED_SETTING_METADATA).toStringList(); - - for (auto i = nameInts.begin(); i != nameInts.end(); ++i) - { - // Save the Category item and map it into our quick-search map - CategoryItem* category = new CategoryItem(nameManager, *i); - category->setFont(categoryFont_); - categories_.push_back(category); - categoryIntToItem_[*i] = category; - - // Create an item for "NO VALUE" since it won't be in the list of values we receive - ValueItem* noValueItem = new ValueItem(nameManager, *i, simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME); - category->addChild(noValueItem); - - // Save all the category values - std::vector valueInts; - nameManager.allValueIntsInCategory(*i, valueInts); - for (auto vi = valueInts.begin(); vi != valueInts.end(); ++vi) - { - ValueItem* valueItem = new ValueItem(nameManager, *i, *vi); - category->addChild(valueItem); - } - - // check settings to determine if newly added categories should be locked - if (settings_) - updateLockedState_(lockedCategories, *category); - } - } - - // Model reset is done - endResetModel(); - - // Alert listeners if we have a new filter - if (hadFilter && filter_) - emit filterChanged(*filter_); -} - -void CategoryTreeModel2::setSettings(Settings* settings, const QString& settingsKeyPrefix) -{ - settings_ = settings; - settingsKey_ = settingsKeyPrefix + "/" + LOCKED_SETTING; - - if (!settings_) - return; - - // check settings to determine if newly added categories should be locked - QStringList lockedCategories = settings_->value(settingsKey_, LOCKED_SETTING_METADATA).toStringList(); - for (int i = 0; i < categories_.size(); ++i) - { - updateLockedState_(lockedCategories, *categories_[i]); - } -} - -void CategoryTreeModel2::clearTree_() -{ - beginResetModel(); - categories_.deleteAll(); - categoryIntToItem_.clear(); - // need to manually clear the filter_ since auto update was turned off - filter_->clear(); - endResetModel(); -} - -void CategoryTreeModel2::addName_(int nameInt) -{ - assert(dataStore_ != NULL); - - // Create the tree item for the category - const auto& nameManager = dataStore_->categoryNameManager(); - CategoryItem* category = new CategoryItem(nameManager, nameInt); - category->setFont(categoryFont_); - // check settings to determine if newly added categories should be locked - if (settings_) - { - QStringList lockedCategories = settings_->value(settingsKey_, LOCKED_SETTING_METADATA).toStringList(); - updateLockedState_(lockedCategories, *category); - } - // Debug mode: Validate that there are no values in that category yet. If this section - // of code fails, then we'll need to add ValueItem entries for the category on creation. -#ifndef NDEBUG - std::vector valuesInCategory; - dataStore_->categoryNameManager().allValueIntsInCategory(nameInt, valuesInCategory); - // Assertion failure means we need to update this code to add the values. - assert(valuesInCategory.empty()); -#endif - - // About to update the GUI by adding a new item at the end - beginInsertRows(QModelIndex(), categories_.size(), categories_.size()); - categories_.push_back(category); - categoryIntToItem_[nameInt] = category; - - // Create an item for "NO VALUE" since it won't be in the list of values we receive - ValueItem* noValueItem = new ValueItem(nameManager, nameInt, simData::CategoryNameManager::NO_CATEGORY_VALUE_AT_TIME); - category->addChild(noValueItem); - - endInsertRows(); -} - -CategoryTreeModel2::CategoryItem* CategoryTreeModel2::findNameTree_(int nameInt) const -{ - auto i = categoryIntToItem_.find(nameInt); - return (i == categoryIntToItem_.end()) ? NULL : i->second; -} - -void CategoryTreeModel2::updateLockedState_(const QStringList& lockedCategories, CategoryItem& category) -{ - if (!lockedCategories.contains(category.categoryName())) - return; - bool wasChanged = false; - category.setData(true, CategoryTreeModel2::ROLE_LOCKED_STATE, *filter_, wasChanged); -} - -void CategoryTreeModel2::addValue_(int nameInt, int valueInt) -{ - // Find the parent item - TreeItem* nameItem = findNameTree_(nameInt); - // Means we got a category that we don't know about; shouldn't happen. - assert(nameItem); - if (nameItem == NULL) - return; - - // Create the value item - ValueItem* valueItem = new ValueItem(dataStore_->categoryNameManager(), nameInt, valueInt); - // Value item is unchecked, unless the parent has a regular expression - if (nameItem->isRegExpApplied()) - { - auto* reObject = filter_->getRegExp(nameInt); - if (reObject) - valueItem->setChecked(reObject->match(valueItem->valueString().toStdString())); - } - - // Get the index for the name (parent), and add this new valueItem into the tree - const QModelIndex nameIndex = createIndex(categories_.indexOf(static_cast(nameItem)), 0, nameItem); - beginInsertRows(nameIndex, nameItem->childCount(), nameItem->childCount()); - nameItem->addChild(valueItem); - endInsertRows(); -} - -void CategoryTreeModel2::processCategoryCounts(const simQt::CategoryCountResults& results) -{ - const int numCategories = categories_.size(); - int firstRowChanged = -1; - int lastRowChanged = -1; - const auto& allCats = results.allCategories; - for (int k = 0; k < numCategories; ++k) - { - CategoryItem* categoryItem = categories_[k]; - const int nameInt = categoryItem->nameInt(); - - // Might have a category added between when we fired off the call and when it finished - const auto entry = allCats.find(nameInt); - bool haveChange = false; - - // Updates the text for the category and its child values - if (entry == allCats.end()) - haveChange = categoryItem->updateCounts(std::map()); - else - haveChange = categoryItem->updateCounts(entry->second); - - // Record the row for data changed - if (haveChange) - { - if (firstRowChanged == -1) - firstRowChanged = k; - lastRowChanged = k; - } - } - - // Emit data changed - if (firstRowChanged != -1) - emit dataChanged(index(firstRowChanged, 0), index(lastRowChanged, 0)); -} - -///////////////////////////////////////////////////////////////////////// - -/** Style options for drawing a toggle switch */ -struct StyleOptionToggleSwitch -{ - /** Rectangle to draw the switch in */ - QRect rect; - /** Vertical space between drawn track and the rect */ - int trackMargin; - /** Font to draw text in */ - QFont font; - - /** State: on (to the right) or off (to the left) */ - bool value; - /** Locked state gives the toggle a disabled look */ - bool locked; - - /** Describes On|Off|Lock styles */ - struct StateStyle { - /** Brush for painting the track */ - QBrush track; - /** Brush for painting the thumb */ - QBrush thumb; - /** Text to draw in the track */ - QString text; - /** Color of text to draw */ - QColor textColor; - }; - - /** Style to use for ON state */ - StateStyle on; - /** Style to use for OFF state */ - StateStyle off; - /** Style to use for LOCK state */ - StateStyle lock; - - /** Initialize to default options */ - StyleOptionToggleSwitch() - : trackMargin(0), - value(false), - locked(false) - { - // Teal colored track and thumb - on.track = QColor(0, 150, 136); - on.thumb = on.track; - on.text = QObject::tr("Exclude"); - on.textColor = Qt::black; - - // Black and grey track and thumb - off.track = Qt::black; - off.thumb = QColor(200, 200, 200); - off.text = QObject::tr("Match"); - off.textColor = Qt::white; - - // Disabled-looking grey track and thumb - lock.track = QColor(100, 100, 100); - lock.thumb = lock.track.color().lighter(); - lock.text = QObject::tr("Locked"); - lock.textColor = Qt::black; - } -}; - -///////////////////////////////////////////////////////////////////////// - -/** Responsible for internal layout and painting of a Toggle Switch widget */ -class ToggleSwitchPainter -{ -public: - /** Paint the widget using the given options on the painter provided. */ - virtual void paint(const StyleOptionToggleSwitch& option, QPainter* painter) const; - /** Returns a size hint for the toggle switch. Uses option's rectangle height. */ - virtual QSize sizeHint(const StyleOptionToggleSwitch& option) const; - -private: - /** Stores rectangle zones for sub-elements of switch. */ - struct ChildRects - { - QRect track; - QRect thumb; - QRect text; - }; - - /** Calculates the rectangles for painting for each sub-element of the toggle switch. */ - void calculateRects_(const StyleOptionToggleSwitch& option, ChildRects& rects) const; -}; - -void ToggleSwitchPainter::paint(const StyleOptionToggleSwitch& option, QPainter* painter) const -{ - painter->save(); - - // Adapted from https://stackoverflow.com/questions/14780517 - - // Figure out positions of all subelements - ChildRects r; - calculateRects_(option, r); - - // Priority goes to the locked state style over on/off - const StyleOptionToggleSwitch::StateStyle& valueStyle = (option.locked ? option.lock : (option.value ? option.on : option.off)); - - // Draw the track - painter->setPen(Qt::NoPen); - painter->setBrush(valueStyle.track); - painter->setOpacity(0.45); - painter->setRenderHint(QPainter::Antialiasing, true); - // Newer Qt with newer MSVC renders the rounded rect poorly if the rounding - // pixels argument is half of pixel height or greater; reduce to 0.49 - const double halfHeight = r.track.height() * 0.49; - painter->drawRoundedRect(r.track, halfHeight, halfHeight); - - // Draw the text next - painter->setOpacity(1.0); - painter->setPen(valueStyle.textColor); - painter->setFont(option.font); - painter->drawText(r.text, Qt::AlignHCenter | Qt::AlignVCenter, valueStyle.text); - - // Draw thumb on top of all - painter->setPen(Qt::NoPen); - painter->setBrush(valueStyle.thumb); - painter->drawEllipse(r.thumb); - - painter->restore(); -} - -QSize ToggleSwitchPainter::sizeHint(const StyleOptionToggleSwitch& option) const -{ - // Count in the font text for width - int textWidth = 0; - QFontMetrics fontMetrics(option.font); - if (!option.on.text.isEmpty() || !option.off.text.isEmpty()) - { - const int onWidth = fontMetrics.width(option.on.text); - const int offWidth = fontMetrics.width(option.off.text); - const int lockWidth = fontMetrics.width(option.lock.text); - textWidth = qMax(onWidth, offWidth); - textWidth = qMax(lockWidth, textWidth); - } - - // Best width depends on height - int height = option.rect.height(); - if (height == 0) - height = fontMetrics.height(); - - const int desiredWidth = static_cast(1.5 * option.rect.height()) + textWidth; - return QSize(desiredWidth, height); -} - -void ToggleSwitchPainter::calculateRects_(const StyleOptionToggleSwitch& option, ChildRects& rects) const -{ - // Track is centered about the rectangle - rects.track = QRect(option.rect.adjusted(0, option.trackMargin, 0, -option.trackMargin)); - - // Thumb should be 1 pixel shorter than the track on top and bottom - rects.thumb = QRect(option.rect.adjusted(0, 1, 0, -1)); - rects.thumb.setWidth(rects.thumb.height()); - // Move thumb to the right if on and if category isn't locked - if (option.value && !option.locked) - rects.thumb.translate(rects.track.width() - rects.thumb.height(), 0); - - // Text is inside the rect, excluding the thumb area - rects.text = QRect(option.rect); - if (option.value) - rects.text.setRight(rects.thumb.left()); - else - rects.text.setLeft(rects.thumb.right()); - // Shift the text closer to center (thumb) to avoid being too close to edge - rects.text.translate(option.value ? 1 : -1, 0); -} - -///////////////////////////////////////////////////////////////////////// - -/** Expected tree indentation. Tree takes away parts of delegate for tree painting and we want to undo that. */ -static const int TREE_INDENTATION = 20; - -struct CategoryTreeItemDelegate::ChildRects -{ - QRect background; - QRect checkbox; - QRect branch; - QRect text; - QRect excludeToggle; - QRect regExpButton; -}; - -CategoryTreeItemDelegate::CategoryTreeItemDelegate(QObject* parent) - : QStyledItemDelegate(parent) -{ -} - -CategoryTreeItemDelegate::~CategoryTreeItemDelegate() -{ -} - -void CategoryTreeItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& inOption, const QModelIndex& index) const -{ - // Initialize a new option struct that has data from the QModelIndex - QStyleOptionViewItem opt(inOption); - initStyleOption(&opt, index); - - // Save the painter then draw based on type of node - painter->save(); - if (!index.parent().isValid()) - paintCategory_(painter, opt, index); - else - paintValue_(painter, opt, index); - painter->restore(); -} - -void CategoryTreeItemDelegate::paintCategory_(QPainter* painter, QStyleOptionViewItem& opt, const QModelIndex& index) const -{ - const QStyle* style = (opt.widget ? opt.widget->style() : qApp->style()); - - // Calculate the rectangles for drawing - ChildRects r; - calculateRects_(opt, index, r); - - { // Draw a background for the whole row - painter->setBrush(opt.backgroundBrush); - painter->setPen(Qt::NoPen); - painter->drawRect(r.background); - } - - { // Draw the expand/collapse icon on left side - QStyleOptionViewItem branchOpt(opt); - branchOpt.rect = r.branch; - branchOpt.state &= ~QStyle::State_MouseOver; - style->drawPrimitive(QStyle::PE_IndicatorBranch, &branchOpt, painter); - } - - { // Draw the text for the category - opt.rect = r.text; - style->drawControl(QStyle::CE_ItemViewItem, &opt, painter); - } - - if (r.excludeToggle.isValid()) - { // Draw the toggle switch for changing EXCLUDE and INCLUDE - StyleOptionToggleSwitch switchOpt; - ToggleSwitchPainter switchPainter; - switchOpt.rect = r.excludeToggle; - switchOpt.locked = index.data(CategoryTreeModel2::ROLE_LOCKED_STATE).toBool(); - switchOpt.value = (switchOpt.locked ? false : index.data(CategoryTreeModel2::ROLE_EXCLUDE).toBool()); - switchPainter.paint(switchOpt, painter); - } - - if (r.regExpButton.isValid()) - { // Draw the RegExp text box - QStyleOptionButton buttonOpt; - buttonOpt.rect = r.regExpButton; - buttonOpt.text = tr("RegExp..."); - buttonOpt.state = QStyle::State_Enabled; - if (clickedElement_ == SE_REGEXP_BUTTON && clickedIndex_ == index) - buttonOpt.state |= QStyle::State_Sunken; - else - buttonOpt.state |= QStyle::State_Raised; - style->drawControl(QStyle::CE_PushButton, &buttonOpt, painter); - } -} - -void CategoryTreeItemDelegate::paintValue_(QPainter* painter, QStyleOptionViewItem& opt, const QModelIndex& index) const -{ - const QStyle* style = (opt.widget ? opt.widget->style() : qApp->style()); - const bool isChecked = (index.data(Qt::CheckStateRole).toInt() == Qt::Checked); - - // Calculate the rectangles for drawing - ChildRects r; - calculateRects_(opt, index, r); - opt.rect = r.text; - - // Draw a checked checkbox on left side of item if the item is checked - if (isChecked) - { - // Move it to left side of widget - QStyleOption checkOpt(opt); - checkOpt.rect = r.checkbox; - // Check the button, then draw - checkOpt.state |= QStyle::State_On; - style->drawPrimitive(QStyle::PE_IndicatorCheckBox, &checkOpt, painter); - - // Checked category values also show up bold - opt.font.setBold(true); - } - - // Category values that are hovered are shown as underlined in link color (blue usually) - if (opt.state.testFlag(QStyle::State_MouseOver) && opt.state.testFlag(QStyle::State_Enabled)) - { - opt.font.setUnderline(true); - opt.palette.setBrush(QPalette::Text, opt.palette.color(QPalette::Link)); - } - - // Turn off the check indicator unconditionally, then draw the item - opt.features &= ~QStyleOptionViewItem::HasCheckIndicator; - style->drawControl(QStyle::CE_ItemViewItem, &opt, painter); -} - -bool CategoryTreeItemDelegate::editorEvent(QEvent* evt, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) -{ - if (index.isValid() && !index.parent().isValid()) - return categoryEvent_(evt, model, option, index); - return valueEvent_(evt, model, option, index); -} - -bool CategoryTreeItemDelegate::categoryEvent_(QEvent* evt, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) -{ - // Cast may not be valid, depends on evt->type() - const QMouseEvent* me = static_cast(evt); - - switch (evt->type()) - { - case QEvent::MouseButtonPress: - // Only care about left presses. All other presses are ignored. - if (me->button() != Qt::LeftButton) - { - clickedIndex_ = QModelIndex(); - return false; - } - // Ignore event if category is locked - if (index.data(CategoryTreeModel2::ROLE_LOCKED_STATE).toBool()) - { - clickedIndex_ = QModelIndex(); - return true; - } - - clickedElement_ = hit_(me->pos(), option, index); - // Eat the branch press and don't do anything on release - if (clickedElement_ == SE_BRANCH) - { - clickedIndex_ = QModelIndex(); - emit expandClicked(index); - return true; - } - clickedIndex_ = index; - if (clickedElement_ == SE_REGEXP_BUTTON) - return true; - break; - - case QEvent::MouseButtonRelease: - { - // Ignore event if category is locked - if (index.data(CategoryTreeModel2::ROLE_LOCKED_STATE).toBool()) - { - clickedIndex_ = QModelIndex(); - return true; - } - // Clicking on toggle should save the index to detect release on the toggle - const auto newHit = hit_(me->pos(), option, index); - // Must match button, index, and element clicked - if (me->button() == Qt::LeftButton && clickedIndex_ == index && newHit == clickedElement_) - { - // Toggle button should, well, toggle - if (clickedElement_ == SE_EXCLUDE_TOGGLE) - { - QVariant oldState = index.data(CategoryTreeModel2::ROLE_EXCLUDE); - if (index.flags().testFlag(Qt::ItemIsEnabled)) - model->setData(index, !oldState.toBool(), CategoryTreeModel2::ROLE_EXCLUDE); - clickedIndex_ = QModelIndex(); - return true; - } - else if (clickedElement_ == SE_REGEXP_BUTTON) - { - // Need to talk to the tree itself to do the input GUI, so pass this off as a signal - emit editRegExpClicked(index); - clickedIndex_ = QModelIndex(); - return true; - } - } - clickedIndex_ = QModelIndex(); - break; - } - - case QEvent::MouseButtonDblClick: - // Ignore event if category is locked - if (index.data(CategoryTreeModel2::ROLE_LOCKED_STATE).toBool()) - { - clickedIndex_ = QModelIndex(); - return true; - } - - clickedIndex_ = QModelIndex(); - clickedElement_ = hit_(me->pos(), option, index); - // Ignore double click on the toggle, branch, and RegExp buttons, so that it doesn't cause expand/contract - if (clickedElement_ == SE_EXCLUDE_TOGGLE || clickedElement_ == SE_BRANCH || clickedElement_ == SE_REGEXP_BUTTON) - return true; - break; - - default: // Many potential events not handled - break; - } - - return false; -} - -bool CategoryTreeItemDelegate::valueEvent_(QEvent* evt, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) -{ - if (evt->type() != QEvent::MouseButtonPress && evt->type() != QEvent::MouseButtonRelease) - return false; - // At this stage it's either a press or a release - const QMouseEvent* me = static_cast(evt); - const bool isPress = (evt->type() == QEvent::MouseButtonPress); - const bool isRelease = !isPress; - - // Determine whether we care about the event - bool usefulEvent = true; - if (me->button() != Qt::LeftButton) - usefulEvent = false; - else if (isRelease && clickedIndex_ != index) - usefulEvent = false; - // Should have a check state; if not, that's weird, return out - QVariant checkState = index.data(Qt::CheckStateRole); - if (!checkState.isValid()) - usefulEvent = false; - - // Clear out the model index before returning - if (!usefulEvent) - { - clickedIndex_ = QModelIndex(); - return false; - } - - // If it's a press, save the index for later. Note we don't use clickedElement_ - if (isPress) - clickedIndex_ = index; - else - { - // Invert the state and send it as an updated check - Qt::CheckState newState = (checkState.toInt() == Qt::Checked) ? Qt::Unchecked : Qt::Checked; - if (index.flags().testFlag(Qt::ItemIsEnabled)) - model->setData(index, newState, Qt::CheckStateRole); - clickedIndex_ = QModelIndex(); - } - return true; -} - -void CategoryTreeItemDelegate::calculateRects_(const QStyleOptionViewItem& option, const QModelIndex& index, ChildRects& rects) const -{ - rects.background = option.rect; - - const bool isValue = index.isValid() && index.parent().isValid(); - if (isValue) - { - rects.background.setLeft(0); - rects.checkbox = rects.background; - rects.checkbox.setRight(TREE_INDENTATION); - rects.excludeToggle = QRect(); - rects.regExpButton = QRect(); - - // Text takes up everything to the right of the checkbox - rects.text = rects.background.adjusted(TREE_INDENTATION, 0, 0, 0); - } - else - { - // Branch is the > or v indicator for expanding - rects.branch = rects.background; - rects.branch.setRight(rects.branch.left() + rects.branch.height()); - - // Calculate the width given the rectangle of height, for the toggle switch - const bool haveRegExp = !index.data(CategoryTreeModel2::ROLE_REGEXP_STRING).toString().isEmpty(); - if (haveRegExp) - { - rects.excludeToggle = QRect(); - rects.regExpButton = rects.background.adjusted(0, 1, -1, -1); - rects.regExpButton.setLeft(rects.regExpButton.right() - 70); - } - else - { - rects.excludeToggle = rects.background.adjusted(0, 1, -1, -1); - ToggleSwitchPainter switchPainter; - StyleOptionToggleSwitch switchOpt; - switchOpt.rect = rects.excludeToggle; - const QSize toggleSize = switchPainter.sizeHint(switchOpt); - // Set the left side appropriately - rects.excludeToggle.setLeft(rects.excludeToggle.right() - toggleSize.width()); - } - - // Text takes up everything to the right of the branch button until the exclude toggle - rects.text = rects.background; - rects.text.setLeft(rects.branch.right()); - if (haveRegExp) - rects.text.setRight(rects.regExpButton.left()); - else - rects.text.setRight(rects.excludeToggle.left()); - } -} - -CategoryTreeItemDelegate::SubElement CategoryTreeItemDelegate::hit_(const QPoint& pos, const QStyleOptionViewItem& option, const QModelIndex& index) const -{ - // Calculate the various rectangles - ChildRects r; - calculateRects_(option, index, r); - - if (r.excludeToggle.isValid() && r.excludeToggle.contains(pos)) - return SE_EXCLUDE_TOGGLE; - if (r.regExpButton.isValid() && r.regExpButton.contains(pos)) - return SE_REGEXP_BUTTON; - if (r.checkbox.isValid() && r.checkbox.contains(pos)) - return SE_CHECKBOX; - if (r.branch.isValid() && r.branch.contains(pos)) - return SE_BRANCH; - if (r.text.isValid() && r.text.contains(pos)) - return SE_TEXT; - // Background encompasses all, so if we're not here we're in NONE - if (r.background.isValid() && r.background.contains(pos)) - return SE_BACKGROUND; - return SE_NONE; -} - -bool CategoryTreeItemDelegate::helpEvent(QHelpEvent* evt, QAbstractItemView* view, const QStyleOptionViewItem& option, const QModelIndex& index) -{ - if (evt->type() == QEvent::ToolTip) - { - // Special tooltip for the EXCLUDE filter - const SubElement subElement = hit_(evt->pos(), option, index); - if (subElement == SE_EXCLUDE_TOGGLE) - { - QToolTip::showText(evt->globalPos(), simQt::formatTooltip(tr("Exclude"), - tr("When on, Exclude mode will omit all entities that match your selected values.

When off, the filter will match all entities that have one of your checked category values.

Exclude mode does not show entity counts.")), - view); - return true; - } - else if (subElement == SE_REGEXP_BUTTON) - { - QToolTip::showText(evt->globalPos(), simQt::formatTooltip(tr("Set Regular Expression"), - tr("A regular expression has been set for this category. Use this button to change the category's regular expression.")), - view); - return true; - } - } - return QStyledItemDelegate::helpEvent(evt, view, option, index); -} - -///////////////////////////////////////////////////////////////////////// -/** -* Class that listens for entity events in the DataStore, and -* informs the parent when they happen. -*/ -class CategoryFilterWidget2::DataStoreListener : public simData::DataStore::Listener -{ -public: - explicit DataStoreListener(CategoryFilterWidget2& parent) - : parent_(parent) - {}; - - virtual void onAddEntity(simData::DataStore *source, simData::ObjectId newId, simData::ObjectType ot) - { - parent_.countDirty_ = true; - } - virtual void onRemoveEntity(simData::DataStore *source, simData::ObjectId newId, simData::ObjectType ot) - { - parent_.countDirty_ = true; - } - virtual void onCategoryDataChange(simData::DataStore *source, simData::ObjectId changedId, simData::ObjectType ot) - { - parent_.countDirty_ = true; - } - - // Fulfill the interface - virtual void onNameChange(simData::DataStore *source, simData::ObjectId changeId) {} - virtual void onScenarioDelete(simData::DataStore* source) {} - virtual void onPrefsChange(simData::DataStore *source, simData::ObjectId id) {} - virtual void onTimeChange(simData::DataStore *source) {} - virtual void onFlush(simData::DataStore* source, simData::ObjectId id) {} - -private: - CategoryFilterWidget2& parent_; -}; - -///////////////////////////////////////////////////////////////////////// - -CategoryFilterWidget2::CategoryFilterWidget2(QWidget* parent) - : QWidget(parent), - activeFiltering_(false), - showEntityCount_(false), - counter_(NULL), - setRegExpAction_(NULL), - countDirty_(true) -{ - setWindowTitle("Category Data Filter"); - setObjectName("CategoryFilterWidget2"); - - treeModel_ = new simQt::CategoryTreeModel2(this); - proxy_ = new simQt::CategoryProxyModel(this); - proxy_->setSourceModel(treeModel_); - proxy_->setSortRole(simQt::CategoryTreeModel2::ROLE_SORT_STRING); - proxy_->sort(0); - - treeView_ = new QTreeView(this); - treeView_->setObjectName("CategoryFilterTree"); - treeView_->setFocusPolicy(Qt::NoFocus); - treeView_->setEditTriggers(QAbstractItemView::NoEditTriggers); - treeView_->setIndentation(0); - treeView_->setAllColumnsShowFocus(true); - treeView_->setHeaderHidden(true); - treeView_->setModel(proxy_); - treeView_->setMouseTracking(true); - - simQt::CategoryTreeItemDelegate* itemDelegate = new simQt::CategoryTreeItemDelegate(this); - treeView_->setItemDelegate(itemDelegate); - - setRegExpAction_ = new QAction(tr("Set Regular Expression..."), this); - connect(setRegExpAction_, SIGNAL(triggered()), this, SLOT(setRegularExpression_())); - clearRegExpAction_ = new QAction(tr("Clear Regular Expression"), this); - connect(clearRegExpAction_, SIGNAL(triggered()), this, SLOT(clearRegularExpression_())); - - QAction* separator1 = new QAction(this); - separator1->setSeparator(true); - - QAction* resetAction = new QAction(tr("Reset"), this); - connect(resetAction, SIGNAL(triggered()), this, SLOT(resetFilter_())); - QAction* separator2 = new QAction(this); - separator2->setSeparator(true); - - toggleLockCategoryAction_ = new QAction(tr("Lock Category"), this); - connect(toggleLockCategoryAction_, SIGNAL(triggered()), this, SLOT(toggleLockCategory_())); - - QAction* separator3 = new QAction(this); - separator3->setSeparator(true); - - QAction* collapseAction = new QAction(tr("Collapse Values"), this); - connect(collapseAction, SIGNAL(triggered()), treeView_, SLOT(collapseAll())); - collapseAction->setIcon(QIcon(":/simQt/images/Collapse.png")); - - QAction* expandAction = new QAction(tr("Expand Values"), this); - connect(expandAction, SIGNAL(triggered()), this, SLOT(expandUnlockedCategories_())); - expandAction->setIcon(QIcon(":/simQt/images/Expand.png")); - - treeView_->setContextMenuPolicy(Qt::CustomContextMenu); - treeView_->addAction(setRegExpAction_); - treeView_->addAction(clearRegExpAction_); - treeView_->addAction(separator1); - treeView_->addAction(resetAction); - treeView_->addAction(separator2); - treeView_->addAction(toggleLockCategoryAction_); - treeView_->addAction(separator3); - treeView_->addAction(collapseAction); - treeView_->addAction(expandAction); - - simQt::SearchLineEdit* search = new simQt::SearchLineEdit(this); - search->setPlaceholderText(tr("Search Category Data")); - - QVBoxLayout *layout = new QVBoxLayout(this); - layout->setObjectName("CategoryFilterWidgetVBox"); - layout->setMargin(0); - layout->addWidget(search); - layout->addWidget(treeView_); - - connect(treeView_, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu_(QPoint))); - connect(treeModel_, SIGNAL(filterChanged(simData::CategoryFilter)), this, SIGNAL(filterChanged(simData::CategoryFilter))); - connect(treeModel_, SIGNAL(filterEdited(simData::CategoryFilter)), this, SIGNAL(filterEdited(simData::CategoryFilter))); - connect(proxy_, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(expandDueToProxy_(QModelIndex, int, int))); - connect(search, SIGNAL(textChanged(QString)), this, SLOT(expandAfterFilterEdited_(QString))); - connect(search, SIGNAL(textChanged(QString)), proxy_, SLOT(setFilterText(QString))); - connect(itemDelegate, SIGNAL(expandClicked(QModelIndex)), this, SLOT(toggleExpanded_(QModelIndex))); - connect(itemDelegate, SIGNAL(editRegExpClicked(QModelIndex)), this, SLOT(showRegExpEditGui_(QModelIndex))); - - // timer is connected by setShowEntityCount below; it must be constructed before setShowEntityCount - auto recountTimer = new QTimer(this); - recountTimer->setSingleShot(false); - recountTimer->setInterval(3000); - connect(recountTimer, SIGNAL(timeout()), this, SLOT(recountCategories_())); - recountTimer->start(); - - // Entity filtering is on by default - setShowEntityCount(true); - - dsListener_.reset(new CategoryFilterWidget2::DataStoreListener(*this)); -} - -CategoryFilterWidget2::~CategoryFilterWidget2() -{ - if (categoryFilter().getDataStore()) - categoryFilter().getDataStore()->removeListener(dsListener_); -} - -void CategoryFilterWidget2::setDataStore(simData::DataStore* dataStore) -{ - simData::DataStore* prevDataStore = categoryFilter().getDataStore(); - if (prevDataStore == dataStore) - return; - - if (prevDataStore) - prevDataStore->removeListener(dsListener_); - - treeModel_->setDataStore(dataStore); - counter_->setFilter(categoryFilter()); - - if (dataStore) - dataStore->addListener(dsListener_); -} - -void CategoryFilterWidget2::setSettings(Settings* settings, const QString& settingsKeyPrefix) -{ - treeModel_->setSettings(settings, settingsKeyPrefix); -} - -const simData::CategoryFilter& CategoryFilterWidget2::categoryFilter() const -{ - return treeModel_->categoryFilter(); -} - -void CategoryFilterWidget2::setFilter(const simData::CategoryFilter& categoryFilter) -{ - treeModel_->setFilter(categoryFilter); -} - -void CategoryFilterWidget2::processCategoryCounts(const simQt::CategoryCountResults& results) -{ - treeModel_->processCategoryCounts(results); -} - -bool CategoryFilterWidget2::showEntityCount() const -{ - return showEntityCount_; -} - -void CategoryFilterWidget2::setShowEntityCount(bool fl) -{ - if (fl == showEntityCount_) - return; - showEntityCount_ = fl; - - // Clear out the old counter - delete counter_; - counter_ = NULL; - - // Create a new counter and configure it - if (showEntityCount_) - { - counter_ = new simQt::AsyncCategoryCounter(this); - connect(counter_, SIGNAL(resultsReady(simQt::CategoryCountResults)), this, SLOT(processCategoryCounts(simQt::CategoryCountResults))); - connect(treeModel_, SIGNAL(filterChanged(simData::CategoryFilter)), counter_, SLOT(setFilter(simData::CategoryFilter))); - connect(treeModel_, SIGNAL(rowsInserted(QModelIndex, int, int)), counter_, SLOT(asyncCountEntities())); - counter_->setFilter(categoryFilter()); - } - else - { - treeModel_->processCategoryCounts(simQt::CategoryCountResults()); - } -} - -void CategoryFilterWidget2::expandAfterFilterEdited_(const QString& filterText) -{ - if (filterText.isEmpty()) - { - // Just removed the last character of a search so collapse all to hide everything - if (activeFiltering_) - treeView_->collapseAll(); - - activeFiltering_ = false; - } - else - { - // Just started a search so expand all to make everything visible - if (!activeFiltering_) - treeView_->expandAll(); - - activeFiltering_ = true; - } -} - -void CategoryFilterWidget2::expandDueToProxy_(const QModelIndex& parentIndex, int to, int from) -{ - // Only expand when we're actively filtering, because we want - // to see rows that match the active filter as they show up - if (!activeFiltering_) - return; - - bool isCategory = !parentIndex.isValid(); - if (isCategory) - { - // The category names are the "to" to "from" and they just showed up, so expand them - for (int ii = to; ii <= from; ++ii) - { - QModelIndex catIndex = proxy_->index(ii, 0, parentIndex); - treeView_->expand(catIndex); - } - } - else - { - if (activeFiltering_) - { - // Adding a category value; make sure it is visible by expanding its parent - if (!treeView_->isExpanded(parentIndex)) - treeView_->expand(parentIndex); - } - } -} - -void CategoryFilterWidget2::toggleExpanded_(const QModelIndex& proxyIndex) -{ - treeView_->setExpanded(proxyIndex, !treeView_->isExpanded(proxyIndex)); -} - -void CategoryFilterWidget2::resetFilter_() -{ - // Create a new empty filter using same data store - const simData::CategoryFilter newFilter(treeModel_->categoryFilter().getDataStore()); - treeModel_->setFilter(newFilter); - - // Tree would have sent out a changed signal, but not an edited signal (because we are - // doing this programmatically). That's OK, but we need to send out an edited signal. - emit filterEdited(treeModel_->categoryFilter()); -} - -void CategoryFilterWidget2::showContextMenu_(const QPoint& point) -{ - QMenu contextMenu(this); - contextMenu.addActions(treeView_->actions()); - - // Mark the RegExp and Lock actions enabled or disabled based on current state - const QModelIndex idx = treeView_->indexAt(point); - const bool emptyRegExp = idx.data(CategoryTreeModel2::ROLE_REGEXP_STRING).toString().isEmpty(); - const bool locked = idx.data(CategoryTreeModel2::ROLE_LOCKED_STATE).toBool(); - if (locked && !emptyRegExp) - assert(0); // Should not be possible to have a RegExp set on a locked category - setRegExpAction_->setProperty("index", idx); - setRegExpAction_->setEnabled(idx.isValid() && !locked); // RegExp is disabled while locked - // Mark the Clear RegExp action similarly - clearRegExpAction_->setProperty("index", idx); - clearRegExpAction_->setEnabled(idx.isValid() && !emptyRegExp && !locked); // RegExp is disabled while locked - - // Store the index in the Toggle Lock Category action - toggleLockCategoryAction_->setProperty("index", idx); - toggleLockCategoryAction_->setEnabled(idx.isValid() && emptyRegExp); // Locking is disabled while locked - // Update the text based on the current lock state - toggleLockCategoryAction_->setText(locked ? tr("Unlock Category") : tr("Lock Category")); - - // Show the menu - contextMenu.exec(treeView_->mapToGlobal(point)); - - // Clear the index property and disable - setRegExpAction_->setProperty("index", QVariant()); - setRegExpAction_->setEnabled(false); - clearRegExpAction_->setProperty("index", idx); - clearRegExpAction_->setEnabled(false); - toggleLockCategoryAction_->setProperty("index", QVariant()); -} - -void CategoryFilterWidget2::setRegularExpression_() -{ - // Make sure we have a sender and can pull out the index. If not, return - QObject* senderObject = sender(); - if (senderObject == NULL) - return; - QModelIndex index = senderObject->property("index").toModelIndex(); - if (index.isValid()) - showRegExpEditGui_(index); -} - -void CategoryFilterWidget2::showRegExpEditGui_(const QModelIndex& index) -{ - // Grab category name and old regexp, then ask user for new value - const QString oldRegExp = index.data(CategoryTreeModel2::ROLE_REGEXP_STRING).toString(); - const QString categoryName = index.data(CategoryTreeModel2::ROLE_CATEGORY_NAME).toString(); - - // pop up dialog with a entity filter line edit that supports formatting regexp - QDialog optionsDialog(this); - optionsDialog.setWindowTitle(tr("Set Regular Expression")); - optionsDialog.setWindowFlags(optionsDialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); - - QLayout* layout = new QVBoxLayout(&optionsDialog); - QLabel* label = new QLabel(tr("Set '%1' value regular expression:").arg(categoryName), &optionsDialog); - layout->addWidget(label); - EntityFilterLineEdit* lineEdit = new EntityFilterLineEdit(&optionsDialog); - lineEdit->setRegexOnly(true); - lineEdit->setText(oldRegExp); - lineEdit->setToolTip( - tr("Regular expressions can be applied to categories in a filter. Categories with regular expression filters will match only the values that match the regular expression." - "

This popup changes the regular expression value for the category '%1'." - "

An empty string can be used to clear the regular expression and return to normal matching mode.").arg(categoryName)); - layout->addWidget(lineEdit); - QDialogButtonBox buttons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &optionsDialog); - connect(lineEdit, SIGNAL(isValidChanged(bool)), buttons.button(QDialogButtonBox::Ok), SLOT(setEnabled(bool))); - connect(&buttons, SIGNAL(accepted()), &optionsDialog, SLOT(accept())); - connect(&buttons, SIGNAL(rejected()), &optionsDialog, SLOT(reject())); - layout->addWidget(&buttons); - optionsDialog.setLayout(layout); - if (optionsDialog.exec() == QDialog::Accepted && lineEdit->text() != oldRegExp) - { - // index.model() is const because changes to the model might invalidate indices. Since we know this - // and no longer use the index after this call, it is safe to use const_cast here to use setData(). - QAbstractItemModel* model = const_cast(index.model()); - model->setData(index, lineEdit->text(), CategoryTreeModel2::ROLE_REGEXP_STRING); - } -} - -void CategoryFilterWidget2::clearRegularExpression_() -{ - // Make sure we have a sender and can pull out the index. If not, return - QObject* senderObject = sender(); - if (senderObject == NULL) - return; - QModelIndex index = senderObject->property("index").toModelIndex(); - if (!index.isValid()) - return; - // index.model() is const because changes to the model might invalidate indices. Since we know this - // and no longer use the index after this call, it is safe to use const_cast here to use setData(). - QAbstractItemModel* model = const_cast(index.model()); - model->setData(index, QString(""), CategoryTreeModel2::ROLE_REGEXP_STRING); -} - -void CategoryFilterWidget2::toggleLockCategory_() -{ - // Make sure we have a sender and can pull out the index. If not, return - QObject* senderObject = sender(); - if (senderObject == NULL) - return; - QModelIndex index = senderObject->property("index").toModelIndex(); - if (!index.isValid()) - return; - - const bool locked = index.data(CategoryTreeModel2::ROLE_LOCKED_STATE).toBool(); - - if (!locked) - { - // If index is a value, get its category parent - if (index.parent().isValid()) - index = index.parent(); - if (!index.isValid()) - { - assert(0); // value index should have a valid parent - return; - } - - // Collapse the category - treeView_->setExpanded(index, false); - } - - // index.model() is const because changes to the model might invalidate indices. Since we know this - // and no longer use the index after this call, it is safe to use const_cast here to use setData(). - QAbstractItemModel* model = const_cast(index.model()); - // Unlock the category - model->setData(index, !locked, CategoryTreeModel2::ROLE_LOCKED_STATE); -} - -void CategoryFilterWidget2::expandUnlockedCategories_() -{ - // Expand each category if it isn't locked - for (int i = 0; i < proxy_->rowCount(); ++i) - { - const QModelIndex& idx = proxy_->index(i, 0); - if (!idx.data(CategoryTreeModel2::ROLE_LOCKED_STATE).toBool()) - treeView_->setExpanded(idx, true); - } -} - -void CategoryFilterWidget2::recountCategories_() -{ - if (countDirty_) - { - if (showEntityCount_ && counter_) - counter_->asyncCountEntities(); - countDirty_ = false; - } -} - -} diff --git a/SDK/simQt/CategoryTreeModel2.h b/SDK/simQt/CategoryTreeModel2.h deleted file mode 100644 index 19adac1d9..000000000 --- a/SDK/simQt/CategoryTreeModel2.h +++ /dev/null @@ -1,366 +0,0 @@ -/* -*- mode: c++ -*- */ -/**************************************************************************** -***** ***** -***** Classification: UNCLASSIFIED ***** -***** Classified By: ***** -***** Declassify On: ***** -***** ***** -**************************************************************************** -* -* -* Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. -* EW Modeling & Simulation, Code 5773 -* 4555 Overlook Ave. -* Washington, D.C. 20375-5339 -* -* License for source code at https://simdis.nrl.navy.mil/License.aspx -* -* The U.S. Government retains all rights to use, duplicate, distribute, -* disclose, or release this software. -* -*/ -#ifndef SIMQT_CATEGORYTREEMODEL2_H -#define SIMQT_CATEGORYTREEMODEL2_H - -#include -#include -#include -#include -#include -#include -#include "simCore/Common/Common.h" -#include "simData/CategoryData/CategoryFilter.h" -#include "simData/ObjectId.h" - -class QAction; -class QFont; -class QTreeView; -namespace simData { - class CategoryFilter; - class DataStore; -} - -namespace simQt { - -class AsyncCategoryCounter; -struct CategoryCountResults; -class Settings; - -/** -* Container class that keeps track of a set of pointers. The container is indexed to -* provide O(lg n) responses to indexOf() while maintaining O(1) on access-by-index. -* The trade-off is a second internal container that maintains a list of indices. -* -* This is a template container. Typename T can be any type that can be deleted. -* -* This class is particularly useful for Abstract Item Models that need to know things like -* the indexOf() for a particular entry. -*/ -template -class SDKQT_EXPORT IndexedPointerContainer -{ -public: - IndexedPointerContainer(); - virtual ~IndexedPointerContainer(); - - /** Retrieves the item at the given index. Not range checked. O(1). */ - T* operator[](int index) const; - /** Retrieves the index of the given item. Returns -1 on not-found. O(lg n). */ - int indexOf(const T* ptr) const; - /** Returns the number of items in the container. */ - int size() const; - /** Adds an item into the container. Must be a unique item. */ - void push_back(T* item); - /** Convenience method to delete each item, then clear(). */ - void deleteAll(); - -private: - /** Vector of pointers. */ - typename std::vector vec_; - /** Maps pointers to their index in the vector. */ - typename std::map itemToIndex_; -}; - -/// Used to sort and filter the CategoryTreeModel -class SDKQT_EXPORT CategoryProxyModel : public QSortFilterProxyModel -{ - Q_OBJECT - -public: - /// constructor passes parent to QSortFilterProxyModel - explicit CategoryProxyModel(QObject *parent = 0); - virtual ~CategoryProxyModel(); - - /** Override setSourceModel() to detect the "All Items" model type */ - virtual void setSourceModel(QAbstractItemModel* sourceModel); - -public slots: - /// string to filter against - void setFilterText(const QString& filter); - /// Rests the filter by calling invalidateFilter - void resetFilter(); - -protected: - /// filtering function - virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; - -private: - /// string to filter against - QString filter_; - /// Set true when "All Categories" is the root item (i.e. using CategoryTreeModel, not CategoryTreeModel2) - bool hasAllCategories_; -}; - -/** Single tier tree model that maintains and allows users to edit a simData::CategoryFilter. */ -class SDKQT_EXPORT CategoryTreeModel2 : public QAbstractItemModel -{ - Q_OBJECT; -public: - explicit CategoryTreeModel2(QObject* parent = NULL); - virtual ~CategoryTreeModel2(); - - /** Changes the data store, updating what categories and values are shown. */ - void setDataStore(simData::DataStore* dataStore); - /** Retrieves the category filter. Only call this if the Data Store has been set. */ - const simData::CategoryFilter& categoryFilter() const; - /** Sets the settings and the key prefix for saving and loading the locked states */ - void setSettings(Settings* settings, const QString& settingsKeyPrefix); - - /** Enumeration of user roles supported by data() */ - enum { - ROLE_SORT_STRING = Qt::UserRole, - ROLE_EXCLUDE, - ROLE_CATEGORY_NAME, - ROLE_REGEXP_STRING, - ROLE_LOCKED_STATE - }; - - // QAbstractItemModel overrides - virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; - virtual QModelIndex parent(const QModelIndex &child) const; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - virtual Qt::ItemFlags flags(const QModelIndex& index) const; - virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); - - /// data role for obtaining names that are remapped to force "Unlisted Value" and "No Value" to the top - static const int SortRole = Qt::UserRole + 1; - -public slots: - /** Changes the model state to match the values in the filter. */ - void setFilter(const simData::CategoryFilter& filter); - /** Given results of a category count, updates the text for each category. */ - void processCategoryCounts(const simQt::CategoryCountResults& results); - -signals: - /** The internal filter has changed, possibly from user editing or programmatically. */ - void filterChanged(const simData::CategoryFilter& filter); - /** The internal filter has changed from user editing. */ - void filterEdited(const simData::CategoryFilter& filter); - /** Called when the match/exclude button changes. Only emitted if filterChanged() is not emitted. */ - void excludeEdited(int nameInt = 0, bool excludeMode = true); - -private: - class CategoryItem; - class ValueItem; - - /** Adds the category name into the tree structure. */ - void addName_(int nameInt); - /** Adds the category value into the tree structure under the given name. */ - void addValue_(int nameInt, int valueInt); - /** Remove all categories and values. */ - void clearTree_(); - /** Retrieve the CategoryItem representing the name provided. */ - CategoryItem* findNameTree_(int nameInt) const; - /** - * Update the locked state of the specified category if its name appears in the lockedCategories list. - * This method should only be called on data that is updating, since it doesn't emit its own signal for a data change - */ - void updateLockedState_(const QStringList& lockedCategories, CategoryItem& category); - - /** Quick-search vector of category tree items */ - simQt::IndexedPointerContainer categories_; - /** Maps category int values to CategoryItem pointers */ - std::map categoryIntToItem_; - - /** Data store providing the name manager we depend on */ - simData::DataStore* dataStore_; - /** Internal representation of the GUI settings in the form of a simData::CategoryFilter. */ - simData::CategoryFilter* filter_; - - /** Listens to CategoryNameManager to know when new categories and values are added. */ - class CategoryFilterListener; - std::shared_ptr listener_; - - /** Font used for the Category Name tree items */ - QFont* categoryFont_; - - /** Ptr to settings for storing locked states */ - Settings* settings_; - /** Key for accessing the setting */ - QString settingsKey_; -}; - -/** - * Item delegate that provides custom styling for a QTreeView with a CategoryTreeModel2. This - * delegate is required in order to get "Unlisted Value" editing working properly with - * CategoryTreeModel2. The Unlisted Value editing is shown as an EXCLUDE flag on the category - * itself, using a toggle switch to draw the on/off state. Clicking on the toggle will change - * the value in the tree model and therefore in the filter. - * - * Because the item delegate does not have direct access to the QTreeView on which it is - * placed, it cannot correctly deal with clicking on expand/collapse icons. Please listen - * for the expandClicked() signal when using this class in order to deal with expanding - * and collapsing trees. - */ -class SDKQT_EXPORT CategoryTreeItemDelegate : public QStyledItemDelegate -{ - Q_OBJECT; -public: - explicit CategoryTreeItemDelegate(QObject* parent = NULL); - virtual ~CategoryTreeItemDelegate(); - - /** Overrides from QStyledItemDelegate */ - virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; - virtual bool editorEvent(QEvent* evt, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index); - - /** Overrides from QAbstractItemDelegate */ - virtual bool helpEvent(QHelpEvent* evt, QAbstractItemView* view, const QStyleOptionViewItem& option, const QModelIndex& index); - -signals: - /** User clicked on the custom expand button and index needs to be expanded/collapsed. */ - void expandClicked(const QModelIndex& index); - /** User clicked on the custom RegExp edit button and index needs a RegExp assigned. */ - void editRegExpClicked(const QModelIndex& index); - -private: - /** Handles paint() for category name items */ - void paintCategory_(QPainter* painter, QStyleOptionViewItem& option, const QModelIndex& index) const; - /** Handles paint() for value items */ - void paintValue_(QPainter* painter, QStyleOptionViewItem& option, const QModelIndex& index) const; - - /** Handles editorEvent() for category name items */ - bool categoryEvent_(QEvent* evt, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index); - /** Handles editorEvent() for value items. */ - bool valueEvent_(QEvent* evt, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index); - - /** Sub-elements vary depending on the type of index to draw. */ - enum SubElement - { - SE_NONE = 0, - SE_BACKGROUND, - SE_CHECKBOX, - SE_BRANCH, - SE_TEXT, - SE_EXCLUDE_TOGGLE, - SE_REGEXP_BUTTON - }; - /** Contains the rectangles for all sub-elements for an index. */ - struct ChildRects; - /** Calculate the drawn rectangle areas for each sub-element of a given index. */ - void calculateRects_(const QStyleOptionViewItem& option, const QModelIndex& index, ChildRects& rects) const; - /** Determine which sub-element, if any, was hit in a mouse click. See QMouseEvent::pos(). */ - SubElement hit_(const QPoint& pos, const QStyleOptionViewItem& option, const QModelIndex& index) const; - - /** Keeps track of the QModelIndex being clicked. */ - QModelIndex clickedIndex_; - /** Sub-element being clicked */ - SubElement clickedElement_; -}; - -/** - * Widget that includes a QTreeView with a Category Tree Model and a Search Filter - * widget that will display a given category filter. This is an easy-to-use wrapper - * around the CategoryTreeModel2 class that provides a view widget and search field. - */ -class SDKQT_EXPORT CategoryFilterWidget2 : public QWidget -{ - Q_OBJECT; - Q_PROPERTY(bool showEntityCount READ showEntityCount WRITE setShowEntityCount); - -public: - explicit CategoryFilterWidget2(QWidget* parent = 0); - virtual ~CategoryFilterWidget2(); - - /** Sets the data store, updating the category tree based on changes to that data store. */ - void setDataStore(simData::DataStore* dataStore); - /** Retrieves the category filter. Only call this if the Data Store has been set. */ - const simData::CategoryFilter& categoryFilter() const; - /** Sets the settings and the key prefix for saving and loading the locked states */ - void setSettings(Settings* settings, const QString& settingsKeyPrefix); - - /** Returns true if the entity count should be shown next to values. */ - bool showEntityCount() const; - /** Changes whether entity count is shown next to category values. */ - void setShowEntityCount(bool show); - -public slots: - /** Changes the model state to match the values in the filter. */ - void setFilter(const simData::CategoryFilter& filter); - /** Updates the (#) count next to category values with the given category value counts. */ - void processCategoryCounts(const simQt::CategoryCountResults& results); - /** Shows a GUI for editing the regular expression of a given index */ - void showRegExpEditGui_(const QModelIndex& index); - -signals: - /** The internal filter has changed, possibly from user editing or programmatically. */ - void filterChanged(const simData::CategoryFilter& filter); - /** The internal filter has changed from user editing. */ - void filterEdited(const simData::CategoryFilter& filter); - -private slots: - /** Expand the given index from the proxy if filtering */ - void expandDueToProxy_(const QModelIndex& parentIndex, int to, int from); - /** Conditionally expand tree after filter edited. */ - void expandAfterFilterEdited_(const QString& filterText); - - /** Called by delegate to expand an item */ - void toggleExpanded_(const QModelIndex& proxyIndex); - /** Right click occurred on the QTreeView, point relative to QTreeView */ - void showContextMenu_(const QPoint& point); - - /** Reset the active filter, clearing all values */ - void resetFilter_(); - /** Sets the regular expression on the item saved from showContextMenu_ */ - void setRegularExpression_(); - /** Clears the regular expression on the item saved from showContextMenu_ */ - void clearRegularExpression_(); - /** Locks the current category saved from the showContextMenu_ */ - void toggleLockCategory_(); - /** Expands all unlocked categories */ - void expandUnlockedCategories_(); - /** Start a recount of the category values if countDirty_ is true */ - void recountCategories_(); - -private: - class DataStoreListener; - - /** The tree */ - QTreeView* treeView_; - /** Hold the category data */ - simQt::CategoryTreeModel2* treeModel_; - /** Provides sorting and filtering */ - simQt::CategoryProxyModel* proxy_; - /** If true the category values are filtered; used to conditionally expand tree. */ - bool activeFiltering_; - /** If true the category values show a (#) count after them. */ - bool showEntityCount_; - /** Counter object that provides values for entity counting. */ - AsyncCategoryCounter* counter_; - /** Action used for setting regular expressions */ - QAction* setRegExpAction_; - /** Action used for clearing regular expressions */ - QAction* clearRegExpAction_; - /** Action used for toggling the lock state of a category */ - QAction* toggleLockCategoryAction_; - /** Listener for datastore entity events */ - std::shared_ptr dsListener_; - /** If true then the category counts need to be redone */ - bool countDirty_; -}; - -} - -#endif /* SIMQT_CATEGORYTREEMODEL2_H */ diff --git a/SDK/simQt/DndTreeView.cpp b/SDK/simQt/DndTreeView.cpp new file mode 100644 index 000000000..91658949d --- /dev/null +++ b/SDK/simQt/DndTreeView.cpp @@ -0,0 +1,44 @@ +/* -*- mode: c++ -*- */ +/**************************************************************************** + ***** ***** + ***** Classification: UNCLASSIFIED ***** + ***** Classified By: ***** + ***** Declassify On: ***** + ***** ***** + **************************************************************************** + * + * + * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. + * EW Modeling & Simulation, Code 5773 + * 4555 Overlook Ave. + * Washington, D.C. 20375-5339 + * + * License for source code at https://simdis.nrl.navy.mil/License.aspx + * + * The U.S. Government retains all rights to use, duplicate, distribute, + * disclose, or release this software. + * + */ + +#include +#include "simQt/DndTreeView.h" + +namespace simQt { + +DndTreeView::DndTreeView(QWidget* parent) + : QTreeView(parent) +{ +} + +DndTreeView::~DndTreeView() +{ +} + +void DndTreeView::dragEnterEvent(QDragEnterEvent *event) +{ + QTreeView::dragEnterEvent(event); + event->acceptProposedAction(); +} + +} + diff --git a/SDK/simQt/DndTreeView.h b/SDK/simQt/DndTreeView.h new file mode 100644 index 000000000..b80793bf4 --- /dev/null +++ b/SDK/simQt/DndTreeView.h @@ -0,0 +1,54 @@ +/* -*- mode: c++ -*- */ +/**************************************************************************** + ***** ***** + ***** Classification: UNCLASSIFIED ***** + ***** Classified By: ***** + ***** Declassify On: ***** + ***** ***** + **************************************************************************** + * + * + * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. + * EW Modeling & Simulation, Code 5773 + * 4555 Overlook Ave. + * Washington, D.C. 20375-5339 + * + * License for source code at https://simdis.nrl.navy.mil/License.aspx + * + * The U.S. Government retains all rights to use, duplicate, distribute, + * disclose, or release this software. + * + */ + +#ifndef SIMQT_DND_TREE_VIEW_H +#define SIMQT_DND_TREE_VIEW_H + +#include +#include "simCore/Common/Export.h" + +namespace simQt { + +/** + * Wrapper class to circumvent a Drag and Drop bug with QTreeView. + * For details see: https://bugreports.qt.io/browse/QTBUG-76418 + * and https://bugreports.qt.io/browse/QTBUG-44939 + */ +class SDKQT_EXPORT DndTreeView : public QTreeView +{ + Q_OBJECT; + +public: + /** Constructor */ + explicit DndTreeView(QWidget* parent = NULL); + + /** Destructor */ + virtual ~DndTreeView(); + +protected: + /** Override to circumvent a Drag and Drop bug */ + virtual void dragEnterEvent(QDragEnterEvent* event); +}; + +} + +#endif diff --git a/SDK/simQt/EntityCategoryFilter.cpp b/SDK/simQt/EntityCategoryFilter.cpp index bdbc00006..6dc46216d 100644 --- a/SDK/simQt/EntityCategoryFilter.cpp +++ b/SDK/simQt/EntityCategoryFilter.cpp @@ -20,8 +20,7 @@ * */ #include "simData/CategoryData/CategoryFilter.h" -#include "simQt/CategoryFilterWidget.h" -#include "simQt/CategoryTreeModel2.h" +#include "simQt/CategoryTreeModel.h" #include "simQt/RegExpImpl.h" #include "simQt/EntityCategoryFilter.h" @@ -55,25 +54,13 @@ QWidget* EntityCategoryFilter::widget(QWidget* newWidgetParent) const break; case SHOW_WIDGET: { - CategoryFilterWidget2* rv = new CategoryFilterWidget2(newWidgetParent); + CategoryFilterWidget* rv = new CategoryFilterWidget(newWidgetParent); rv->setDataStore(categoryFilter_->getDataStore()); rv->setFilter(*categoryFilter_); rv->setSettings(settings_, settingsKeyPrefix_); bindToWidget(rv); return rv; } - case SHOW_LEGACY_WIDGET: - { -#ifdef USE_DEPRECATED_SIMDISSDK_API - CategoryFilterWidget* rv = new CategoryFilterWidget(newWidgetParent); - rv->setProviders(categoryFilter_->getDataStore()); - rv->setFilter(*categoryFilter_); - bindToWidget(rv); - return rv; -#else - return NULL; -#endif - } } return NULL; @@ -104,7 +91,7 @@ void EntityCategoryFilter::setFilterSettings(const QMap& sett } } -void EntityCategoryFilter::bindToWidget(CategoryFilterWidget2* widget) const +void EntityCategoryFilter::bindToWidget(CategoryFilterWidget* widget) const { // Whenever the filter updates in the GUI, update our internal filter, // which then in turn emits filterUpdated(). @@ -114,15 +101,6 @@ void EntityCategoryFilter::bindToWidget(CategoryFilterWidget2* widget) const connect(this, SIGNAL(categoryFilterChanged(simData::CategoryFilter)), widget, SLOT(setFilter(simData::CategoryFilter))); } -#ifdef USE_DEPRECATED_SIMDISSDK_API -void EntityCategoryFilter::bindToWidget(CategoryFilterWidget* widget) const -{ - // connect to the signals/slots between the gui and the filter so changes to one will update the other - connect(widget, SIGNAL(categoryFilterChanged(const simData::CategoryFilter&)), this, SLOT(setCategoryFilterFromGui_(const simData::CategoryFilter&))); - connect(this, SIGNAL(categoryFilterChanged(simData::CategoryFilter)), widget, SLOT(setFilter(simData::CategoryFilter))); -} -#endif - void EntityCategoryFilter::setCategoryFilter(const simData::CategoryFilter& categoryFilter) { if ((*categoryFilter_) == categoryFilter) diff --git a/SDK/simQt/EntityCategoryFilter.h b/SDK/simQt/EntityCategoryFilter.h index 59980ef98..66bef59c4 100644 --- a/SDK/simQt/EntityCategoryFilter.h +++ b/SDK/simQt/EntityCategoryFilter.h @@ -32,11 +32,7 @@ namespace simData { namespace simQt { - -#ifdef USE_DEPRECATED_SIMDISSDK_API class CategoryFilterWidget; -#endif -class CategoryFilterWidget2; class Settings; /** @@ -52,10 +48,8 @@ class SDKQT_EXPORT EntityCategoryFilter : public EntityFilter { /** widget() will return NULL, creating nothing when integrated into Qt. */ NO_WIDGET, - /** widget() will return a CategoryFilterWidget2, the new style of category filtering. */ - SHOW_WIDGET, - /** widget() will return a CategoryFilterWidget, the old legacy style of category filtering. */ - SHOW_LEGACY_WIDGET + /** widget() will return a CategoryFilterWidget, the new style of category filtering. */ + SHOW_WIDGET }; /** @@ -90,19 +84,15 @@ class SDKQT_EXPORT EntityCategoryFilter : public EntityFilter virtual void setFilterSettings(const QMap& settings); /** - * Bind this filter to a CategoryFilterWidget2 so that changes to either widget updates the other widget. + * Bind this filter to a CategoryFilterWidget so that changes to either widget updates the other widget. * @param widget to be bound */ - void bindToWidget(CategoryFilterWidget2* widget) const; -#ifdef USE_DEPRECATED_SIMDISSDK_API - /** Category Filter Widget (legacy style) variant of bindToWidget() */ void bindToWidget(CategoryFilterWidget* widget) const; -#endif /** Retrieves the current category filter. */ const simData::CategoryFilter& categoryFilter() const; - /** Set the settings object and key prefix that gets used by the CategoryFilterWidget2 */ + /** Set the settings object and key prefix that gets used by the CategoryFilterWidget */ void setSettings(Settings* settings, const QString& settingsKeyPrefix); public slots: diff --git a/SDK/simQt/EntityProxyModel.cpp b/SDK/simQt/EntityProxyModel.cpp index 70af7517d..e5671676c 100644 --- a/SDK/simQt/EntityProxyModel.cpp +++ b/SDK/simQt/EntityProxyModel.cpp @@ -145,7 +145,9 @@ namespace simQt { return false; } - if (alwaysShow_ == id) + // Make sure alwaysShow_ is active before comparing; otherwise there is a conflict + // with the Scenario entry which uses an ID of 0. + if ((alwaysShow_ != 0) && (alwaysShow_ == id)) return true; // check against all filters diff --git a/SDK/simQt/EntityTreeComposite.cpp b/SDK/simQt/EntityTreeComposite.cpp index 3da426788..0b7122c21 100644 --- a/SDK/simQt/EntityTreeComposite.cpp +++ b/SDK/simQt/EntityTreeComposite.cpp @@ -127,6 +127,7 @@ class EntityTreeComposite::ButtonActions private: /** Declared but not defined to keep cppCheck warning free */ ButtonActions(const ButtonActions& rhs); + ButtonActions& operator=(ButtonActions& rhs); /** Sets the text and tooltip on the "Load" button */ void setLoadTextAndTooltips_(const QString& filterName) @@ -705,6 +706,14 @@ void EntityTreeComposite::setUseCenterAction(bool use, const QString& reason) centerAction_->setEnabled(false); } +void EntityTreeComposite::setTreeView(bool useTreeView) +{ + if (!treeViewUsable_) + return; + + setTreeView_(useTreeView); +} + void EntityTreeComposite::onItemsChanged_(const QList& ids) { bool empty = ids.isEmpty(); @@ -743,11 +752,17 @@ void EntityTreeComposite::centerOnSelection_() void EntityTreeComposite::setTreeView_(bool useTreeView) { + // Return early if nothing changed + if (entityTreeWidget_->isTreeView() == useTreeView && toggleTreeViewAction_->isChecked() == useTreeView) + return; + // Toggle the tree view entityTreeWidget_->toggleTreeView(useTreeView); // Update related UI components toggleTreeViewAction_->setChecked(useTreeView); updateActionEnables_(); + + emit treeViewChanged(useTreeView); } void EntityTreeComposite::updateActionEnables_() diff --git a/SDK/simQt/EntityTreeComposite.h b/SDK/simQt/EntityTreeComposite.h index 9e54b792f..e56b7b28d 100644 --- a/SDK/simQt/EntityTreeComposite.h +++ b/SDK/simQt/EntityTreeComposite.h @@ -143,6 +143,8 @@ class SDKQT_EXPORT EntityTreeComposite : public QWidget * @param reason The reason is appended to the end of the center action text */ void setUseCenterAction(bool use, const QString& reason = ""); + /** Toggle the tree/list view and update related UI component and action states if the tree view action is enabled */ + void setTreeView(bool useTreeView); /** Class to store information about an Entity Tab Filter Configuration */ class FilterConfiguration @@ -215,6 +217,9 @@ public slots: /** Fired before showing the right mouse click menu to allow external code to add and remove actions. */ void rightClickMenuRequested(QMenu* menu=NULL); + /** Fired when entityTreeComposite toggles between tree and list view */ + void treeViewChanged(bool useTreeView); + protected slots: /** Receive notice of an inserted row */ void rowsInserted_(const QModelIndex & parent, int start, int end); diff --git a/SDK/simQt/EntityTreeComposite.ui b/SDK/simQt/EntityTreeComposite.ui index abae5445c..44ad5ec64 100644 --- a/SDK/simQt/EntityTreeComposite.ui +++ b/SDK/simQt/EntityTreeComposite.ui @@ -25,7 +25,7 @@ - + Qt::ActionsContextMenu @@ -237,6 +237,11 @@ QLineEdit

simQt/EntityFilterLineEdit.h
+ + simQt::DndTreeView + QTreeView +
simQt/DndTreeView.h
+
diff --git a/SDK/simQt/EntityTreeModel.cpp b/SDK/simQt/EntityTreeModel.cpp index 4c3a3acb4..f22555733 100644 --- a/SDK/simQt/EntityTreeModel.cpp +++ b/SDK/simQt/EntityTreeModel.cpp @@ -627,6 +627,20 @@ QModelIndex EntityTreeModel::index(uint64_t id) const return QModelIndex(); } +QModelIndex EntityTreeModel::index(uint64_t id) +{ + EntityTreeItem* item = findItem_(id); + if (item == NULL) + { + commitDelayedEntities_(); + item = findItem_(id); + if (item == NULL) + return QModelIndex(); + } + + return createIndex(item->row(), 0, item); +} + uint64_t EntityTreeModel::uniqueId(const QModelIndex &index) const { if (!index.isValid()) diff --git a/SDK/simQt/EntityTreeModel.h b/SDK/simQt/EntityTreeModel.h index b5adc8956..43eae62a5 100644 --- a/SDK/simQt/EntityTreeModel.h +++ b/SDK/simQt/EntityTreeModel.h @@ -80,6 +80,8 @@ class SDKQT_EXPORT EntityTreeModel : public simQt::AbstractEntityTreeModel virtual QModelIndex index(int row, int column, const QModelIndex &parent) const; /// Return the index for the given id virtual QModelIndex index(uint64_t id) const; + /// Return an Index based on the entity's ID; if necessary, process any pending adds + virtual QModelIndex index(uint64_t id); /// Return the index of the parent of the item given by index virtual QModelIndex parent(const QModelIndex &index) const; /// Return the number of rows in the data diff --git a/SDK/simQt/MapDataModel.cpp b/SDK/simQt/MapDataModel.cpp index 4acef255e..cdb67322e 100644 --- a/SDK/simQt/MapDataModel.cpp +++ b/SDK/simQt/MapDataModel.cpp @@ -777,7 +777,7 @@ class MapDataModel::MapListener : public osgEarth::MapCallback }; /** Watch for image layer changes */ -class MapDataModel::ImageLayerListener : public osgEarth::ImageLayerCallback +class MapDataModel::ImageLayerListener : public osgEarth::TileLayerCallback { public: /** Constructor */ @@ -801,30 +801,12 @@ class MapDataModel::ImageLayerListener : public osgEarth::ImageLayerCallback emit dataModel_.imageLayerOpacityChanged(imageLayer); } - /** Inherited from VisibleLayerCallback */ - virtual void onVisibleRangeChanged(osgEarth::ImageLayer *layer) - { - emit dataModel_.imageLayerVisibleRangeChanged(layer); - } - - /** Inherited from VisibleLayerCallback */ - virtual void onColorFiltersChanged(osgEarth::ImageLayer *layer) - { - emit dataModel_.imageLayerColorFilterChanged(layer); - } - - /** Inherited from ImageLayerCallback */ - virtual void onAltitudeChanged(class osgEarth::ImageLayer *layer) - { - emit dataModel_.imageLayerAltitudeChanged(layer); - } - private: MapDataModel& dataModel_; }; /** Watch for elevation layer changes */ -class MapDataModel::ElevationLayerListener : public osgEarth::ElevationLayerCallback +class MapDataModel::ElevationLayerListener : public osgEarth::TileLayerCallback { public: /** Constructor */ @@ -967,7 +949,10 @@ void MapDataModel::removeAllCallbacks_(osgEarth::Map* map) for (osgEarth::ImageLayerVector::const_iterator iter = imageLayers.begin(); iter != imageLayers.end(); ++iter) { if (imageCallbacks_.contains(iter->get())) - iter->get()->removeCallback(imageCallbacks_.find(iter->get())->get()); + { + osgEarth::TileLayer* tileLayer = iter->get(); + tileLayer->removeCallback(imageCallbacks_.find(iter->get())->get()); + } } // Assertion failure means that we were out of sync with map; not a one-to-one with callback-to-layer assert(imageCallbacks_.size() == static_cast(imageLayers.size())); @@ -980,7 +965,10 @@ void MapDataModel::removeAllCallbacks_(osgEarth::Map* map) for (osgEarth::ElevationLayerVector::const_iterator iter = elevationLayers.begin(); iter != elevationLayers.end(); ++iter) { if (elevationCallbacks_.contains(iter->get())) - iter->get()->removeCallback(elevationCallbacks_.find(iter->get())->get()); + { + osgEarth::TileLayer* tileLayer = iter->get(); + tileLayer->removeCallback(elevationCallbacks_.find(iter->get())->get()); + } } // Assertion failure means that we were out of sync with map; not a one-to-one with callback-to-layer assert(elevationCallbacks_.size() == static_cast(elevationLayers.size())); @@ -1026,9 +1014,9 @@ void MapDataModel::fillModel_(osgEarth::Map *map) for (osgEarth::ImageLayerVector::const_reverse_iterator iter = imageLayers.rbegin(); iter != imageLayers.rend(); ++iter) { imageGroup_()->insertChild(new ImageLayerItem(imageGroup_(), iter->get()), 0); - osg::ref_ptr cb = new ImageLayerListener(*this); + osg::ref_ptr cb = new ImageLayerListener(*this); imageCallbacks_[iter->get()] = cb.get(); - (*iter)->addCallback(cb.get()); + static_cast(*iter)->addCallback(cb.get()); } osgEarth::ElevationLayerVector elevationLayers; @@ -1037,9 +1025,9 @@ void MapDataModel::fillModel_(osgEarth::Map *map) for (osgEarth::ElevationLayerVector::const_reverse_iterator iter = elevationLayers.rbegin(); iter != elevationLayers.rend(); ++iter) { elevationGroup_()->insertChild(new ElevationLayerItem(elevationGroup_(), iter->get()), 0); - osg::ref_ptr cb = new ElevationLayerListener(*this); + osg::ref_ptr cb = new ElevationLayerListener(*this); elevationCallbacks_[iter->get()] = cb.get(); - (*iter)->addCallback(cb.get()); + static_cast(*iter)->addCallback(cb.get()); } FeatureModelLayerVector featureLayers; @@ -1104,9 +1092,9 @@ void MapDataModel::addImageLayer_(osgEarth::ImageLayer *layer, unsigned int inde imageGroup_()->insertChild(new ImageLayerItem(imageGroup_(), layer), index); endInsertRows(); - osg::ref_ptr cb = new ImageLayerListener(*this); + osg::ref_ptr cb = new ImageLayerListener(*this); imageCallbacks_[layer] = cb.get(); - layer->addCallback(cb.get()); + static_cast(layer)->addCallback(cb.get()); emit imageLayerAdded(layer); } @@ -1118,9 +1106,9 @@ void MapDataModel::addElevationLayer_(osgEarth::ElevationLayer *layer, unsigned elevationGroup_()->insertChild(new ElevationLayerItem(elevationGroup_(), layer), index); endInsertRows(); - osg::ref_ptr cb = new ElevationLayerListener(*this); + osg::ref_ptr cb = new ElevationLayerListener(*this); elevationCallbacks_[layer] = cb.get(); - layer->addCallback(cb.get()); + static_cast(layer)->addCallback(cb.get()); emit elevationLayerAdded(layer); } @@ -1371,10 +1359,9 @@ QVariant MapDataModel::layerMapIndex_(osgEarth::Layer* layer) const if (layer == NULL || !map_.valid()) return QVariant(); - unsigned int index = MapReindexer::INVALID_INDEX; osgEarth::LayerVector layers; map_->getLayers(layers); - index = indexOf(layers, layer); + unsigned int index = indexOf(layers, layer); if (index != MapReindexer::INVALID_INDEX) return index; diff --git a/SDK/simQt/MapDataModel.h b/SDK/simQt/MapDataModel.h index 335aae4a6..a9afb6d23 100644 --- a/SDK/simQt/MapDataModel.h +++ b/SDK/simQt/MapDataModel.h @@ -35,7 +35,6 @@ namespace osgEarth { class Layer; class Map; - class TerrainLayer; } typedef std::vector > FeatureModelLayerVector; @@ -155,12 +154,6 @@ public slots: /** Qt signal as described by the signal name */ void imageLayerOpacityChanged(osgEarth::ImageLayer* layer); /** Qt signal as described by the signal name */ - void imageLayerColorFilterChanged(osgEarth::ImageLayer* layer); - /** Qt signal as described by the signal name */ - void imageLayerVisibleRangeChanged(osgEarth::ImageLayer* layer); - /** Qt signal as described by the signal name */ - void imageLayerAltitudeChanged(osgEarth::ImageLayer* layer); - /** Qt signal as described by the signal name */ void imageLayerAdded(osgEarth::ImageLayer* layer); /** Qt signal as described by the signal name */ void elevationLayerVisibleChanged(osgEarth::ElevationLayer* layer); @@ -257,8 +250,8 @@ public slots: QIcon otherIcon_; /** Maps of terrain layer callbacks */ - QMap > imageCallbacks_; - QMap > elevationCallbacks_; + QMap > imageCallbacks_; + QMap > elevationCallbacks_; QMap > featureCallbacks_; QMap > otherCallbacks_; diff --git a/SDK/simQt/MruList.cpp b/SDK/simQt/MruList.cpp index 636e598af..90ce1ea75 100644 --- a/SDK/simQt/MruList.cpp +++ b/SDK/simQt/MruList.cpp @@ -157,8 +157,11 @@ void MruList::fixActions_() action->setVisible(true); action->setEnabled(true); - // Text looks like: "&3 file.asi" - action->setText(tr("&%1 %2").arg(idx + 1).arg(fi.fileName())); + // Text looks like: "&3 file.asi"; can only specify shortcuts (&) for items numbered < 10. + if (idx < 9) + action->setText(tr("&%1 %2").arg(idx + 1).arg(fi.fileName())); + else + action->setText(tr("%1 %2").arg(idx + 1).arg(fi.fileName())); // We're showing an action, so we want to show the separators showSeparators = true; diff --git a/SDK/simQt/ResourceInitializer.cpp b/SDK/simQt/ResourceInitializer.cpp index 6aa31cef4..0e546c900 100644 --- a/SDK/simQt/ResourceInitializer.cpp +++ b/SDK/simQt/ResourceInitializer.cpp @@ -24,7 +24,7 @@ #include #include "simQt/ConsoleDataModel.h" #ifdef HAVE_SIMDATA -#include "simQt/EntityLineEdit.h" +#include "simQt/EntityStateFilter.h" #include "simQt/EntityTreeComposite.h" #endif #include "simQt/SettingsModel.h" diff --git a/SDK/simQt/SegmentedSpinBox.cpp b/SDK/simQt/SegmentedSpinBox.cpp index 504115e99..68ba84d20 100644 --- a/SDK/simQt/SegmentedSpinBox.cpp +++ b/SDK/simQt/SegmentedSpinBox.cpp @@ -292,9 +292,8 @@ class SegmentedEventFilter : public QObject if (lastEditedTime_ != completeLine_->timeStamp()) { completeLine_->valueChanged(); - // If we have focus assume the change was user initiated - if (hasFocus()) - completeLine_->valueEdited(); + // emit signal regardless of hasFocus() because this routine is only called by user initiated changes + completeLine_->valueEdited(); lastEditedTime_ = completeLine_->timeStamp(); } } diff --git a/SDK/simQt/TimestampedLayerManager.cpp b/SDK/simQt/TimestampedLayerManager.cpp index 1e1b2f08f..dbcc65b7c 100644 --- a/SDK/simQt/TimestampedLayerManager.cpp +++ b/SDK/simQt/TimestampedLayerManager.cpp @@ -302,11 +302,11 @@ void TimestampedLayerManager::addLayerWithTime_(osgEarth::ImageLayer* newLayer) iso8601 = conf.value("times"); /* * Some image layer file types can have time values (e.g. db files). Config values can't be - * changed at time of file read, so time is set as a user value of the tile source in these cases. + * changed at time of file read, so time is set as a user value of the layer in these cases. * Config values take precedence of user values. */ - if (iso8601.empty() && newLayer->getTileSource()) - newLayer->getTileSource()->getUserValue("time", iso8601); + if (iso8601.empty()) + newLayer->getUserValue("time", iso8601); // If layer has no time, nothing to do with it if (iso8601.empty()) return; diff --git a/SDK/simUtil.h b/SDK/simUtil.h index 91c5b8fa2..415d3cb4d 100644 --- a/SDK/simUtil.h +++ b/SDK/simUtil.h @@ -25,7 +25,6 @@ #include "simUtil/Capabilities.h" #include "simUtil/DataStoreTestHelper.h" #include "simUtil/DatumConvert.h" -#include "simUtil/DbConfigurationFile.h" #include "simUtil/DefaultDataStoreValues.h" #include "simUtil/DynamicSelectionPicker.h" #include "simUtil/ExampleControls.h" diff --git a/SDK/simUtil/CMakeLists.txt b/SDK/simUtil/CMakeLists.txt index 4383f7f33..e44a693ba 100644 --- a/SDK/simUtil/CMakeLists.txt +++ b/SDK/simUtil/CMakeLists.txt @@ -18,7 +18,6 @@ set(UTIL_HEADERS ${UTIL_INC}Capabilities.h ${UTIL_INC}DataStoreTestHelper.h ${UTIL_INC}DatumConvert.h - ${UTIL_INC}DbConfigurationFile.h ${UTIL_INC}DefaultDataStoreValues.h ${UTIL_INC}ExampleControls.h ${UTIL_INC}ExampleResources.h @@ -54,7 +53,6 @@ set(UTIL_SOURCES ${UTIL_SRC}Capabilities.cpp ${UTIL_SRC}DataStoreTestHelper.cpp ${UTIL_SRC}DatumConvert.cpp - ${UTIL_SRC}DbConfigurationFile.cpp ${UTIL_SRC}DefaultDataStoreValues.cpp ${UTIL_SRC}ExampleControls.cpp ${UTIL_SRC}ExampleResources.cpp @@ -83,6 +81,12 @@ set(UTIL_SOURCES ${UTIL_SRC}ViewpointMonitor.cpp ${UTIL_SRC}ViewpointPositions.cpp ) + +if(SIM_HAVE_DB_SUPPORT) + list(APPEND UTIL_HEADERS ${UTIL_INC}DbConfigurationFile.h) + list(APPEND UTIL_SOURCES ${UTIL_SRC}DbConfigurationFile.cpp) +endif() + set(UTIL_GENERATED_HEADERS) set(UTIL_GENERATED_SOURCES) @@ -132,7 +136,7 @@ endif() add_library(simUtil ${STATIC_OR_SHARED} ${UTIL_PROJECT_FILES}) set_target_properties(simUtil PROPERTIES FOLDER "SIMDIS SDK" - PROJECT_LABEL "SIMDIS SDK - Util" + PROJECT_LABEL "simUtil" ) ApplySDKVersion(simUtil) target_include_directories(simUtil PUBLIC @@ -158,6 +162,9 @@ endif() if(OSGEARTH_SILVERLINING_SUPPORT STREQUAL "NODEKIT" AND TARGET OSGEARTH_SILVERLINING) target_link_libraries(simUtil PRIVATE OSGEARTH_SILVERLINING) endif() +if(SIM_HAVE_DB_SUPPORT) + target_compile_definitions(simUtil PRIVATE SIM_HAVE_DB_SUPPORT) +endif() if(INSTALL_SIMDIS_SDK_SHADERS) if(NOT SIMDIS_SDK_SHADERS_INSTALL_DIR) diff --git a/SDK/simUtil/Capabilities.cpp b/SDK/simUtil/Capabilities.cpp index ee8e19839..a924957c8 100644 --- a/SDK/simUtil/Capabilities.cpp +++ b/SDK/simUtil/Capabilities.cpp @@ -30,7 +30,6 @@ #include "osgEarth/Registry" #include "osgEarth/Capabilities" #include "osgEarth/StringUtils" -#include "simCore/String/Format.h" #include "simCore/String/Utils.h" #include "simUtil/Capabilities.h" @@ -97,14 +96,6 @@ void Capabilities::init_() glVersion_ = extractGlVersion_(caps.getVersion()); caps_.push_back(std::make_pair("Core Profile", toString_(caps.isCoreProfile()))); - // Based on recommendation from https://www.khronos.org/opengl/wiki/OpenGL_Context#Context_information_queries - // Note that Mesa, Gallium, and Direct3D renderers are all potentially backed by a hardware - // acceleration, and do not necessarily imply software acceleration. - if (caps.getVendor().find("Microsoft") != std::string::npos) - { - recordUsabilityConcern_(USABLE_WITH_ARTIFACTS, "Software renderer detected; possibly no 3D acceleration; performance concerns"); - } - // OpenGL version must be usable. OSG 3.6 with core profile support will not function // without support for VAO, which requires OpenGL 3.0, released in 2008. Although we // require interface blocks from GLSL 3.3, we only absolutely require OpenGL features @@ -114,6 +105,8 @@ void Capabilities::init_() recordUsabilityConcern_(UNUSABLE, osgEarth::Stringify() << "OpenGL version below 3.0 (detected " << glVersion_ << ")"); } + checkVendorOpenGlSupport_(caps.getVendor(), caps.getVersion()); + caps_.push_back(std::make_pair("Max FFP texture units", toString_(caps.getMaxFFPTextureUnits()))); caps_.push_back(std::make_pair("Max GPU texture units", toString_(caps.getMaxGPUTextureUnits()))); caps_.push_back(std::make_pair("Max GPU texture coordinate sets", toString_(caps.getMaxGPUTextureCoordSets()))); @@ -196,14 +189,6 @@ void Capabilities::init_(osg::GraphicsContext& gc) const bool isCoreProfile = (glVersion_ >= 3.2f && ((profileMask & GL_CONTEXT_CORE_PROFILE_BIT) != 0)); caps_.push_back(std::make_pair("Core Profile", toString_(isCoreProfile))); - // Based on recommendation from https://www.khronos.org/opengl/wiki/OpenGL_Context#Context_information_queries - // Note that Mesa, Gallium, and Direct3D renderers are all potentially backed by a hardware - // acceleration, and do not necessarily imply software acceleration. - if (vendorString.find("Microsoft") != std::string::npos) - { - recordUsabilityConcern_(USABLE_WITH_ARTIFACTS, "Software renderer detected; possibly no 3D acceleration; performance concerns"); - } - // OpenGL version must be usable. OSG 3.6 with core profile support will not function // without support for VAO, which requires OpenGL 3.0, released in 2008. Although we // require interface blocks from GLSL 3.3, we only absolutely require OpenGL features @@ -213,6 +198,8 @@ void Capabilities::init_(osg::GraphicsContext& gc) recordUsabilityConcern_(UNUSABLE, osgEarth::Stringify() << "OpenGL version below 3.0 (detected " << glVersion_ << ")"); } + checkVendorOpenGlSupport_(vendorString, glVersionString); + #if OSG_MIN_VERSION_REQUIRED(3,6,0) // OSG 3.6 auto-detects for us GLint maxTextureUnits = ext->glMaxTextureUnits; @@ -231,7 +218,7 @@ void Capabilities::init_(osg::GraphicsContext& gc) maxTextureCoords = maxTextureUnits; #endif } - else if (glVersion_ >= 1.3f || osg::isGLExtensionSupported(contextId, "GL_ARB_multitexture") || osg::isGLExtensionSupported(contextId, "GL_ARB_multitexture")) + else if (glVersion_ >= 1.3f || osg::isGLExtensionSupported(contextId, "GL_ARB_multitexture") || osg::isGLExtensionSupported(contextId, "GL_EXT_multitexture")) { // Fall back to multitexturing units for oldest OpenGL glGetIntegerv(GL_MAX_TEXTURE_UNITS, &maxTextureUnits); @@ -322,6 +309,54 @@ double Capabilities::extractGlVersion_(const std::string& glVersionString) const return atof(glVersionString.c_str()); } +void Capabilities::checkVendorOpenGlSupport_(const std::string& vendor, const std::string& glVersionString) +{ + // Based on recommendation from https://www.khronos.org/opengl/wiki/OpenGL_Context#Context_information_queries + // Note that Mesa, Gallium, and Direct3D renderers are all potentially backed by a hardware + // acceleration, and do not necessarily imply software acceleration. + if (vendor.find("Microsoft") != std::string::npos) + { + recordUsabilityConcern_(USABLE_WITH_ARTIFACTS, "Software renderer detected; possibly no 3D acceleration; performance concerns"); + return; + } + + if (vendor.find("NVIDIA") != std::string::npos) + { + // glVersionString is expected to look like: 3.3.0 NVIDIA major.minor + const std::string& nvidiaVersionStr = simCore::StringUtils::after(glVersionString, "NVIDIA"); + const std::string& nvidiaMajorStr = simCore::StringUtils::before(nvidiaVersionStr, "."); + const std::string& nvidiaMinorStr = simCore::StringUtils::after(nvidiaVersionStr, "."); + if (nvidiaVersionStr.empty() || nvidiaMajorStr.empty() || nvidiaMinorStr.empty()) + { + // nvidia driver that does not return version string as part of opengl version + // nothing to do + return; + } + const int nVidiaMajor = atoi(nvidiaMajorStr.c_str()); + const int nVidiaMinor = atoi(nvidiaMinorStr.c_str()); + // testing indicates that 304.125 and most drivers > 340 work + const bool usable = (nVidiaMajor >= 340) || (nVidiaMajor == 304 && nVidiaMinor >= 125); + if (usable) + return; + // testing indicates that: + // nvidia 331 drivers were not usable + // most drivers <= 340 had issues + const bool unusable = (nVidiaMajor == 331); + recordUsabilityConcern_((unusable ? UNUSABLE : USABLE_WITH_ARTIFACTS), osgEarth::Stringify() << "nVidia driver version " << nVidiaMajor << "." << nVidiaMinor); + return; + } + + if (vendor.find("Intel") != std::string::npos) + { + if (glVersionString.find("9.18.10.3186") != std::string::npos) + { + // driver 9.18.10.3186 known to have issues + recordUsabilityConcern_(UNUSABLE, osgEarth::Stringify() << "Intel driver version 9.18.10.3186"); + } + return; + } +} + Capabilities::Usability Capabilities::isUsable() const { return isUsable_; @@ -331,5 +366,4 @@ std::vector Capabilities::usabilityConcerns() const { return usabilityConcerns_; } - } diff --git a/SDK/simUtil/Capabilities.h b/SDK/simUtil/Capabilities.h index fccea5fae..dd746563b 100644 --- a/SDK/simUtil/Capabilities.h +++ b/SDK/simUtil/Capabilities.h @@ -84,6 +84,8 @@ class SDKUTIL_EXPORT Capabilities void recordUsabilityConcern_(Usability severity, const std::string& concern); /** Extracts the OpenGL version from the GL_VERSION string */ double extractGlVersion_(const std::string& glVersionString) const; + /** Checks for usability concerns wrt vendor-specific OpenGL support */ + void checkVendorOpenGlSupport_(const std::string& vendor, const std::string& glVersionString); /** Converts boolean to string */ std::string toString_(bool val) const; diff --git a/SDK/simUtil/DbConfigurationFile.cpp b/SDK/simUtil/DbConfigurationFile.cpp index 4ae7f057b..7349ffd82 100644 --- a/SDK/simUtil/DbConfigurationFile.cpp +++ b/SDK/simUtil/DbConfigurationFile.cpp @@ -34,7 +34,6 @@ #include "simCore/String/Utils.h" #include "simCore/String/ValidNumber.h" #include "simVis/AlphaColorFilter.h" -#include "simVis/DBOptions.h" #include "simVis/DBFormat.h" #include "simVis/SceneManager.h" #include "simVis/Utils.h" @@ -106,9 +105,11 @@ int DbConfigurationFile::load(osg::ref_ptr& mapNode, const st mapNode = NULL; SAFETRYBEGIN; osg::ref_ptr map = simUtil::DbConfigurationFile::loadLegacyConfigFile(adjustedConfigFile, quiet); - - mapNode = new osgEarth::MapNode(map.get()); - simVis::SceneManager::initializeTerrainOptions(mapNode); + if (map.valid()) + { + mapNode = new osgEarth::MapNode(map.get()); + simVis::SceneManager::initializeTerrainOptions(mapNode.get()); + } SAFETRYEND((std::string("legacy SIMDIS 9 .txt processing of file ") + configFile)); } @@ -196,7 +197,7 @@ osgEarth::Map* DbConfigurationFile::loadLegacyConfigFile(const std::string& file gotValidFirstLine = true; if (!map) { - map = DbConfigurationFile::createDefaultMap_(); + map = new osgEarth::Map(); map->beginUpdate(); } @@ -378,6 +379,7 @@ void DbConfigurationFile::parseLayers_(const std::vector& tokens, o imageLayer->setOpacity(opacity); imageLayer->setVisible(active); imageLayer->setEnabled(active); + imageLayer->setName(layerName); map->addLayer(imageLayer); } if (altitudeSet) @@ -394,6 +396,7 @@ void DbConfigurationFile::parseLayers_(const std::vector& tokens, o iStr >> noDataValue; } newLayer->setNoDataValue(noDataValue); + newLayer->setName(layerName); } } } @@ -496,14 +499,6 @@ std::string DbConfigurationFile::findTokenValue_(const std::vector& return std::string(); } -osgEarth::Map* DbConfigurationFile::createDefaultMap_() -{ - // configure an EGM96 MSL globe.for the Map - osgEarth::Map* map = new osgEarth::Map(); - map->setProfile(osgEarth::Profile::create("wgs84", "egm96")); - return map; -} - osg::Node* DbConfigurationFile::readEarthFile(std::istream& istream, const std::string& relativeTo) { osg::ref_ptr readWrite = osgDB::Registry::instance()->getReaderWriterForExtension("earth"); diff --git a/SDK/simUtil/DbConfigurationFile.h b/SDK/simUtil/DbConfigurationFile.h index 080e1d395..f4b5d901b 100644 --- a/SDK/simUtil/DbConfigurationFile.h +++ b/SDK/simUtil/DbConfigurationFile.h @@ -114,13 +114,6 @@ class SDKUTIL_EXPORT DbConfigurationFile static std::string findTokenValue_(const std::vector& tokens, const std::string& keyword); /// convert QuadSphere levels to osgEarth levels static unsigned int getOsgEarthLevel_(unsigned int QSlevel); - - /** - * Creates a default osgEarth::Map object with the EGM96 MSL globe - * Loads parts of a legacy .txt configuration file (typically consisting of .db files) - * @return the osgEarth::Map created - */ - static osgEarth::Map* createDefaultMap_(); }; } diff --git a/SDK/simUtil/ExampleResources.cpp b/SDK/simUtil/ExampleResources.cpp index cc30723e7..e4e89225b 100644 --- a/SDK/simUtil/ExampleResources.cpp +++ b/SDK/simUtil/ExampleResources.cpp @@ -34,7 +34,6 @@ #include "simCore/String/Utils.h" #include "simCore/Time/ClockImpl.h" #include "simData/DataStore.h" -#include "simVis/DBOptions.h" #include "simVis/Gl3Utils.h" #include "simVis/Registry.h" #include "simVis/SceneManager.h" diff --git a/SDK/simUtil/HudManager.cpp b/SDK/simUtil/HudManager.cpp index 3659fb50c..5ac8547b9 100644 --- a/SDK/simUtil/HudManager.cpp +++ b/SDK/simUtil/HudManager.cpp @@ -220,10 +220,11 @@ void HudTextAdapter::update_() osgText->setDrawMode(osgText::TextBase::TEXT); else { + // set draw mode first, other changes depend on it + osgText->setDrawMode(osgText::TextBase::FILLEDBOUNDINGBOX | osgText::TextBase::TEXT); // Turn on the bounding box, which disables Halo osgText->setBackdropType(osgText::Text::NONE); osgText->setBoundingBoxColor(backgroundColor_); - osgText->setDrawMode(osgText::TextBase::FILLEDBOUNDINGBOX | osgText::TextBase::TEXT); } } @@ -522,13 +523,13 @@ class HudManager::ResizeHandler : public osgGA::GUIEventHandler //------------------------------------------------------------------------------------------------------- -HudManager::HudManager(simVis::View* view) +HudManager::HudManager(osgViewer::View* view, osg::Group* parentNode) : renderLevel_(HUD_BASE_LEVEL), - view_(view) + view_(view), + parentNode_(parentNode) { group_ = new osg::Group(); - hud_ = view_->getOrCreateHUD(); - osg::StateSet* stateset = hud_->getOrCreateStateSet(); + osg::StateSet* stateset = parentNode_->getOrCreateStateSet(); simVis::setLighting(stateset, osg::StateAttribute::OFF); const osg::Viewport* vp = view->getCamera()->getViewport(); @@ -537,12 +538,12 @@ HudManager::HudManager(simVis::View* view) handler_ = new ResizeHandler(this); view_->addEventHandler(handler_); - hud_->addChild(group_); + parentNode_->addChild(group_); } HudManager::~HudManager() { - hud_->removeChild(group_); + parentNode_->removeChild(group_); view_->removeEventHandler(handler_); } @@ -625,11 +626,6 @@ void HudManager::resize_(int width, int height) (*it)->resize(windowWidth_, windowHeight_); } -osg::Camera* HudManager::hud() const -{ - return hud_.get(); -} - void HudManager::setRenderLevel(HudRenderLevel renderLevel) { group_->getOrCreateStateSet()->setRenderBinDetails(renderLevel, "RenderBin"); diff --git a/SDK/simUtil/HudManager.h b/SDK/simUtil/HudManager.h index 613c36a8e..c628fd23f 100644 --- a/SDK/simUtil/HudManager.h +++ b/SDK/simUtil/HudManager.h @@ -28,8 +28,9 @@ #include #include +#include #include "simCore/Common/Common.h" -#include "simVis/View.h" +#include "simVis/Types.h" namespace osgGA { class GUIEventHandler; } @@ -465,8 +466,12 @@ class SDKUTIL_EXPORT HudImage : public osg::Geode class SDKUTIL_EXPORT HudManager { public: - /** Constructor */ - HudManager(simVis::View* view); + /** + * Constructs a new HudManager. Passes in the View and the parent node for the HUD. For example: + * simVis::View* hudView = ...; + * HudManager* hudManager = new HudManager(hudView, hudView->getOrCreateHUD()); + */ + HudManager(osgViewer::View* view, osg::Group* parentNode); virtual ~HudManager(); /** @@ -548,9 +553,6 @@ class SDKUTIL_EXPORT HudManager /// Removes the specified image void removeImage(HudImage* hudImage); - /// Returns the current HUD - osg::Camera* hud() const; - /// Set the render level this HudManager should apply to all its hud items void setRenderLevel(HudRenderLevel renderLevel); @@ -569,8 +571,8 @@ class SDKUTIL_EXPORT HudManager osg::ref_ptr group_; ///< group node that holds all the hud text and images std::vector< osg::ref_ptr > textVector_; ///< The current overlay text std::vector< osg::ref_ptr > imageVector_; ///< The current overlay images - osg::observer_ptr view_; ///< The view for this manager - osg::observer_ptr hud_; ///< The HUD for this manager + osg::observer_ptr view_; ///< The view for this manager + osg::observer_ptr parentNode_; ///< The parent node where this manager adds itself to the scene osg::ref_ptr handler_; ///< The callback for window re-size int windowWidth_; ///< Save a copy of the window width int windowHeight_; ///< Save a copy of the window height diff --git a/SDK/simUtil/HudPositionManager.cpp b/SDK/simUtil/HudPositionManager.cpp index b7b4fe43a..c6a17118f 100644 --- a/SDK/simUtil/HudPositionManager.cpp +++ b/SDK/simUtil/HudPositionManager.cpp @@ -238,7 +238,7 @@ int HudPositionManager::setSize(const std::string& name, const osg::Vec2d& minXy class RepositionPixelsCallback::ResizeCallback : public osgGA::GUIEventHandler { public: - ResizeCallback(RepositionPixelsCallback* parent) + explicit ResizeCallback(RepositionPixelsCallback* parent) : windowSize_(0.0, 0.0), parent_(parent) { diff --git a/SDK/simUtil/LayerFactory.cpp b/SDK/simUtil/LayerFactory.cpp index d6eef5877..0248a5442 100644 --- a/SDK/simUtil/LayerFactory.cpp +++ b/SDK/simUtil/LayerFactory.cpp @@ -19,6 +19,7 @@ * disclose, or release this software. * */ +#include #include "osgEarth/ElevationLayer" #include "osgEarth/FeatureModelLayer" #include "osgEarth/GDAL" @@ -29,10 +30,13 @@ #include "simCore/String/Format.h" #include "simCore/String/Utils.h" #include "simVis/Constants.h" -#include "simVis/DBFormat.h" #include "simVis/Types.h" #include "simUtil/LayerFactory.h" +#ifdef SIM_HAVE_DB_SUPPORT +#include "simVis/DBFormat.h" +#endif + namespace simUtil { /** Default cache time of one year */ @@ -40,6 +44,11 @@ static const osgEarth::TimeSpan ONE_YEAR(365 * 86400); simVis::DBImageLayer* LayerFactory::newDbImageLayer(const std::string& fullPath) const { +#ifndef SIM_HAVE_DB_SUPPORT + // Likely developer error unintended + assert(0); + return NULL; +#else osgEarth::Config config; config.setReferrer(fullPath); @@ -54,6 +63,7 @@ simVis::DBImageLayer* LayerFactory::newDbImageLayer(const std::string& fullPath) layer->setCachePolicy(cachePolicy); return layer.release(); +#endif } osgEarth::MBTilesImageLayer* LayerFactory::newMbTilesImageLayer(const std::string& fullPath) const @@ -97,6 +107,11 @@ osgEarth::GDALImageLayer* LayerFactory::newGdalImageLayer(const std::string& ful simVis::DBElevationLayer* LayerFactory::newDbElevationLayer(const std::string& fullPath) const { +#ifndef SIM_HAVE_DB_SUPPORT + // Likely developer error unintended + assert(0); + return NULL; +#else osgEarth::Config config; config.setReferrer(fullPath); @@ -111,6 +126,7 @@ simVis::DBElevationLayer* LayerFactory::newDbElevationLayer(const std::string& f layer->setCachePolicy(cachePolicy); return layer.release(); +#endif } osgEarth::MBTilesElevationLayer* LayerFactory::newMbTilesElevationLayer(const std::string& fullPath) const @@ -202,7 +218,7 @@ ShapeFileLayerFactory::~ShapeFileLayerFactory() osgEarth::FeatureModelLayer* ShapeFileLayerFactory::load(const std::string& url) const { osg::ref_ptr layer = new osgEarth::FeatureModelLayer(); - configureOptions(url, layer); + configureOptions(url, layer.get()); if (layer->getStatus().isError()) { diff --git a/SDK/simUtil/LayerFactory.h b/SDK/simUtil/LayerFactory.h index 320e7620e..90b9d6a7a 100644 --- a/SDK/simUtil/LayerFactory.h +++ b/SDK/simUtil/LayerFactory.h @@ -58,14 +58,14 @@ namespace simUtil { class SDKUTIL_EXPORT LayerFactory { public: - /** Returns an image layer properly configured for DB layer. */ + /** Returns an image layer properly configured for DB layer. May return NULL if not configured with DB support. */ simVis::DBImageLayer* newDbImageLayer(const std::string& fullPath) const; /** Returns an image layer properly configured for MBTiles layer. */ osgEarth::MBTilesImageLayer* newMbTilesImageLayer(const std::string& fullPath) const; /** Returns an image layer properly configured for GDAL layer. */ osgEarth::GDALImageLayer* newGdalImageLayer(const std::string& fullPath) const; - /** Returns an elevation layer properly configured for DB layer. */ + /** Returns an elevation layer properly configured for DB layer. May return NULL if not configured with DB support. */ simVis::DBElevationLayer* newDbElevationLayer(const std::string& fullPath) const; /** Returns an elevation layer properly configured for MBTiles layer. */ osgEarth::MBTilesElevationLayer* newMbTilesElevationLayer(const std::string& fullPath) const; diff --git a/SDK/simUtil/MapScale.cpp b/SDK/simUtil/MapScale.cpp index 4591d523a..9aa07d804 100644 --- a/SDK/simUtil/MapScale.cpp +++ b/SDK/simUtil/MapScale.cpp @@ -137,7 +137,7 @@ MapScale::MapScale() // Create colors bgColorArray_ = new osg::Vec4Array(osg::Array::BIND_OVERALL, 1); bgColorArray_->setDataVariance(osg::Object::DYNAMIC); - backgroundGeom->setColorArray(bgColorArray_); + backgroundGeom->setColorArray(bgColorArray_.get()); // Note that setting the background color to 0.f alpha hides the background setBackgroundColor(osg::Vec4f(0.f, 0.f, 0.f, 0.f)); diff --git a/SDK/simUtil/StatusText.cpp b/SDK/simUtil/StatusText.cpp index a4b612a86..da7ae7c72 100644 --- a/SDK/simUtil/StatusText.cpp +++ b/SDK/simUtil/StatusText.cpp @@ -89,7 +89,12 @@ class StatusText::FrameEventHandler : public osgGA::GUIEventHandler { public: /** Constructor */ - explicit FrameEventHandler(simUtil::StatusText* parent) : parent_(parent) {} + explicit FrameEventHandler(simUtil::StatusText* parent) + : parent_(parent), + widthPx_(-1), + heightPx_(-1) + { + } /** Handles frame updates and returns false so other handlers can process as well */ bool handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa) { diff --git a/SDK/simVis.h b/SDK/simVis.h index 29949f19d..729c99af3 100644 --- a/SDK/simVis.h +++ b/SDK/simVis.h @@ -35,7 +35,9 @@ #include "simVis/BillboardAutoTransform.h" #include "simVis/BoxGraphic.h" #include "simVis/BoxZoomMouseHandler.h" +#include "simVis/BrightnessContrastColorFilter.h" #include "simVis/CentroidManager.h" +#include "simVis/ChromaKeyColorFilter.h" #include "simVis/ClassificationBanner.h" #include "simVis/ClockOptions.h" #include "simVis/Compass.h" @@ -43,7 +45,6 @@ #include "simVis/CustomRendering.h" #include "simVis/CylinderGeode.h" #include "simVis/CylinderStorage.h" -#include "simVis/DBOptions.h" #include "simVis/DisableDepthOnAlpha.h" #include "simVis/DynamicScaleTransform.h" #include "simVis/EarthManipulator.h" @@ -85,6 +86,7 @@ #include "simVis/InsetViewEventHandler.h" #include "simVis/LabelContentManager.h" #include "simVis/Laser.h" +#include "simVis/LayerRefreshCallback.h" #include "simVis/LobGroup.h" #include "simVis/LocalGrid.h" #include "simVis/Locator.h" @@ -97,7 +99,6 @@ #include "simVis/osgEarthVersion.h" #include "simVis/OverheadMode.h" #include "simVis/OverrideColor.h" -#include "simVis/LayerRefreshCallback.h" #include "simVis/Picker.h" #include "simVis/PlanetariumViewTool.h" #include "simVis/Platform.h" @@ -105,7 +106,6 @@ #include "simVis/PlatformFilter.h" #include "simVis/PlatformInertialTransform.h" #include "simVis/PlatformModel.h" -#include "simVis/PointSize.h" #include "simVis/PolygonStipple.h" #include "simVis/Popup.h" #include "simVis/Projector.h" @@ -147,6 +147,8 @@ #include "simVis/SphericalVolume.h" #include "simVis/TargetDelegation.h" #include "simVis/Text.h" +#include "simVis/TimeTicks.h" +#include "simVis/TimeTicksChunk.h" #include "simVis/Tool.h" #include "simVis/TrackChunkNode.h" #include "simVis/TrackHistory.h" diff --git a/SDK/simVis/Antenna.cpp b/SDK/simVis/Antenna.cpp index 3b938f50a..038c96569 100644 --- a/SDK/simVis/Antenna.cpp +++ b/SDK/simVis/Antenna.cpp @@ -543,6 +543,7 @@ void AntennaNode::render_() // draw right and left sides of pattern + // if hRange == M_TWOPI, then there is no left or right side to be drawn - face comprises the complete graphic if (hRange < M_TWOPI) { // draw right side of pattern diff --git a/SDK/simVis/AxisVector.cpp b/SDK/simVis/AxisVector.cpp index 630bc2efe..9cab305af 100644 --- a/SDK/simVis/AxisVector.cpp +++ b/SDK/simVis/AxisVector.cpp @@ -139,10 +139,8 @@ void AxisVector::setPositionOrientation(const osg::Vec3f& pos, const osg::Vec3f& void AxisVector::createAxisVectors_(osg::Geode* geode) const { - osgEarth::LineDrawable* line = NULL; - // draw x axis vector - line = new osgEarth::LineDrawable(GL_LINE_STRIP); + osgEarth::LineDrawable* line = new osgEarth::LineDrawable(GL_LINE_STRIP); line->setName("simVis::AxisVector"); line->allocate(AXIS_NUM_POINTS_PER_LINE_STRIP); VectorScaling::generatePoints(*line, osg::Vec3(), osg::X_AXIS); diff --git a/SDK/simVis/CMakeLists.txt b/SDK/simVis/CMakeLists.txt index a66118f21..18bc00cea 100644 --- a/SDK/simVis/CMakeLists.txt +++ b/SDK/simVis/CMakeLists.txt @@ -24,7 +24,6 @@ set(SHADER_FILES Picker.vert.glsl Picker.frag.glsl PlatformAzimElevViewTool.vert.glsl - PointSize.vert.glsl PolygonStipple.frag.glsl Projector.vert.glsl Projector.frag.glsl @@ -64,8 +63,6 @@ set(VIS_HEADERS_CORE ${VIS_INC}CustomRendering.h ${VIS_INC}CylinderGeode.h ${VIS_INC}CylinderStorage.h - ${VIS_INC}DBFormat.h - ${VIS_INC}DBOptions.h ${VIS_INC}DisableDepthOnAlpha.h ${VIS_INC}DynamicScaleTransform.h ${VIS_INC}EarthManipulator.h @@ -101,7 +98,6 @@ set(VIS_HEADERS_CORE ${VIS_INC}PlatformFilter.h ${VIS_INC}PlatformInertialTransform.h ${VIS_INC}PlatformModel.h - ${VIS_INC}PointSize.h ${VIS_INC}PolygonStipple.h ${VIS_INC}Popup.h ${VIS_INC}Projector.h @@ -122,6 +118,8 @@ set(VIS_HEADERS_CORE ${VIS_INC}SphericalVolume.h ${VIS_INC}TargetDelegation.h ${VIS_INC}Text.h + ${VIS_INC}TimeTicks.h + ${VIS_INC}TimeTicksChunk.h ${VIS_INC}Tool.h ${VIS_INC}TrackChunkNode.h ${VIS_INC}TrackHistory.h @@ -137,6 +135,20 @@ set(VIS_HEADERS_CORE ${CMAKE_CURRENT_BINARY_DIR}/include/simVis/osgEarthVersion.h ) +set(VIS_HEADERS_DB + ${VIS_INC}DBFormat.h + ${VIS_INC}DBOptions.h +) + +set(VIS_HEADERS_DB_PRIVATE + ${VIS_INC}DB/QSCommon.h + ${VIS_INC}DB/QSError.h + ${VIS_INC}DB/QSNodeID96.h + ${VIS_INC}DB/QSPosXYExtents.h + ${VIS_INC}DB/SQLiteDataBaseReadUtil.h + ${VIS_INC}DB/swapbytes.h +) + set(VIS_HEADERS_RFPROP ${VIS_INC}RFProp/ArepsLoader.h ${VIS_INC}RFProp/BearingProfileMap.h @@ -209,7 +221,6 @@ set(VIS_SOURCES_CORE ${VIS_SRC}CustomRendering.cpp ${VIS_SRC}CylinderGeode.cpp ${VIS_SRC}CylinderStorage.cpp - ${VIS_SRC}DBFormat.cpp ${VIS_SRC}DisableDepthOnAlpha.cpp ${VIS_SRC}DynamicScaleTransform.cpp ${VIS_SRC}EarthManipulator.cpp @@ -242,7 +253,6 @@ set(VIS_SOURCES_CORE ${VIS_SRC}PlatformFilter.cpp ${VIS_SRC}PlatformInertialTransform.cpp ${VIS_SRC}PlatformModel.cpp - ${VIS_SRC}PointSize.cpp ${VIS_SRC}PolygonStipple.cpp ${VIS_SRC}Popup.cpp ${VIS_SRC}Projector.cpp @@ -263,6 +273,8 @@ set(VIS_SOURCES_CORE ${VIS_SRC}SphericalVolume.cpp ${VIS_SRC}TargetDelegation.cpp ${VIS_SRC}Text.cpp + ${VIS_SRC}TimeTicks.cpp + ${VIS_SRC}TimeTicksChunk.cpp ${VIS_SRC}TrackChunkNode.cpp ${VIS_SRC}TrackHistory.cpp ${VIS_SRC}Utils.cpp @@ -275,6 +287,14 @@ set(VIS_SOURCES_CORE ${VIS_SRC}ViewManagerLogDbAdapter.cpp ) +set(VIS_SOURCES_DB + ${VIS_INC}DBFormat.cpp + ${VIS_INC}DB/QSError.cpp + ${VIS_INC}DB/QSNodeID96.cpp + ${VIS_INC}DB/QSPosXYExtents.cpp + ${VIS_INC}DB/SQLiteDataBaseReadUtil.cpp +) + set(VIS_SOURCES_RFPROP ${VIS_SRC}RFProp/ArepsLoader.cpp ${VIS_SRC}RFProp/BearingProfileMap.cpp @@ -363,8 +383,23 @@ source_group("" FILES ${VIS_ALL_HEADER}) set(VIS_HEADERS ${VIS_HEADERS_CORE} ${VIS_HEADERS_RFPROP} ${VIS_HEADERS_GOG}) set(VIS_SOURCES ${VIS_SOURCES_CORE} ${VIS_SOURCES_RFPROP} ${VIS_SOURCES_GOG} ${VIS_SHADER_FILES}) +# Add DB Format support to right MSVC folders and headers for installation +if(SIM_HAVE_DB_SUPPORT) + source_group(Headers FILES ${VIS_HEADERS_DB}) + source_group(Headers\\DB FILES ${VIS_HEADERS_DB_PRIVATE}) + source_group(Sources\\DB FILES ${VIS_SOURCES_DB}) + + list(APPEND VIS_HEADERS_CORE ${VIS_HEADERS_DB}) + list(APPEND VIS_HEADERS ${VIS_HEADERS_DB} ${VIS_HEADERS_DB_PRIVATE}) + list(APPEND VIS_SOURCES ${VIS_SOURCES_DB}) +endif() + # ---------------------------------------------------------------------- +# Avoid false MSVC 2017/2019 MSB8027 warning from Unity build on Utils.cpp and Angle.cpp +set_source_files_properties(${VIS_SRC}Utils.cpp ${VIS_SRC}GOG/Utils.cpp + PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) + set(VIS_PROJECT_FILES ${VIS_HEADERS} ${VIS_SOURCES} ${VIS_ALL_HEADER} ) @@ -378,7 +413,7 @@ endif() add_library(simVis ${STATIC_OR_SHARED} ${VIS_PROJECT_FILES}) set_target_properties(simVis PROPERTIES FOLDER "SIMDIS SDK" - PROJECT_LABEL "SIMDIS SDK - Vis" + PROJECT_LABEL "simVis" ) ApplySDKVersion(simVis) target_include_directories(simVis PUBLIC @@ -390,11 +425,23 @@ target_link_libraries(simVis PUBLIC simData simCore simNotify ${OSG_ALL_LIBDEPENDENCIES} OSGEARTH) target_link_libraries(simVis PRIVATE VSI::GL) +if(SQLITE3_FOUND) + target_link_libraries(simVis PRIVATE SQLITE3) +endif() + if(SIMVIS_SHARED) target_compile_definitions(simVis PRIVATE simVis_LIB_EXPORT_SHARED) else() target_compile_definitions(simVis PUBLIC simVis_LIB_EXPORT_STATIC) endif() +if(SIM_HAVE_DB_SUPPORT) + target_compile_definitions(simVis PRIVATE SIM_HAVE_DB_SUPPORT) + if(SDK_BIG_ENDIAN) + target_compile_definitions(simVis PRIVATE SIM_BIG_ENDIAN) + else() + target_compile_definitions(simVis PRIVATE SIM_LITTLE_ENDIAN) + endif() +endif() if(INSTALL_SIMDIS_SDK_LIBRARIES) vsi_install_export(simVis ${SIMDIS_SDK_VERSION_STRING} AnyNewerVersion) diff --git a/SDK/simVis/ChromaKeyColorFilter.cpp b/SDK/simVis/ChromaKeyColorFilter.cpp index 26c499e65..3eb5965e6 100644 --- a/SDK/simVis/ChromaKeyColorFilter.cpp +++ b/SDK/simVis/ChromaKeyColorFilter.cpp @@ -70,8 +70,7 @@ ChromaKeyColorFilter::ChromaKeyColorFilter(const osgEarth::Config& conf) val[2] = conf.value("b", 0.0); setColor(val); - float distance = 0.0f; - distance = conf.value("distance", 0.0f); + float distance = conf.value("distance", 0.0f); setDistance(distance); } diff --git a/SDK/simVis/Compass.cpp b/SDK/simVis/Compass.cpp index c7039538c..dc140a09f 100644 --- a/SDK/simVis/Compass.cpp +++ b/SDK/simVis/Compass.cpp @@ -19,6 +19,7 @@ * disclose, or release this software. * */ +#include "osgDB/ReadFile" #include "osgEarth/NodeUtils" #include "osgEarth/AnnotationUtils" #include "simCore/Calc/Angle.h" @@ -163,7 +164,7 @@ void CompassNode::initCompass_(const std::string& compassFilename) 0, // texture image unit 0.0, // heading image->s() / COMPASS_SIZE); // scale, down to 128x128 - + // Texture is likely GL_LUMINANCE or GL_LUMINANCE_ALPHA; fix it if so if (compass && compass->getStateSet()) { diff --git a/Plugins/OSGEarthDBDriver/include/QSCommon.h b/SDK/simVis/DB/QSCommon.h similarity index 95% rename from Plugins/OSGEarthDBDriver/include/QSCommon.h rename to SDK/simVis/DB/QSCommon.h index 6e1f6d744..a5c7d076b 100644 --- a/Plugins/OSGEarthDBDriver/include/QSCommon.h +++ b/SDK/simVis/DB/QSCommon.h @@ -19,8 +19,8 @@ * disclose, or release this software. * */ -#ifndef QS_COMMON_H -#define QS_COMMON_H +#ifndef SIMVIS_DB_QSCOMMON_H +#define SIMVIS_DB_QSCOMMON_H #include "simCore/Common/Common.h" @@ -47,4 +47,4 @@ namespace simVis_db static const RasterFormat SPLIT_TIFF = 14; } // namespace simVis_db -#endif /* QS_COMMON_H */ +#endif /* SIMVIS_DB_QSCOMMON_H */ diff --git a/Plugins/OSGEarthDBDriver/src/QSError.cpp b/SDK/simVis/DB/QSError.cpp similarity index 99% rename from Plugins/OSGEarthDBDriver/src/QSError.cpp rename to SDK/simVis/DB/QSError.cpp index ddff49188..77bc6ff69 100644 --- a/Plugins/OSGEarthDBDriver/src/QSError.cpp +++ b/SDK/simVis/DB/QSError.cpp @@ -71,7 +71,7 @@ static const char* QS_IS_EMPTY_FILENAME_STR = "Empty file static const char* QS_IS_SET_CREATION_FAILED_STR = "Attempted raster set creation failed"; static const char* QS_IS_NO_TIME_STAMP_STR = "Unable to obtain timestamp"; -const char* GetErrorString(const QsErrorType& errorValue) +const char* getErrorString(const QsErrorType& errorValue) { switch (errorValue) { diff --git a/Plugins/OSGEarthDBDriver/include/QSError.h b/SDK/simVis/DB/QSError.h similarity index 96% rename from Plugins/OSGEarthDBDriver/include/QSError.h rename to SDK/simVis/DB/QSError.h index e4d3c42d1..2461f8659 100644 --- a/Plugins/OSGEarthDBDriver/include/QSError.h +++ b/SDK/simVis/DB/QSError.h @@ -20,8 +20,8 @@ * */ -#ifndef QS_ERROR_H -#define QS_ERROR_H +#ifndef SIMVIS_DB_QSERROR_H +#define SIMVIS_DB_QSERROR_H #include @@ -74,8 +74,8 @@ namespace simVis_db static const QsErrorType QS_IS_NO_TIME_STAMP = 42; //=========================================================================== - const char* GetErrorString(const QsErrorType&); + const char* getErrorString(const QsErrorType&); } // namespace simVis_db -#endif /* QS_ERROR_H */ +#endif /* SIMVIS_DB_QSERROR_H */ diff --git a/Plugins/OSGEarthDBDriver/src/QSNodeID96.cpp b/SDK/simVis/DB/QSNodeID96.cpp similarity index 90% rename from Plugins/OSGEarthDBDriver/src/QSNodeID96.cpp rename to SDK/simVis/DB/QSNodeID96.cpp index ccaa33a02..05feac53a 100644 --- a/Plugins/OSGEarthDBDriver/src/QSNodeID96.cpp +++ b/SDK/simVis/DB/QSNodeID96.cpp @@ -132,7 +132,9 @@ QSNodeID96 QSNodeID96::operator>>(int numBitsToShift) const { returnValue.three_ = 0; returnValue.two_ = three_ >> (numBitsToShift-32); - returnValue.one_ = (two_ >> (numBitsToShift-32)) | (three_ << (64 - numBitsToShift)); + // Number of bits to shift the third field by. Separated into a local var to avoid cppCheck false positive + int thirdFieldBits = 64 - numBitsToShift; + returnValue.one_ = (two_ >> (numBitsToShift-32)) | (three_ << thirdFieldBits); } else if (numBitsToShift < 96) { @@ -187,7 +189,9 @@ QSNodeID96 QSNodeID96::operator<<(int numBitsToShift) const { returnValue.one_ = 0; returnValue.two_ = one_ << (numBitsToShift-32); - returnValue.three_ = (two_ << (numBitsToShift-32)) | (one_ >> (64 - numBitsToShift)); + // Number of bits to shift the third field by. Separated into a local var to avoid cppCheck false positive + int thirdFieldBits = 64 - numBitsToShift; + returnValue.three_ = (two_ << (numBitsToShift-32)) | (one_ >> thirdFieldBits); } else if (numBitsToShift < 96) { @@ -219,27 +223,27 @@ QSNodeID96 QSNodeID96::operator&(const QSNodeID96& value) const } //--------------------------------------------------------------------------- -void QSNodeID96::Pack(uint8_t* buffer) const +void QSNodeID96::pack(uint8_t* buffer) const { if (buffer == NULL) return; - bewrite(buffer, &three_); - bewrite(buffer + sizeof(three_), &two_); - bewrite(buffer + sizeof(three_) + sizeof(two_), &one_); + beWrite(buffer, &three_); + beWrite(buffer + sizeof(three_), &two_); + beWrite(buffer + sizeof(three_) + sizeof(two_), &one_); } //--------------------------------------------------------------------------- -void QSNodeID96::UnPack(const uint8_t* buffer) +void QSNodeID96::unpack(const uint8_t* buffer) { if (buffer == NULL) return; - beread(buffer, &three_); - beread(buffer + sizeof(three_), &two_); - beread(buffer + sizeof(three_) + sizeof(two_), &one_); + beRead(buffer, &three_); + beRead(buffer + sizeof(three_), &two_); + beRead(buffer + sizeof(three_) + sizeof(two_), &one_); } //--------------------------------------------------------------------------- -std::string QSNodeID96::FormatAsHex(bool bLeadingZeros) const +std::string QSNodeID96::formatAsHex(bool bLeadingZeros) const { std::string returnValue; diff --git a/Plugins/OSGEarthDBDriver/include/QSNodeID96.h b/SDK/simVis/DB/QSNodeID96.h similarity index 85% rename from Plugins/OSGEarthDBDriver/include/QSNodeID96.h rename to SDK/simVis/DB/QSNodeID96.h index 54d9cb6bc..d656ec1a1 100644 --- a/Plugins/OSGEarthDBDriver/include/QSNodeID96.h +++ b/SDK/simVis/DB/QSNodeID96.h @@ -19,9 +19,8 @@ * disclose, or release this software. * */ - -#ifndef QSNODEID96_H -#define QSNODEID96_H +#ifndef SIMVIS_DB_QSNODEID96_H +#define SIMVIS_DB_QSNODEID96_H #include #include "simCore/Common/Common.h" @@ -32,7 +31,7 @@ namespace simVis_db { public: QSNodeID96(); - QSNodeID96(const uint32_t& value); + explicit QSNodeID96(const uint32_t& value); ~QSNodeID96(); bool operator==(const QSNodeID96& value) const; @@ -45,10 +44,10 @@ namespace simVis_db QSNodeID96 operator<<(int numBitsToShift) const; QSNodeID96 operator&(const QSNodeID96& value) const; - int SizeOf() const {return 12;} - void Pack(uint8_t*) const; - void UnPack(const uint8_t*); - std::string FormatAsHex(bool bLeadingZeros=true) const; + int sizeOf() const {return 12;} + void pack(uint8_t*) const; + void unpack(const uint8_t*); + std::string formatAsHex(bool bLeadingZeros=true) const; protected: uint32_t one_; @@ -61,4 +60,4 @@ namespace simVis_db } // namespace simVis_db -#endif /* QSNODEID96_H */ +#endif /* SIMVIS_DB_QSNODEID96_H */ diff --git a/SDK/simVis/DB/QSPosXYExtents.cpp b/SDK/simVis/DB/QSPosXYExtents.cpp new file mode 100644 index 000000000..2372d828c --- /dev/null +++ b/SDK/simVis/DB/QSPosXYExtents.cpp @@ -0,0 +1,108 @@ +/* -*- mode: c++ -*- */ +/**************************************************************************** + ***** ***** + ***** Classification: UNCLASSIFIED ***** + ***** Classified By: ***** + ***** Declassify On: ***** + ***** ***** + **************************************************************************** + * + * + * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. + * EW Modeling & Simulation, Code 5773 + * 4555 Overlook Ave. + * Washington, D.C. 20375-5339 + * + * License for source code at https://simdis.nrl.navy.mil/License.aspx + * + * The U.S. Government retains all rights to use, duplicate, distribute, + * disclose, or release this software. + * + */ +#include +#include "simCore/Calc/Math.h" +#include "swapbytes.h" +#include "QSCommon.h" +#include "QSPosXYExtents.h" + +namespace simVis_db { + +//===================================================================================== +PosXPosYExtents::PosXPosYExtents(QsPosType minXIn, QsPosType maxXIn, QsPosType minYIn, QsPosType maxYIn) + : minX(minXIn), + maxX(maxXIn), + minY(minYIn), + maxY(maxYIn) +{ +} + +void PosXPosYExtents::initialize() +{ + minX = QS_MAX_LENGTH_UINT64; + maxX = 0; + minY = QS_MAX_LENGTH_UINT64; + maxY = 0; +} + +bool PosXPosYExtents::isValid() const +{ + return ((minX >= maxX) || (minY >= maxY)) ? false : true; +} + +void PosXPosYExtents::setAll(const PosXPosYExtents& given) +{ + minX = given.minX; + maxX = given.maxX; + minY = given.minY; + maxY = given.maxY; +} + +void PosXPosYExtents::setAll(const QsPosType& minXIn, const QsPosType& maxXIn, const QsPosType& minYIn, const QsPosType& maxYIn) +{ + minX = minXIn; + maxX = maxXIn; + minY = minYIn; + maxY = maxYIn; +} + +void PosXPosYExtents::pack(uint8_t* buffer) const +{ + if (buffer == NULL) + return; + beWrite(buffer, &minX); + beWrite(buffer + sizeof(minX), &maxX); + beWrite(buffer + sizeof(minX) + sizeof(maxX), &minY); + beWrite(buffer + sizeof(minX) + sizeof(maxX) + sizeof(minY), &maxY); +} + +void PosXPosYExtents::unpack(const uint8_t* buffer) +{ + if (buffer == NULL) + return; + beRead(buffer, &minX); + beRead(buffer + sizeof(minX), &maxX); + beRead(buffer + sizeof(minX) + sizeof(maxX), &minY); + beRead(buffer + sizeof(minX) + sizeof(maxX) + sizeof(minY), &maxY); +} + +//===================================================================================== +bool equalTo(const PosXPosYExtents& a, const PosXPosYExtents& b) +{ + if (a.minX != b.minX) return false; + if (a.maxX != b.maxX) return false; + if (a.minY != b.minY) return false; + if (a.maxY != b.maxY) return false; + return true; +} + +bool operator==(const PosXPosYExtents& a, const PosXPosYExtents& b) +{ + return simVis_db::equalTo(a, b); +} + +bool operator!=(const PosXPosYExtents& a, const PosXPosYExtents& b) +{ + return !simVis_db::equalTo(a, b); +} + +} diff --git a/Plugins/OSGEarthDBDriver/include/QSPosXYExtents.h b/SDK/simVis/DB/QSPosXYExtents.h similarity index 57% rename from Plugins/OSGEarthDBDriver/include/QSPosXYExtents.h rename to SDK/simVis/DB/QSPosXYExtents.h index e00f32be3..4e56faab0 100644 --- a/Plugins/OSGEarthDBDriver/include/QSPosXYExtents.h +++ b/SDK/simVis/DB/QSPosXYExtents.h @@ -20,19 +20,21 @@ * */ -#ifndef QS_POSXY_EXTENTS_H -#define QS_POSXY_EXTENTS_H +#ifndef SIMVIS_DB_POSXYEXTENTS_H +#define SIMVIS_DB_POSXYEXTENTS_H + +#include "simCore/Common/Common.h" namespace simVis_db { typedef uint64_t QsPosType; -#if defined Linux || defined Solaris - static const QsPosType gQsMaxLength = 4294967296LL; +#ifndef WIN32 + static const QsPosType QS_MAX_LENGTH_UINT64 = 4294967296LL; #else - static const QsPosType gQsMaxLength = 4294967296; + static const QsPosType QS_MAX_LENGTH_UINT64 = 4294967296; #endif - static const double gQsDMaxLength = 4294967296.0; + static const double QS_MAX_LENGTH_DOUBLE = 4294967296.0; /** A bounding rectangle of x/y extents */ struct PosXPosYExtents @@ -42,24 +44,21 @@ namespace simVis_db QsPosType minY; QsPosType maxY; - PosXPosYExtents(QsPosType minX=gQsMaxLength, QsPosType maxX=0, QsPosType minY=gQsMaxLength, QsPosType maxY=0); + PosXPosYExtents(QsPosType minX=QS_MAX_LENGTH_UINT64, QsPosType maxX=0, QsPosType minY=QS_MAX_LENGTH_UINT64, QsPosType maxY=0); /** Sets up invalid extents */ - void Initialize(); + void initialize(); /** Confirms validity of extents */ - bool Valid() const; + bool isValid() const; /** Sets the extents */ - void SetAll(const PosXPosYExtents& given); - void SetAll(const QsPosType& minX, const QsPosType& maxX, const QsPosType& minY, const QsPosType& maxY); + void setAll(const PosXPosYExtents& given); + void setAll(const QsPosType& minX, const QsPosType& maxX, const QsPosType& minY, const QsPosType& maxY); /** Packs/unpacks the extents into or from a buffer */ - void Pack(uint8_t*) const; - void UnPack(const uint8_t*); - - /** Prints the extents to the console */ - void Print(); + void pack(uint8_t*) const; + void unpack(const uint8_t*); }; //===================================================================================== @@ -67,19 +66,6 @@ namespace simVis_db bool operator==(const PosXPosYExtents& a, const PosXPosYExtents& b); bool operator!=(const PosXPosYExtents& a, const PosXPosYExtents& b); - //===================================================================================== - /** Updates extents such that the given x/y is within the extents */ - void UpdateExtents(const QsPosType& posX, const QsPosType& posY, PosXPosYExtents* extents); - - /** Copies an array of 6 extents */ - bool Copy6Extents(const PosXPosYExtents*, PosXPosYExtents*); - - /** Checks for any overlap between two rectangles */ - bool AnyOverlap(const PosXPosYExtents&, const PosXPosYExtents&); - - /** Checks if the given x/y is within the given extents */ - bool AnyOverlap(const QsPosType& posX, const QsPosType& posY, const PosXPosYExtents&); - } // Namespace simVis_db -#endif /* QS_POSXY_EXTENTS_H */ +#endif /* SIMVIS_DB_POSXYEXTENTS_H */ diff --git a/Plugins/OSGEarthDBDriver/src/SQLiteDataBaseReadUtil.cpp b/SDK/simVis/DB/SQLiteDataBaseReadUtil.cpp similarity index 86% rename from Plugins/OSGEarthDBDriver/src/SQLiteDataBaseReadUtil.cpp rename to SDK/simVis/DB/SQLiteDataBaseReadUtil.cpp index b6a26cc5b..6f89b4d3c 100644 --- a/Plugins/OSGEarthDBDriver/src/SQLiteDataBaseReadUtil.cpp +++ b/SDK/simVis/DB/SQLiteDataBaseReadUtil.cpp @@ -19,10 +19,9 @@ * disclose, or release this software. * */ - #include #include -#include "simCore/Time/Utils.h" +#include "simCore/Time/TimeClass.h" #include "swapbytes.h" #include "QSCommon.h" #include "SQLiteDataBaseReadUtil.h" @@ -33,7 +32,7 @@ namespace { //===================================================================================== template - void UnPackArray(SomeClass* givenArray, const uint8_t* givenBuffer, const uint32_t& numElements) + void unpackArray(SomeClass* givenArray, const uint8_t* givenBuffer, const uint32_t& numElements) { if ((givenArray == NULL) || (givenBuffer == NULL)) return; @@ -43,7 +42,7 @@ namespace for (i = 0; i < numElements; ++i) { memcpy(tmpBuffer, givenBuffer + (sizeof(SomeClass) * i), sizeof(SomeClass)); - givenArray[i].UnPack(tmpBuffer); + givenArray[i].unpack(tmpBuffer); } } @@ -111,7 +110,7 @@ SQLiteDataBaseReadUtil::SQLiteDataBaseReadUtil() tsInsertSetIdTimeValue_(11) { QSNodeId nodeID; - sizeOfIdBlob_ = sizeof(FaceIndexType) + nodeID.SizeOf(); + sizeOfIdBlob_ = sizeof(FaceIndexType) + nodeID.sizeOf(); // Creates the command for reading an image from a "texture set" table textureSetSelectFileCommand1_ = "SELECT * From \""; @@ -133,7 +132,7 @@ SQLiteDataBaseReadUtil::~SQLiteDataBaseReadUtil() } //------------------------------------------------------------------------------------- -QsErrorType SQLiteDataBaseReadUtil::OpenDataBaseFile(const std::string& dbFileName, +QsErrorType SQLiteDataBaseReadUtil::openDatabaseFile(const std::string& dbFileName, sqlite3** sqlite3Db, const int& flags) const { @@ -147,7 +146,7 @@ QsErrorType SQLiteDataBaseReadUtil::OpenDataBaseFile(const std::string& dbFileNa if ((errorCode == SQLITE_BUSY) || (errorCode == SQLITE_LOCKED)) return QS_IS_BUSY; - std::cerr << "OpenDataBaseFile sqlite3_open_v2 Error: " << dbFileName << "\n" << printExtendedErrorMessage(*sqlite3Db); + std::cerr << "openDatabaseFile sqlite3_open_v2 Error: " << dbFileName << "\n" << printExtendedErrorMessage(*sqlite3Db); return QS_IS_UNABLE_TO_OPEN_DB; } if (sqlite3_exec(*sqlite3Db, "PRAGMA CACHE_SIZE=100;", NULL, NULL, NULL) != SQLITE_OK) @@ -160,7 +159,7 @@ QsErrorType SQLiteDataBaseReadUtil::OpenDataBaseFile(const std::string& dbFileNa } //------------------------------------------------------------------------------------- -QsErrorType SQLiteDataBaseReadUtil::TsReadDataBuffer(sqlite3* sqlite3Db, +QsErrorType SQLiteDataBaseReadUtil::readDataBuffer(sqlite3* sqlite3Db, const std::string& dbFileName, const std::string& dataTableName, const FaceIndexType& faceIndex, @@ -183,13 +182,12 @@ QsErrorType SQLiteDataBaseReadUtil::TsReadDataBuffer(sqlite3* sqlite3Db, // opens the database bool localDb = false; - QsErrorType tmpReturnValue; if (sqlite3Db == NULL) { if (allowLocalDB == false) return QS_IS_DB_NOT_INITIALIZED; localDb = true; - tmpReturnValue = OpenDataBaseFile(dbFileName, &sqlite3Db, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX); + QsErrorType tmpReturnValue = openDatabaseFile(dbFileName, &sqlite3Db, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX); if (tmpReturnValue != QS_IS_OK) return tmpReturnValue; } @@ -206,7 +204,7 @@ QsErrorType SQLiteDataBaseReadUtil::TsReadDataBuffer(sqlite3* sqlite3Db, { if (displayErrorMessage && (returnValue != SQLITE_BUSY && returnValue != SQLITE_LOCKED)) { - std::cerr << "TsReadDataBuffer sqlite3_prepare_v2 Error(" << returnValue << "): " << dbFileName << "\n" << printExtendedErrorMessage(sqlite3Db); + std::cerr << "readDataBuffer sqlite3_prepare_v2 Error(" << returnValue << "): " << dbFileName << "\n" << printExtendedErrorMessage(sqlite3Db); } if (stmt != NULL) sqlite3_finalize(stmt); if (localDb) sqlite3_close(sqlite3Db); @@ -218,12 +216,12 @@ QsErrorType SQLiteDataBaseReadUtil::TsReadDataBuffer(sqlite3* sqlite3Db, // binds id uint8_t* idBlob = new uint8_t[sizeOfIdBlob_]; - bewrite(idBlob, &faceIndex); - nodeID.Pack(idBlob + sizeof(FaceIndexType)); + beWrite(idBlob, &faceIndex); + nodeID.pack(idBlob + sizeof(FaceIndexType)); returnValue = sqlite3_bind_blob(stmt, 1, idBlob, sizeOfIdBlob_, SQLITE_TRANSIENT); if (returnValue != SQLITE_OK && displayErrorMessage) { - std::cerr << "TsReadDataBuffer sqlite3_bind_blob Error(" << returnValue << "): " << dbFileName << "\n" << printExtendedErrorMessage(sqlite3Db); + std::cerr << "readDataBuffer sqlite3_bind_blob Error(" << returnValue << "): " << dbFileName << "\n" << printExtendedErrorMessage(sqlite3Db); } delete[] idBlob; @@ -255,8 +253,8 @@ QsErrorType SQLiteDataBaseReadUtil::TsReadDataBuffer(sqlite3* sqlite3Db, { if (displayErrorMessage) { - std::cerr << "TsReadDataBuffer sqlite3_step Error(" << returnValue << "): " << dbFileName << "\n"; - std::cerr << "not done (" << nodeID.FormatAsHex().c_str() << ") " << printExtendedErrorMessage(sqlite3Db); + std::cerr << "readDataBuffer sqlite3_step Error(" << returnValue << "): " << dbFileName << "\n"; + std::cerr << "not done (" << nodeID.formatAsHex().c_str() << ") " << printExtendedErrorMessage(sqlite3Db); } otherReturnValue = QS_IS_UNABLE_TO_READ_DATA_BUFFER; } @@ -267,14 +265,14 @@ QsErrorType SQLiteDataBaseReadUtil::TsReadDataBuffer(sqlite3* sqlite3Db, returnValue = sqlite3_close(sqlite3Db); if (returnValue != SQLITE_OK && displayErrorMessage) { - std::cerr << "TsReadDataBuffer localDb sqlite3_close Error(" << returnValue << "): " << dbFileName << "\n" << printExtendedErrorMessage(sqlite3Db); + std::cerr << "readDataBuffer localDb sqlite3_close Error(" << returnValue << "): " << dbFileName << "\n" << printExtendedErrorMessage(sqlite3Db); } } return otherReturnValue; } //------------------------------------------------------------------------------------- -QsErrorType SQLiteDataBaseReadUtil::TsGetSetFromListOfSetsTable(sqlite3* sqlite3Db, +QsErrorType SQLiteDataBaseReadUtil::getSetFromListOfSetsTable(sqlite3* sqlite3Db, const std::string& tableName, int& rasterFormat, int& pixelLength, @@ -300,7 +298,7 @@ QsErrorType SQLiteDataBaseReadUtil::TsGetSetFromListOfSetsTable(sqlite3* sqlite3 returnValue = sqlite3_prepare_v2(sqlite3Db, textureSetSelectCommand_.c_str(), static_cast(textureSetSelectCommand_.length()), &stmt, NULL); if (returnValue != SQLITE_OK) { - std::cerr << "TsGetSetFromListOfSetsTable sqlite3_prepare_v2 Error(" << returnValue << ")\n" << printExtendedErrorMessage(sqlite3Db); + std::cerr << "getSetFromListOfSetsTable sqlite3_prepare_v2 Error(" << returnValue << ")\n" << printExtendedErrorMessage(sqlite3Db); if (stmt != NULL) sqlite3_finalize(stmt); return QS_IS_PREPARE_ERROR; } @@ -309,14 +307,14 @@ QsErrorType SQLiteDataBaseReadUtil::TsGetSetFromListOfSetsTable(sqlite3* sqlite3 returnValue = sqlite3_bind_text(stmt, 1, tableName.c_str(), int(tableName.length()), SQLITE_TRANSIENT); if (returnValue != SQLITE_OK) { - std::cerr << "TsGetSetFromListOfSetsTable sqlite3_bind_text Error(" << returnValue << ")\n" << printExtendedErrorMessage(sqlite3Db); + std::cerr << "getSetFromListOfSetsTable sqlite3_bind_text Error(" << returnValue << ")\n" << printExtendedErrorMessage(sqlite3Db); } // executes the statement returnValue = sqlite3_step(stmt); if (returnValue > SQLITE_OK&& returnValue < SQLITE_ROW) { - std::cerr << "TsGetSetFromListOfSetsTable sqlite3_step Error(" << returnValue << ")\n" << printExtendedErrorMessage(sqlite3Db); + std::cerr << "getSetFromListOfSetsTable sqlite3_step Error(" << returnValue << ")\n" << printExtendedErrorMessage(sqlite3Db); } if (returnValue == SQLITE_ROW) @@ -326,7 +324,7 @@ QsErrorType SQLiteDataBaseReadUtil::TsGetSetFromListOfSetsTable(sqlite3* sqlite3 pixelLength = sqlite3_column_int(stmt, tsInsertSetIdPixelLength_ - 1); shallowLevel = sqlite3_column_int(stmt, tsInsertSetIdShallowestLevel_ - 1); deepLevel = sqlite3_column_int(stmt, tsInsertSetIdDeepestLevel_ - 1); - UnPackArray(tmpExtents, (const uint8_t*)sqlite3_column_blob(stmt, tsInsertSetIdExtents_ - 1), 6); + unpackArray(tmpExtents, (const uint8_t*)sqlite3_column_blob(stmt, tsInsertSetIdExtents_ - 1), 6); source = (const char*)sqlite3_column_text(stmt, tsInsertSetIdSource_ - 1); classification = (const char*)sqlite3_column_text(stmt, tsInsertSetIdClassification_ - 1); description = (const char*)sqlite3_column_text(stmt, tsInsertSetIdDescription_ - 1); @@ -340,9 +338,9 @@ QsErrorType SQLiteDataBaseReadUtil::TsGetSetFromListOfSetsTable(sqlite3* sqlite3 int frac = 0; // read TimeStamp data members from buffer - beread(buffer, &refYear); - beread(buffer + sizeof(refYear), &(secs)); - beread(buffer + sizeof(refYear) + sizeof(secs), &(frac)); + beRead(buffer, &refYear); + beRead(buffer + sizeof(refYear), &(secs)); + beRead(buffer + sizeof(refYear) + sizeof(secs), &(frac)); simCore::Seconds secsSinceRefYear(secs, frac); timeStamp.setTime(refYear, secsSinceRefYear); } diff --git a/Plugins/OSGEarthDBDriver/include/SQLiteDataBaseReadUtil.h b/SDK/simVis/DB/SQLiteDataBaseReadUtil.h similarity index 78% rename from Plugins/OSGEarthDBDriver/include/SQLiteDataBaseReadUtil.h rename to SDK/simVis/DB/SQLiteDataBaseReadUtil.h index db708e850..90a05ef92 100644 --- a/Plugins/OSGEarthDBDriver/include/SQLiteDataBaseReadUtil.h +++ b/SDK/simVis/DB/SQLiteDataBaseReadUtil.h @@ -20,34 +20,17 @@ * */ -#ifndef SQLITE_DATABASE_READ_UTIL_H -#define SQLITE_DATABASE_READ_UTIL_H +#ifndef SIMVIS_DB_SQLITEDATABASEREADUTIL_H +#define SIMVIS_DB_SQLITEDATABASEREADUTIL_H #include #include "sqlite3.h" -#include "simCore/Time/TimeClass.h" #include "QSCommon.h" #include "QSError.h" #include "QSNodeID96.h" #include "QSPosXYExtents.h" -// Temporary defines until we update sqlite -#ifndef SQLITE_OPEN_READONLY -#define SQLITE_OPEN_READONLY 0x00000001 -#define SQLITE_OPEN_READWRITE 0x00000002 -#define SQLITE_OPEN_CREATE 0x00000004 -#define SQLITE_OPEN_DELETEONCLOSE 0x00000008 -#define SQLITE_OPEN_EXCLUSIVE 0x00000010 -#define SQLITE_OPEN_MAIN_DB 0x00000100 -#define SQLITE_OPEN_TEMP_DB 0x00000200 -#define SQLITE_OPEN_TRANSIENT_DB 0x00000400 -#define SQLITE_OPEN_MAIN_JOURNAL 0x00000800 -#define SQLITE_OPEN_TEMP_JOURNAL 0x00001000 -#define SQLITE_OPEN_SUBJOURNAL 0x00002000 -#define SQLITE_OPEN_MASTER_JOURNAL 0x00004000 -#define SQLITE_OPEN_NOMUTEX 0x00008000 -#define SQLITE_OPEN_FULLMUTEX 0x00010000 -#endif /* SQLITE_OPEN_READONLY */ +namespace simCore { class TimeStamp; } namespace simVis_db { @@ -55,19 +38,8 @@ namespace simVis_db //===================================================================================== static const char* QS_DEFAULT_SET_TABLE_NAME = "default"; - static const char* SPLITTER_STRING_OUTPUTDB = "dbFile"; - static const char* SIMQS_CONFIG_TABLENAME_KEYWORD = "tableName"; static const char* QS_LIST_OF_TEXTURE_SETS_TABLE_NAME = "ListOfTextureSets"; static const char* QS_TSO_NAME_OF_TEXTURE_SET_TABLE = "nt"; - static const char* QS_TSO_OUTPUT_TYPE = "ot"; - static const char* QS_TSO_PIXEL_LENGTH = "pl"; - static const char* QS_TSO_SHALLOWEST_LEVEL = "sl"; - static const char* QS_TSO_DEEPEST_LEVEL = "dl"; - static const char* QS_TSO_EXTENTS = "ex"; - static const char* QS_TSO_SOURCE = "s"; - static const char* QS_TSO_CLASSIFICATION = "c"; - static const char* QS_TSO_DESCRIPTION = "ds"; - static const char* QS_TSO_TIME_SPECIFIED = "ts"; //===================================================================================== class SQLiteDataBaseReadUtil @@ -77,7 +49,7 @@ namespace simVis_db virtual ~SQLiteDataBaseReadUtil(); /** Opens a database file */ - QsErrorType OpenDataBaseFile(const std::string& dbFileName, + QsErrorType openDatabaseFile(const std::string& dbFileName, sqlite3** sqlite3Db, const int& flags) const; @@ -98,7 +70,7 @@ namespace simVis_db * @param[out] timeStamp Loads a time value, if there is a valid timeStamp on the file * @return Returns 0 on success, otherwise returns an error value mapped to QsErrorType. */ - QsErrorType TsGetSetFromListOfSetsTable(sqlite3* sqlite3Db, + QsErrorType getSetFromListOfSetsTable(sqlite3* sqlite3Db, const std::string& tableName, int& rasterFormat, int& pixelLength, @@ -125,7 +97,7 @@ namespace simVis_db * @param[in] displayErrorMessage Determines whether to display error messages to console when failing * @return An error value, mapped to QsErrorType */ - QsErrorType TsReadDataBuffer(sqlite3* sqlite3Db, + QsErrorType readDataBuffer(sqlite3* sqlite3Db, const std::string& dbFileName, const std::string& dataTableName, const FaceIndexType& faceIndex, @@ -159,4 +131,4 @@ namespace simVis_db } // namespace simVis_db -#endif /* SQLITE_DATABASE_READ_UTIL_H */ +#endif /* SIMVIS_DB_SQLITEDATABASEREADUTIL_H */ diff --git a/SDK/simVis/DB/swapbytes.h b/SDK/simVis/DB/swapbytes.h new file mode 100644 index 000000000..8992437d5 --- /dev/null +++ b/SDK/simVis/DB/swapbytes.h @@ -0,0 +1,125 @@ +/* -*- mode: c++ -*- */ +/**************************************************************************** + ***** ***** + ***** Classification: UNCLASSIFIED ***** + ***** Classified By: ***** + ***** Declassify On: ***** + ***** ***** + **************************************************************************** + * + * + * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. + * EW Modeling & Simulation, Code 5773 + * 4555 Overlook Ave. + * Washington, D.C. 20375-5339 + * + * License for source code at https://simdis.nrl.navy.mil/License.aspx + * + * The U.S. Government retains all rights to use, duplicate, distribute, + * disclose, or release this software. + * + */ +#ifndef SIMVIS_DB_SWAPBYTES_H +#define SIMVIS_DB_SWAPBYTES_H + +#include +#include +#include "simCore/Common/Common.h" + +// Try to guess endianness +#if !defined(SIM_LITTLE_ENDIAN) && !defined(SIM_BIG_ENDIAN) +#if defined(X86) || defined(ALPHA) || defined(__x86_64__) || defined(_WIN64) || defined(WIN32) +#define SIM_LITTLE_ENDIAN +#endif +#endif + +namespace simVis_db +{ + template + inline + void swapBytes(T *const value, const size_t nItems = 1) + { + for (size_t i = 0; i < nItems; ++i) + { + char *ptr = (char*)&value[i]; // Treat value as an array of bytes + const size_t size = sizeof(T); + const size_t halfSize = size >> 1; + const size_t end = size - 1; + for (size_t i = 0; i < halfSize; ++i) + std::swap(ptr[end - i], ptr[i]); + } + } + +#ifdef SIM_LITTLE_ENDIAN + + template + inline + void makeBigEndianImpl(T *const value) + { + swapBytes(value); + } + +#else + + template + inline + void makeBigEndianImpl(T *const value) + { + } + +#endif + + inline + void makeBigEndian(char *const val) { makeBigEndianImpl(val); } + inline + void makeBigEndian(int8_t *const val) { makeBigEndianImpl(val); } + inline + void makeBigEndian(uint8_t *const val) { makeBigEndianImpl(val); } + inline + void makeBigEndian(int16_t *const val) { makeBigEndianImpl(val); } + inline + void makeBigEndian(uint16_t *const val) { makeBigEndianImpl(val); } + inline + void makeBigEndian(int32_t *const val) { makeBigEndianImpl(val); } + inline + void makeBigEndian(uint32_t *const val) { makeBigEndianImpl(val); } + inline + void makeBigEndian(int64_t *const val) { makeBigEndianImpl(val); } + inline + void makeBigEndian(uint64_t *const val) { makeBigEndianImpl(val); } + inline + void makeBigEndian(float *const val) { makeBigEndianImpl(val); } + inline + void makeBigEndian(double *const val) { makeBigEndianImpl(val); } + + template + inline + void makeBigEndian(T *const value, const size_t nItems) + { + for (size_t i = 0; i < nItems; ++i) + { + makeBigEndian(&value[i]); + } + } + + template + inline + size_t beRead(const void *const stream, T *const val, const size_t nItems = 1) + { + memcpy(val, stream, nItems * sizeof(T)); + makeBigEndian(val, nItems); + return nItems; + } + + template + inline + size_t beWrite(void *const stream, const T *const val, const size_t nItems = 1) + { + memcpy(stream, val, nItems * sizeof(T)); + makeBigEndian((T *)stream, nItems); + return nItems; + } + +} // namespace simVis_db + +#endif /* SIMVIS_DB_SWAPBYTES_H */ diff --git a/SDK/simVis/DBFormat.cpp b/SDK/simVis/DBFormat.cpp index 0f6398436..c649b2765 100644 --- a/SDK/simVis/DBFormat.cpp +++ b/SDK/simVis/DBFormat.cpp @@ -20,415 +20,832 @@ * */ #include "osgDB/FileUtils" +#include "osgEarth/Cube" +#include "osgEarth/ImageToHeightFieldConverter" +#include "simCore/Calc/Math.h" +#include "simCore/Time/TimeClass.h" +#include "simVis/DBOptions.h" #include "simVis/DBFormat.h" +#include "simVis/DB/QSCommon.h" +#include "simVis/DB/swapbytes.h" +#include "simVis/DB/SQLiteDataBaseReadUtil.h" using namespace simVis; +using namespace simVis_db; //........................................................... -#if 0 -#include "simCore/Common/Common.h" -#include "simCore/Calc/MathConstants.h" -namespace simVis { namespace DB +namespace { - typedef int16_t LevelInt; - static const LevelInt QT_MIN_LEVEL = 0; - static const LevelInt QT_MAX_LEVEL = 32; - - typedef uint8_t ChildIndexInt; - static const ChildIndexInt QT_CHILD_NE = 0; - static const ChildIndexInt QT_CHILD_NW = 1; - static const ChildIndexInt QT_CHILD_SW = 2; - static const ChildIndexInt QT_CHILD_SE = 3; - - typedef uint8_t FaceIndexType; - static const FaceIndexType QsFaceIndexWW = 0; - static const FaceIndexType QsFaceIndexW = 1; - static const FaceIndexType QsFaceIndexE = 2; - static const FaceIndexType QsFaceIndexEE = 3; - static const FaceIndexType QsFaceIndexN = 4; - static const FaceIndexType QsFaceIndexS = 5; - - typedef float AltitudeDataType; - - static const int16_t MAX_NUM_READ_THREADS = 128; - - typedef uint64_t QsPosType; - -#if defined Linux || defined Solaris - static const QsPosType gQsMaxLength = 4294967296LL; - static const QsPosType gQsHalfMaxLength = 2147483648LL; -#else - static const QsPosType gQsMaxLength = 4294967296; - static const QsPosType gQsHalfMaxLength = 2147483648; -#endif - static const double gQsDMaxLength = 4294967296.0; - static const double gQsDHalfMaxLength = 2147483648.0; - static const double gQsLatLonDelta = M_PI_2 / gQsDMaxLength; - - /** A bounding rectangle of x/y extents */ - struct PosXPosYExtents + bool convertTileKeyToQsKey(const osgEarth::TileKey& key, FaceIndexType& out_faceIndex, QSNodeId& out_nodeId, + osg::Vec2d& out_fmin, osg::Vec2d& out_fmax) { - QsPosType minX; - QsPosType maxX; - QsPosType minY; - QsPosType maxY; + QSNodeId zero(0); + QSNodeId one(1); - PosXPosYExtents(QsPosType minX = gQsMaxLength, QsPosType maxX = 0, QsPosType minY = gQsMaxLength, QsPosType maxY = 0); + const unsigned int maxLevel = key.getLevelOfDetail(); - /** Sets up invalid extents */ - void Initialize(); + QSNodeId nodeId; - /** Confirms validity of extents */ - bool Valid() const; + osgEarth::TileKey pkey = key; - /** Sets the extents */ - void SetAll(const PosXPosYExtents& given); - void SetAll(const QsPosType& minX, const QsPosType& maxX, const QsPosType& minY, const QsPosType& maxY); + for (unsigned int i = 0; i < maxLevel; ++i) + { + const unsigned int plevel = pkey.getLevelOfDetail(); + const unsigned int level = plevel * 3; + const QSNodeId bit0 = one << level; + const QSNodeId bit1 = one << (level + 1); + const QSNodeId bit2 = one << (level + 2); - /** Packs/unpacks the extents into or from a buffer */ - void Pack(uint8_t*) const; - void UnPack(const uint8_t*); - void UnPackHexChars(const char*); + unsigned int tx, ty; + pkey.getTileXY(tx, ty); - /** Prints the extents to the console */ - void Print(); - }; + const int xoff = ((tx % 2) == 0) ? 0 : 1; + const int yoff = ((ty % 2) == 0) ? 0 : 1; - // --- SQLiteDataBaseReadUtil - - static const char* QS_TO_ID = "id"; - static const char* QS_DEFAULT_SET_TABLE_NAME = "default"; - static const char* SPLITTER_STRING_OUTPUTDB = "dbFile"; - static const char* SIMQS_CONFIG_TABLENAME_KEYWORD = "tableName"; - static const char* QS_LIST_OF_TEXTURE_SETS_TABLE_NAME = "ListOfTextureSets"; - static const char* QS_TSO_NAME_OF_TEXTURE_SET_TABLE = "nt"; - static const char* QS_TSO_OUTPUT_TYPE = "ot"; - static const char* QS_TSO_PIXEL_LENGTH = "pl"; - static const char* QS_TSO_SHALLOWEST_LEVEL = "sl"; - static const char* QS_TSO_DEEPEST_LEVEL = "dl"; - static const char* QS_TSO_EXTENTS = "ex"; - static const char* QS_TSO_SOURCE = "s"; - static const char* QS_TSO_CLASSIFICATION = "c"; - static const char* QS_TSO_DESCRIPTION = "ds"; - static const char* QS_TSO_TIME_SPECIFIED = "ts"; - - class SQLiteDataBaseReadUtil - { - public: - SQLiteDataBaseReadUtil(); - virtual ~SQLiteDataBaseReadUtil(); - - /** Opens a database file */ - QsErrorType OpenDataBaseFile(const std::string& dbFileName, - sqlite3** sqlite3Db, - const int& flags) const; - - /** - * Gets TextureSet information about a data table - * @param[in] sqlite3Db Pointer to a SQLite database object - * @param[in] tableName Name of the table to access within the given database - * The following are TextureSet creation options - * @param[out] rasterFormat Flag that determines how the texture image is drawn - * @param[out] pixelLength Tile size of the TextureSet - * @param[out] shallowLevel Minimum depth of the TextureSet - * @param[out] deepLevel Maximum depth of the TextureSet - * @param[out] tmpExtents Stores the TextureSet's X/Y extent values - * @param[out] source Name of the TextureSet's source file - * @param[out] classification Classification information of the loaded TextureSet - * @param[out] description Description of the loaded TextureSet - * @param[out] timeSpecified Whether or not a valid timeStamp was specified for the source file - * @param[out] timeStamp Loads a time value, if there is a valid timeStamp on the file - * @return Returns 0 on success, otherwise returns an error value mapped to QsErrorType. - */ - QsErrorType TsGetSetFromListOfSetsTable(sqlite3* sqlite3Db, - const std::string& tableName, - int& rasterFormat, - int& pixelLength, - int& shallowLevel, - int& deepLevel, - PosXPosYExtents tmpExtents[6], - std::string& source, - std::string& classification, - std::string& description, - bool& timeSpecified, - simCore::TimeStamp& timeStamp) const; - - /** - * Reads a node's data buffer from a sets table; caller is responsible for deleting buffer - * @param[in] sqlite3Db Pointer to a SQLite database object - * @param[in] dbFileName Name of a SQLite database file, used to fetch a database if sqlite3Db == NULL - * @param[in] dataTableName Name of the table to access within the given database - * @param[in] faceIndex Mapping to a face index/orientation, used to create a SQLite idBlob - * @param[in] nodeID Used to fill the idBlob - * @param[out] buffer Destination for data from the SQLite database - * @param[in, out] bufferSize Current max size of the buffer, will be changed if data to be copied is greater than max - * @param[out] currentRasterSize Size (bytes) of the data from the SQLite database - * @param[in] allowLocalDB Determines whether to fall back to a local database pointed to by dbFileName - * @param[in] displayErrorMessage Determines whether to display error messages to console when failing - * @return An error value, mapped to QsErrorType - */ - QsErrorType TsReadDataBuffer(sqlite3* sqlite3Db, - const std::string& dbFileName, - const std::string& dataTableName, - const FaceIndexType& faceIndex, - const QSNodeId& nodeID, - TextureDataType** buffer, - uint32_t* bufferSize, - uint32_t* currentRasterSize, - bool allowLocalDB, - bool displayErrorMessage = false) const; - protected: - int sizeOfIdBlob_; - - std::string textureSetSelectCommand_; - std::string textureSetSelectFileCommand1_; - std::string textureSetSelectFileCommand2_; - - // ids for inserting a "texture set" into a "list of texture sets" table - int tsInsertFileIdData_; - int tsInsertSetTextureSetName_; - int tsInsertSetIdRasterFormat_; - int tsInsertSetIdPixelLength_; - int tsInsertSetIdShallowestLevel_; - int tsInsertSetIdDeepestLevel_; - int tsInsertSetIdExtents_; - int tsInsertSetIdSource_; - int tsInsertSetIdClassification_; - int tsInsertSetIdDescription_; - int tsInsertSetIdTimeSpecified_; - int tsInsertSetIdTimeValue_; - }; -} } -#endif + if (xoff == 0 && yoff == 0) + { + nodeId |= bit1; + } + else if (xoff == 1 && yoff == 0) + { + nodeId |= bit0; + } + else if (xoff == 0 && yoff == 1) + { + nodeId |= bit0; + nodeId |= bit1; + } + else if (xoff == 1 && yoff == 1) + { + nodeId |= bit2; + } -#if 0 -//........................................................... + pkey = pkey.createParentKey(); + } -void DB::Options::readFrom(const osgEarth::Config& conf) -{ -} + out_faceIndex = osgEarth::Contrib::UnifiedCubeProfile::getFace(key); + out_nodeId = nodeId; -void DB::Options::writeTo(osgEarth::Config& conf) const -{ -} -//........................................................... + double xMin = key.getExtent().xMin(); + double yMin = key.getExtent().yMin(); + double xMax = key.getExtent().xMax(); + double yMax = key.getExtent().yMax(); + int face; -osgEarth::Status DB::Driver::open( - const osgEarth::URI& uri, - osg::ref_ptr& profile, - osgEarth::DataExtentList& dataExtents, - const osgDB::Options* readOptions) -{ - pathname_ = osgDB::findDataFile(uri, readOptions); + osgEarth::Contrib::CubeUtils::cubeToFace(xMin, yMin, xMax, yMax, face); - if (DB::OpenDataBaseFile(pathname_, &db_, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX) != QS_IS_OK) - { - db_ = NULL; - return Status::Error(Stringify() << "Failed to open DB file at " << options_.url()->full()); + out_fmin.set(xMin * QS_MAX_LENGTH_DOUBLE, yMin * QS_MAX_LENGTH_DOUBLE); + out_fmax.set(xMax * QS_MAX_LENGTH_DOUBLE, yMax * QS_MAX_LENGTH_DOUBLE); + + return true; } - else + + bool decompressZLIB(const char* input, int inputLen, std::string& output) { - QsErrorType err = DBUtil::TsGetSetFromListOfSetsTable( - db_, - "default", - rasterFormat_, - pixelLength_, - shallowLevel_, - deepLevel_, - extents_, - source_, - classification_, - description_, - timeSpecified_, - timeStamp_); + osgDB::BaseCompressor* comp = osgDB::Registry::instance()->getObjectWrapperManager()->findCompressor("zlib"); + std::string inString(input, inputLen); + std::istringstream inStream(inString); + return comp->decompress(inStream, output); + } - // Limit the deepLevel_ by the passed-in option - if (options_.deepestLevel().isSet()) + // Uses one of OSG's native ReaderWriter's to read image data from a buffer. + bool readNativeImage(osgDB::ReaderWriter* reader, const char* inBuf, int inBufLen, osg::ref_ptr& outImage) + { + std::string inString(inBuf, inBufLen); + std::istringstream inStream(inString); + osgDB::ReaderWriter::ReadResult result = reader->readImage(inStream); + outImage = result.getImage(); + if (result.error() || !outImage.valid()) { - deepLevel_ = simCore::sdkMin(deepLevel_, static_cast(options_.deepestLevel().get())); + return false; } + else + return true; + } - if (err != QS_IS_OK) + struct DBContext + { + DBContext() { - sqlite3_close(db_); + rasterFormat_ = SPLIT_UNKNOWN; + pixelLength_ = 128; + shallowLevel_ = 0; + deepLevel_ = 32; + timeSpecified_ = false; + timeStamp_ = simCore::INFINITE_TIME_STAMP; db_ = NULL; - return Status::Error(Stringify() << "Failed to read metadata for " << pathname_); } - // Set up as a unified cube: - profile = new osgEarth::UnifiedCubeProfile(); + int rasterFormat_; + int pixelLength_; + int shallowLevel_; + int deepLevel_; + bool timeSpecified_; + simCore::TimeStamp timeStamp_; + + std::string pathname_; + sqlite3* db_; + SQLiteDataBaseReadUtil dbUtil_; + PosXPosYExtents extents_[6]; + std::string source_; + std::string classification_; + std::string description_; + + osg::ref_ptr pngReader_; + osg::ref_ptr jpgReader_; + osg::ref_ptr tifReader_; + osg::ref_ptr rgbReader_; + + template + void makeImage(int size, GLenum internalFormat, GLenum pixelFormat, GLenum type, + std::string& buf, osg::ref_ptr& outImage) + { + unsigned char* data = new unsigned char[buf.length()]; + std::copy(buf.begin(), buf.end(), data); - // Lat/long extents (for debugging) - GeoExtent llex[6]; + // Be sure to cast here to get the right swap function: + makeBigEndian((T*)data, size * size); - // Tell the engine how deep the data actually goes: - for (unsigned int f = 0; f < 6; ++f) + outImage = new osg::Image(); + outImage->setImage(size, size, 1, internalFormat, pixelFormat, type, data, osg::Image::USE_NEW_DELETE); + } + + bool decodeRaster_(int rasterFormat, const char* inputBuffer, int inputBufferLen, osg::ref_ptr& outImage) { - if (extents_[f].minX < extents_[f].maxX && extents_[f].minY < extents_[f].maxY) + switch (rasterFormat) { - const double x0 = extents_[f].minX / gQsDMaxLength; - const double x1 = extents_[f].maxX / gQsDMaxLength; - const double y0 = extents_[f].minY / gQsDMaxLength; - const double y1 = extents_[f].maxY / gQsDMaxLength; + case SPLIT_5551_ZLIB_COMPRESS: // TESTED OK + case SPLIT_5551_GZ: // UNTESTED + { + std::string buf; + if (decompressZLIB(inputBuffer, inputBufferLen, buf)) + { + // Three component image (red, green, and blue channels) + makeImage( + pixelLength_, GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, buf, outImage); + return true; + } + } + break; - GeoExtent cubeEx(profile->getSRS(), f + x0, y0, f + x1, y1); + case SPLIT_8BIT_ZLIB_COMPRESS: // TESTED OK + case SPLIT_8BIT_GZ: // UNTESTED + { + std::string buf; + if (decompressZLIB(inputBuffer, inputBufferLen, buf)) + { + // Single component image (grayscale channel) + makeImage( + pixelLength_, GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE, buf, outImage); + return true; + } + } + break; + case SPLIT_INTA_ZLIB_COMPRESS: // TESTED OK + { + std::string buf; + if (decompressZLIB(inputBuffer, inputBufferLen, buf)) + { + // Two component image (grayscale w/alpha channel) + makeImage( + pixelLength_, GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, buf, outImage); + return true; + } + break; + } + case SPLIT_RGBA_ZLIB_COMPRESS: // TESTED OK + { + std::string buf; + if (decompressZLIB(inputBuffer, inputBufferLen, buf)) + { + // Four component image (red, green, blue and alpha channels) + makeImage( + pixelLength_, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, buf, outImage); + return true; + } + break; + } + case SPLIT_SGI_RGBA: // TESTED - OK (earthColorSGI.db) + { + if (rgbReader_.valid()) + return readNativeImage(rgbReader_.get(), inputBuffer, inputBufferLen, outImage); + else + OE_WARN << "SGI RGBA reader not available" << std::endl; + } + break; - // Transform to lat/long for the debugging msgs - cubeEx.transform(profile->getSRS()->getGeodeticSRS(), llex[f]); + case SPLIT_SGI_RGB: // UNTESTED + { + if (rgbReader_.valid()) + return readNativeImage(rgbReader_.get(), inputBuffer, inputBufferLen, outImage); + else + OE_WARN << "SGI RGB reader not available" << std::endl; + } + break; - dataExtents.push_back(DataExtent(cubeEx, shallowLevel_, deepLevel_)); + case SPLIT_FLOAT32_ZLIB_COMPRESS: // TESTED OK; + { + // Single-channel 32-bit float elevation data + std::string buf; + if (decompressZLIB(inputBuffer, inputBufferLen, buf)) + { + makeImage(pixelLength_, GL_LUMINANCE32F_ARB, GL_LUMINANCE, GL_FLOAT, buf, outImage); + return true; + } } - } + break; - // Set time value of image if a time was found in the db - if (timeStamp_ != simCore::INFINITE_TIME_STAMP) - { - DateTime osgTime(timeStamp_.secondsSinceRefYear(1970)); - // Set time as a user value since config is not editable from here - setUserValue("time", osgTime.asISO8601()); - } + case SPLIT_JPEG: // TESTED OK + { + if (jpgReader_.valid()) + return readNativeImage(jpgReader_.get(), inputBuffer, inputBufferLen, outImage); + else + OE_WARN << "JPEG reader not available" << std::endl; + } + break; - OE_INFO << LC - << "Table: " << uri.full() << std::endl - << " Raster format = " << rasterFormat_ << std::endl - << " Tile size = " << pixelLength_ << std::endl - << " Shallow level = " << shallowLevel_ << std::endl - << " Deep level = " << deepLevel_ << std::endl - << " QS Extents = " << std::endl - << " 0: " << extents_[0].minX << "," << extents_[0].minY << "," << extents_[0].maxX << "," << extents_[0].maxY << "(" << (llex[0].isValid() ? llex[0].toString() : "empty") << ")\n" - << " 1: " << extents_[1].minX << "," << extents_[1].minY << "," << extents_[1].maxX << "," << extents_[1].maxY << "(" << (llex[1].isValid() ? llex[1].toString() : "empty") << ")\n" - << " 2: " << extents_[2].minX << "," << extents_[2].minY << "," << extents_[2].maxX << "," << extents_[2].maxY << "(" << (llex[2].isValid() ? llex[2].toString() : "empty") << ")\n" - << " 3: " << extents_[3].minX << "," << extents_[3].minY << "," << extents_[3].maxX << "," << extents_[3].maxY << "(" << (llex[3].isValid() ? llex[3].toString() : "empty") << ")\n" - << " 4: " << extents_[4].minX << "," << extents_[4].minY << "," << extents_[4].maxX << "," << extents_[4].maxY << "(" << (llex[4].isValid() ? llex[4].toString() : "empty") << ")\n" - << " 5: " << extents_[5].minX << "," << extents_[5].minY << "," << extents_[5].maxX << "," << extents_[5].maxY << "(" << (llex[5].isValid() ? llex[5].toString() : "empty") << ")\n"; + case SPLIT_PNG: // UNTESTED + { + if (pngReader_.valid()) + return readNativeImage(pngReader_.get(), inputBuffer, inputBufferLen, outImage); + else + OE_WARN << "PNG reader not available" << std::endl; + } + break; - // Line up the native format readers: - pngReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/png"); - jpgReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/jpeg"); - tifReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/tiff"); - rgbReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/x-rgb"); - } - return STATUS_OK; -} + case SPLIT_TIFF: // UNTESTED + { + if (tifReader_.valid()) + return readNativeImage(tifReader_.get(), inputBuffer, inputBufferLen, outImage); + else + OE_WARN << "TIFF reader not available" << std::endl; + } + break; -osgEarth::ReadResult DB::Driver::read( - const osgEarth::TileKey& key, - osgEarth::ProgressCallback* progress, - const osgDB::Options* readOptions) const -{ - //todo + default: + { + OE_WARN << "Support for raster format " << rasterFormat << " not implemented" << std::endl; + } + break; + } + return false; + } + }; } -#endif //........................................................... +#undef LC +#define LC "[DBImageLayer] " + +REGISTER_OSGEARTH_LAYER(dbimage, simVis::DBImageLayer); + osgEarth::Config DBImageLayer::Options::getConfig() const { osgEarth::Config conf = osgEarth::ImageLayer::Options::getConfig(); - conf.merge(driver()->getConfig()); + conf.set("url", url()); + conf.set("deepest_level", deepestLevel()); return conf; } void DBImageLayer::Options::fromConfig(const osgEarth::Config& conf) { - driver() = simVis::DBOptions(conf); + conf.get("url", url()); + conf.get("deepest_level", deepestLevel()); } void DBImageLayer::setURL(const osgEarth::URI& value) { - options().driver()->url() = value; + options().url() = value; } const osgEarth::URI& DBImageLayer::getURL() const { - return options().driver()->url().get(); + return options().url().get(); } -void DBImageLayer::setDeepestLevel(const unsigned int& value) +void DBImageLayer::setDeepestLevel(unsigned int value) { - options().driver()->deepestLevel() = value; + options().deepestLevel() = value; } -const unsigned int& DBImageLayer::getDeepestLevel() const +unsigned int DBImageLayer::getDeepestLevel() const { - return options().driver()->deepestLevel().get(); + return options().deepestLevel().get(); } -osgEarth::TileSource* DBImageLayer::createTileSource() +void DBImageLayer::init() { - return osgEarth::TileSourceFactory::create(options().driver().get()); + osgEarth::ImageLayer::init(); + context_ = new DBContext(); } -void DBImageLayer::init() +DBImageLayer::~DBImageLayer() { - osgEarth::ImageLayer::init(); - setTileSourceExpected(true); + delete static_cast(context_); } osgEarth::Status DBImageLayer::openImplementation() { - return osgEarth::ImageLayer::openImplementation(); + osgEarth::Status parent = osgEarth::ImageLayer::openImplementation(); + if (parent.isError()) + return parent; + + DBContext& cx = *static_cast(context_); + + if (!options().url().isSet()) + return osgEarth::Status(osgEarth::Status::ConfigurationError, "Missing required URL"); + + cx.pathname_ = osgDB::findDataFile(options().url()->full(), getReadOptions()); + + if (cx.dbUtil_.openDatabaseFile(cx.pathname_, &cx.db_, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX) != simVis_db::QS_IS_OK) + { + cx.db_ = NULL; + return osgEarth::Status( + osgEarth::Status::ResourceUnavailable, + osgEarth::Stringify() << "Failed to open DB file at " << options().url()->full()); + } + else + { + QsErrorType err = cx.dbUtil_.getSetFromListOfSetsTable( + cx.db_, + "default", + cx.rasterFormat_, + cx.pixelLength_, + cx.shallowLevel_, + cx.deepLevel_, + cx.extents_, + cx.source_, + cx.classification_, + cx.description_, + cx.timeSpecified_, + cx.timeStamp_); + + // Limit the deepLevel_ by the passed-in option + if (options().deepestLevel().isSet()) + { + cx.deepLevel_ = simCore::sdkMin(cx.deepLevel_, static_cast(options().deepestLevel().get())); + } + + if (err != simVis_db::QS_IS_OK) + { + sqlite3_close(cx.db_); + cx.db_ = NULL; + return osgEarth::Status( + osgEarth::Status::ResourceUnavailable, + osgEarth::Stringify() << "Failed to read metadata for " << cx.pathname_); + } + + // Set up as a unified cube: + osgEarth::Profile* profile = new osgEarth::Contrib::UnifiedCubeProfile(); + // DB are expected to be wgs84, which Cube defaults to + setProfile(profile); + + // Lat/long extents (for debugging) + osgEarth::GeoExtent llex[6]; + + // Tell the engine how deep the data actually goes: + for (unsigned int f = 0; f < 6; ++f) + { + if (cx.extents_[f].minX < cx.extents_[f].maxX && cx.extents_[f].minY < cx.extents_[f].maxY) + { + const double x0 = cx.extents_[f].minX / QS_MAX_LENGTH_DOUBLE; + const double x1 = cx.extents_[f].maxX / QS_MAX_LENGTH_DOUBLE; + const double y0 = cx.extents_[f].minY / QS_MAX_LENGTH_DOUBLE; + const double y1 = cx.extents_[f].maxY / QS_MAX_LENGTH_DOUBLE; + + osgEarth::GeoExtent cubeEx(profile->getSRS(), f + x0, y0, f + x1, y1); + + // Transform to lat/long for the debugging msgs + cubeEx.transform(profile->getSRS()->getGeodeticSRS(), llex[f]); + + dataExtents().push_back(osgEarth::DataExtent(cubeEx, cx.shallowLevel_, cx.deepLevel_)); + } + } + + // Set time value of image if a time was found in the db + if (cx.timeStamp_ != simCore::INFINITE_TIME_STAMP) + { + const osgEarth::DateTime osgTime(cx.timeStamp_.secondsSinceRefYear(1970).getSeconds()); + // Set time as a user value since config is not editable from here + setUserValue("time", osgTime.asISO8601()); + } + + OE_INFO << LC + << "Table: " << options().url()->full() << std::endl + << " Raster format = " << cx.rasterFormat_ << std::endl + << " Tile size = " << cx.pixelLength_ << std::endl + << " Shallow level = " << cx.shallowLevel_ << std::endl + << " Deep level = " << cx.deepLevel_ << std::endl + << " QS Extents = " << std::endl + << " 0: " << cx.extents_[0].minX << "," << cx.extents_[0].minY << "," << cx.extents_[0].maxX << "," << cx.extents_[0].maxY << "(" << (llex[0].isValid() ? llex[0].toString() : "empty") << ")\n" + << " 1: " << cx.extents_[1].minX << "," << cx.extents_[1].minY << "," << cx.extents_[1].maxX << "," << cx.extents_[1].maxY << "(" << (llex[1].isValid() ? llex[1].toString() : "empty") << ")\n" + << " 2: " << cx.extents_[2].minX << "," << cx.extents_[2].minY << "," << cx.extents_[2].maxX << "," << cx.extents_[2].maxY << "(" << (llex[2].isValid() ? llex[2].toString() : "empty") << ")\n" + << " 3: " << cx.extents_[3].minX << "," << cx.extents_[3].minY << "," << cx.extents_[3].maxX << "," << cx.extents_[3].maxY << "(" << (llex[3].isValid() ? llex[3].toString() : "empty") << ")\n" + << " 4: " << cx.extents_[4].minX << "," << cx.extents_[4].minY << "," << cx.extents_[4].maxX << "," << cx.extents_[4].maxY << "(" << (llex[4].isValid() ? llex[4].toString() : "empty") << ")\n" + << " 5: " << cx.extents_[5].minX << "," << cx.extents_[5].minY << "," << cx.extents_[5].maxX << "," << cx.extents_[5].maxY << "(" << (llex[5].isValid() ? llex[5].toString() : "empty") << ")\n"; + + // Line up the native format readers: + cx.pngReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/png"); + cx.jpgReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/jpeg"); + cx.tifReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/tiff"); + cx.rgbReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/x-rgb"); + } + return osgEarth::Status::OK(); } osgEarth::GeoImage DBImageLayer::createImageImplementation(const osgEarth::TileKey& key, osgEarth::ProgressCallback* progress) const { - //todo - return osgEarth::ImageLayer::createImageImplementation(key, progress); + DBContext& cx = *static_cast(context_); + + if (!cx.db_) + return osgEarth::GeoImage::INVALID; + + osg::ref_ptr result; + + // Convert osgEarth::TileKey into a QuadKeyID + FaceIndexType faceId; + QSNodeId nodeId; + osg::Vec2d tileMin; + osg::Vec2d tileMax; // Tile extents in QS units + convertTileKeyToQsKey(key, faceId, nodeId, tileMin, tileMax); + + if (!cx.extents_[faceId].isValid()) + { + // no data on this face? return nothing + return osgEarth::GeoImage::INVALID; + } + + if (key.getLevelOfDetail() > static_cast(cx.deepLevel_)) + { + // Hopefully this doesn't happen since we called setMaxDataLevel, but you never know + return osgEarth::GeoImage::INVALID; + } + + // Query the database + TextureDataType* buf = NULL; + uint32_t bufSize = 0; + uint32_t currentRasterSize = 0; + + QsErrorType err = cx.dbUtil_.readDataBuffer( + cx.db_, + cx.pathname_, + "default", + faceId, + nodeId, + &buf, + &bufSize, + ¤tRasterSize, + false, true); // AllowLocalDB: no, we created it ourselves + + if (err == simVis_db::QS_IS_OK) + { + if (currentRasterSize > 0) + { + if (cx.decodeRaster_(cx.rasterFormat_, (const char*)buf, currentRasterSize, result)) + { + // If result is 1x1, skip border processing + if (result->s() >= 1 && result->t() >= 1) + { + const unsigned int resultS = static_cast(result->s()); + const unsigned int resultT = static_cast(result->t()); + + // Tile width and height in QS units: + const double tileWidth = tileMax.x() - tileMin.x(); + const double tileHeight = tileMax.y() - tileMin.y(); + + const double qppx = 0.0; + const double qppy = 0.0; + const double xMin = cx.extents_[faceId].minX + qppx; + const double xMax = cx.extents_[faceId].maxX - qppx; + const double yMin = cx.extents_[faceId].minY + qppy; + const double yMax = cx.extents_[faceId].maxY - qppy; + + osgEarth::ImageUtils::PixelReader read(result.get()); + osgEarth::ImageUtils::PixelWriter write(result.get()); + + // Write "no data" to all pixels outside the reported extent. + const double colw = tileWidth / (resultS - 1); + const double rowh = tileHeight / (resultT - 1); + + for (unsigned int row = 0; row < resultS; ++row) + { + const double y = tileMin.y() + row * rowh; + + for (unsigned int col = 0; col < resultT; ++col) + { + const double x = tileMin.x() + col * colw; + + if (x < xMin || x > xMax || y < yMin || y > yMax) + { + osg::Vec4f pixel = read(col, row); + pixel.a() = 0.0f; + write(pixel, col, row); + } + } + } + } + } + else + { + OE_WARN << "Image decode failed for key " << key.str() << std::endl; + } + } + else + { + // Raster size of 0 means no tile in the db + //OE_DEBUG << "No image in the database for key " << key->str() << std::endl; + result = NULL; + } + } + else + { + std::cerr << simVis_db::getErrorString(err) << std::endl; + OE_WARN << "Failed to read image from " << key.str() << std::endl; + } + + delete[] buf; + + return osgEarth::GeoImage(result.release(), key.getExtent()); } //........................................................... +#undef LC +#define LC "[DBElevationLayer] " + +REGISTER_OSGEARTH_LAYER(dbelevation, simVis::DBElevationLayer); + osgEarth::Config DBElevationLayer::Options::getConfig() const { osgEarth::Config conf = osgEarth::ElevationLayer::Options::getConfig(); - conf.merge(driver()->getConfig()); + conf.set("url", url()); + conf.set("deepest_level", deepestLevel()); return conf; } void DBElevationLayer::Options::fromConfig(const osgEarth::Config& conf) { - driver() = simVis::DBOptions(conf); + conf.get("url", url()); + conf.get("deepest_level", deepestLevel()); } void DBElevationLayer::setURL(const osgEarth::URI& value) { - options().driver()->url() = value; + options().url() = value; } const osgEarth::URI& DBElevationLayer::getURL() const { - return options().driver()->url().get(); + return options().url().get(); } -void DBElevationLayer::setDeepestLevel(const unsigned int& value) +void DBElevationLayer::setDeepestLevel(unsigned int value) { - options().driver()->deepestLevel() = value; + options().deepestLevel() = value; } -const unsigned int& DBElevationLayer::getDeepestLevel() const +unsigned int DBElevationLayer::getDeepestLevel() const { - return options().driver()->deepestLevel().get(); + return options().deepestLevel().get(); } -osgEarth::TileSource* DBElevationLayer::createTileSource() +void DBElevationLayer::init() { - return osgEarth::TileSourceFactory::create(options().driver().get()); + osgEarth::ElevationLayer::init(); + context_ = new DBContext(); } -void DBElevationLayer::init() +DBElevationLayer::~DBElevationLayer() { - osgEarth::ElevationLayer::init(); - setTileSourceExpected(true); + delete static_cast(context_); } osgEarth::Status DBElevationLayer::openImplementation() { - return osgEarth::ElevationLayer::openImplementation(); + osgEarth::Status parent = osgEarth::ElevationLayer::openImplementation(); + if (parent.isError()) + return parent; + + + DBContext& cx = *static_cast(context_); + + if (!options().url().isSet()) + return osgEarth::Status(osgEarth::Status::ConfigurationError, "Missing required URL"); + + cx.pathname_ = osgDB::findDataFile(options().url()->full(), getReadOptions()); + + if (cx.dbUtil_.openDatabaseFile(cx.pathname_, &cx.db_, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX) != simVis_db::QS_IS_OK) + { + cx.db_ = NULL; + return osgEarth::Status( + osgEarth::Status::ResourceUnavailable, + osgEarth::Stringify() << "Failed to open DB file at " << options().url()->full()); + } + else + { + QsErrorType err = cx.dbUtil_.getSetFromListOfSetsTable( + cx.db_, + "default", + cx.rasterFormat_, + cx.pixelLength_, + cx.shallowLevel_, + cx.deepLevel_, + cx.extents_, + cx.source_, + cx.classification_, + cx.description_, + cx.timeSpecified_, + cx.timeStamp_); + + // Limit the deepLevel_ by the passed-in option + if (options().deepestLevel().isSet()) + { + cx.deepLevel_ = simCore::sdkMin(cx.deepLevel_, static_cast(options().deepestLevel().get())); + } + + if (err != simVis_db::QS_IS_OK) + { + sqlite3_close(cx.db_); + cx.db_ = NULL; + return osgEarth::Status( + osgEarth::Status::ResourceUnavailable, + osgEarth::Stringify() << "Failed to read metadata for " << cx.pathname_); + } + + // Set up as a unified cube: + osgEarth::Profile* profile = new osgEarth::Contrib::UnifiedCubeProfile(); + // DB are expected to be wgs84, which Cube defaults to + setProfile(profile); + + // Lat/long extents (for debugging) + osgEarth::GeoExtent llex[6]; + + // Tell the engine how deep the data actually goes: + for (unsigned int f = 0; f < 6; ++f) + { + if (cx.extents_[f].minX < cx.extents_[f].maxX && cx.extents_[f].minY < cx.extents_[f].maxY) + { + const double x0 = cx.extents_[f].minX / QS_MAX_LENGTH_DOUBLE; + const double x1 = cx.extents_[f].maxX / QS_MAX_LENGTH_DOUBLE; + const double y0 = cx.extents_[f].minY / QS_MAX_LENGTH_DOUBLE; + const double y1 = cx.extents_[f].maxY / QS_MAX_LENGTH_DOUBLE; + + osgEarth::GeoExtent cubeEx(profile->getSRS(), f + x0, y0, f + x1, y1); + + // Transform to lat/long for the debugging msgs + cubeEx.transform(profile->getSRS()->getGeodeticSRS(), llex[f]); + + dataExtents().push_back(osgEarth::DataExtent(cubeEx, cx.shallowLevel_, cx.deepLevel_)); + } + } + + // Set time value of image if a time was found in the db + if (cx.timeStamp_ != simCore::INFINITE_TIME_STAMP) + { + const osgEarth::DateTime osgTime(cx.timeStamp_.secondsSinceRefYear(1970).getSeconds()); + // Set time as a user value since config is not editable from here + setUserValue("time", osgTime.asISO8601()); + } + + OE_INFO << LC + << "Table: " << options().url()->full() << std::endl + << " Raster format = " << cx.rasterFormat_ << std::endl + << " Tile size = " << cx.pixelLength_ << std::endl + << " Shallow level = " << cx.shallowLevel_ << std::endl + << " Deep level = " << cx.deepLevel_ << std::endl + << " QS Extents = " << std::endl + << " 0: " << cx.extents_[0].minX << "," << cx.extents_[0].minY << "," << cx.extents_[0].maxX << "," << cx.extents_[0].maxY << "(" << (llex[0].isValid() ? llex[0].toString() : "empty") << ")\n" + << " 1: " << cx.extents_[1].minX << "," << cx.extents_[1].minY << "," << cx.extents_[1].maxX << "," << cx.extents_[1].maxY << "(" << (llex[1].isValid() ? llex[1].toString() : "empty") << ")\n" + << " 2: " << cx.extents_[2].minX << "," << cx.extents_[2].minY << "," << cx.extents_[2].maxX << "," << cx.extents_[2].maxY << "(" << (llex[2].isValid() ? llex[2].toString() : "empty") << ")\n" + << " 3: " << cx.extents_[3].minX << "," << cx.extents_[3].minY << "," << cx.extents_[3].maxX << "," << cx.extents_[3].maxY << "(" << (llex[3].isValid() ? llex[3].toString() : "empty") << ")\n" + << " 4: " << cx.extents_[4].minX << "," << cx.extents_[4].minY << "," << cx.extents_[4].maxX << "," << cx.extents_[4].maxY << "(" << (llex[4].isValid() ? llex[4].toString() : "empty") << ")\n" + << " 5: " << cx.extents_[5].minX << "," << cx.extents_[5].minY << "," << cx.extents_[5].maxX << "," << cx.extents_[5].maxY << "(" << (llex[5].isValid() ? llex[5].toString() : "empty") << ")\n"; + + // Line up the native format readers: + cx.pngReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/png"); + cx.jpgReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/jpeg"); + cx.tifReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/tiff"); + cx.rgbReader_ = osgDB::Registry::instance()->getReaderWriterForMimeType("image/x-rgb"); + } + return osgEarth::Status::OK(); } osgEarth::GeoHeightField DBElevationLayer::createHeightFieldImplementation(const osgEarth::TileKey& key, osgEarth::ProgressCallback* progress) const { - //todo - return osgEarth::ElevationLayer::createHeightFieldImplementation(key, progress); + DBContext& cx = *static_cast(context_); + + if (!cx.db_) + return osgEarth::GeoHeightField::INVALID; + + osg::ref_ptr result; + + // Convert osgEarth::TileKey into a QuadKeyID + FaceIndexType faceId; + QSNodeId nodeId; + osg::Vec2d tileMin; + osg::Vec2d tileMax; // Tile extents in QS units + convertTileKeyToQsKey(key, faceId, nodeId, tileMin, tileMax); + + if (!cx.extents_[faceId].isValid()) + { + // If there is no data on that face, return nothing. + return osgEarth::GeoHeightField::INVALID; + } + + // Query the database + TextureDataType* buf = NULL; + uint32_t bufSize = 0; + uint32_t currentRasterSize = 0; + + QsErrorType err = cx.dbUtil_.readDataBuffer( + cx.db_, + cx.pathname_, + "default", + faceId, + nodeId, + &buf, + &bufSize, + ¤tRasterSize, + false); // AllowLocalDB: no, we created it ourselves + + if (err == simVis_db::QS_IS_OK) + { + if (currentRasterSize > 0) + { + osg::ref_ptr image; + if (cx.decodeRaster_(cx.rasterFormat_, (const char*)buf, currentRasterSize, image)) + { + + // SIMDIS .db elevation data is y-inverted: + image->flipVertical(); + + osgEarth::ImageToHeightFieldConverter i2h; + result = i2h.convert(image.get()); + + // If result is 1x1, skip border processing + if (result->getNumColumns() >= 1 && result->getNumRows() >= 1) + { + // Tile width and height in QS units: + const double tileWidth = tileMax.x() - tileMin.x(); + const double tileHeight = tileMax.y() - tileMin.y(); + + /** + * DB data contains a one-pixel border with undefined data. That border falls + * within the reported extents. We have to fill that with "NO DATA". + * First, calculate the size of a pixel in QS units for this tile: + */ + const double qppx = tileWidth / cx.pixelLength_; + const double qppy = tileHeight / cx.pixelLength_; + + /** + * Adjust the reported extents to remove the border. + * NOTE: This will fail in the (rare?) edge case in which a data extent falls + * exactly on a cube-face boundary. Ignore that for now. + */ + const double xMin = cx.extents_[faceId].minX + qppx; + const double xMax = cx.extents_[faceId].maxX - qppx; + const double yMin = cx.extents_[faceId].minY + qppy; + const double yMax = cx.extents_[faceId].maxY - qppy; + + // Write "no data" to all pixels outside the reported extent. + const double colWidth = tileWidth / (result->getNumColumns() - 1); + const double rowHeight = tileHeight / (result->getNumRows() - 1); + + for (unsigned int row = 0; row < result->getNumRows(); ++row) + { + const double y = tileMin.y() + row * rowHeight; + + for (unsigned int col = 0; col < result->getNumColumns(); ++col) + { + const double x = tileMin.x() + col * colWidth; + + if (x < xMin || x > xMax || y < yMin || y > yMax) + { + result->setHeight(col, row, NO_DATA_VALUE); + } + } + } + } + } + else + { + OE_WARN << "Heightfield decode failed for key " << key.str() << std::endl; + } + } + else + { + // Raster size of 0 means no tile in the db + result = NULL; + } + } + else + { + OE_WARN << "Failed to read heightfield from " << key.str() << std::endl; + } + + delete[] buf; + + return osgEarth::GeoHeightField(result.release(), key.getExtent()); } diff --git a/SDK/simVis/DBFormat.h b/SDK/simVis/DBFormat.h index a35b85019..bcde4bee9 100644 --- a/SDK/simVis/DBFormat.h +++ b/SDK/simVis/DBFormat.h @@ -23,13 +23,10 @@ #define SIMVIS_DBFORMAT_H #include -#include "osgEarth/ImageLayer" -#include "osgEarth/ElevationLayer" +#include "osgEarth/TileSourceImageLayer" +#include "osgEarth/TileSourceElevationLayer" #include "osgEarth/URI" #include "simCore/Common/Common.h" -#include "simCore/Common/Export.h" -#include "simCore/Time/TimeClass.h" -#include "simVis/DBOptions.h" namespace simVis { @@ -43,7 +40,8 @@ class SDKVIS_EXPORT DBImageLayer : public osgEarth::ImageLayer class SDKVIS_EXPORT Options : public osgEarth::ImageLayer::Options { public: META_LayerOptions(simVis, Options, osgEarth::ImageLayer::Options); - OE_OPTION(simVis::DBOptions, driver); + OE_OPTION(osgEarth::URI, url); + OE_OPTION(unsigned int, deepestLevel); virtual osgEarth::Config getConfig() const; private: void fromConfig(const osgEarth::Config&); @@ -58,8 +56,8 @@ class SDKVIS_EXPORT DBImageLayer : public osgEarth::ImageLayer const osgEarth::URI& getURL() const; /// Maximum level to use in DB file - void setDeepestLevel(const unsigned int& value); - const unsigned int& getDeepestLevel() const; + void setDeepestLevel(unsigned int value); + unsigned int getDeepestLevel() const; public: // Layer @@ -74,11 +72,12 @@ class SDKVIS_EXPORT DBImageLayer : public osgEarth::ImageLayer /// Called by constructors virtual void init(); - /// Create and return the underlying TileSource - virtual osgEarth::TileSource* createTileSource(); - /// Destructor - virtual ~DBImageLayer() { } + virtual ~DBImageLayer(); + +private: + /// DBContext instance, a struct that maintains per-DB parameters and persistent data + void* context_; }; /** @@ -90,7 +89,8 @@ class SDKVIS_EXPORT DBElevationLayer : public osgEarth::ElevationLayer class SDKVIS_EXPORT Options : public osgEarth::ElevationLayer::Options { public: META_LayerOptions(simVis, Options, osgEarth::ElevationLayer::Options); - OE_OPTION(simVis::DBOptions, driver); + OE_OPTION(osgEarth::URI, url); + OE_OPTION(unsigned int, deepestLevel); virtual osgEarth::Config getConfig() const; private: void fromConfig(const osgEarth::Config&); @@ -104,8 +104,8 @@ class SDKVIS_EXPORT DBElevationLayer : public osgEarth::ElevationLayer const osgEarth::URI& getURL() const; /// Maximum level to use in DB file - void setDeepestLevel(const unsigned int& value); - const unsigned int& getDeepestLevel() const; + void setDeepestLevel(unsigned int value); + unsigned int getDeepestLevel() const; public: // Layer @@ -117,14 +117,15 @@ class SDKVIS_EXPORT DBElevationLayer : public osgEarth::ElevationLayer protected: // Layer - /// Called by constructors + /// Called by constructors virtual void init(); - /// Create and return the underlying TileSource - virtual osgEarth::TileSource* createTileSource(); - /// Destructor - virtual ~DBElevationLayer() { } + virtual ~DBElevationLayer(); + +private: + /// DBContext instance, a struct that maintains per-DB parameters and persistent data + void* context_; }; } diff --git a/SDK/simVis/DBOptions.h b/SDK/simVis/DBOptions.h index d7d8279ee..4f47c02f7 100644 --- a/SDK/simVis/DBOptions.h +++ b/SDK/simVis/DBOptions.h @@ -33,7 +33,7 @@ namespace simVis /** * Configuration options for the "DB" tile source driver. */ -class DBOptions : public osgEarth::TileSourceOptions // header-only (no export) +class DBOptions : public osgEarth::Contrib::TileSourceOptions // header-only (no export) { public: /** Location of the DB file to load (mutable) */ @@ -65,7 +65,7 @@ class DBOptions : public osgEarth::TileSourceOptions // header-only (no export) // (override from osgEarth::TileSourceOptions) virtual osgEarth::Config getConfig() const { - osgEarth::Config conf = osgEarth::TileSourceOptions::getConfig(); + osgEarth::Config conf = osgEarth::Contrib::TileSourceOptions::getConfig(); conf.set("url", _url); conf.set("deepest_level", _deepestLevel); return conf; @@ -76,7 +76,7 @@ class DBOptions : public osgEarth::TileSourceOptions // header-only (no export) // (override from osgEarth::TileSourceOptions) virtual void mergeConfig(const osgEarth::Config& conf) { - osgEarth::TileSourceOptions::mergeConfig(conf); + osgEarth::Contrib::TileSourceOptions::mergeConfig(conf); fromConfig_(conf); } diff --git a/SDK/simVis/Entity.cpp b/SDK/simVis/Entity.cpp index c11cc8099..d95b29d35 100644 --- a/SDK/simVis/Entity.cpp +++ b/SDK/simVis/Entity.cpp @@ -98,6 +98,57 @@ void CoordSurfaceClamping::clampCoordToMapSurface(simCore::Coordinate& coord) coord = llaCoord; } +void CoordSurfaceClamping::clampCoordToMapSurface(simCore::Coordinate& coord, osgEarth::ElevationEnvelope::Context& context) +{ + // nothing to do if we don't have valid ways of accessing elevation + if (!mapNode_.valid()) + { + assert(0); // called this method without setting the map node + return; + } + if (coord.coordinateSystem() != simCore::COORD_SYS_LLA && coord.coordinateSystem() != simCore::COORD_SYS_ECEF) + { + // coordinate type must be LLA to work with osgEarth elevation query + assert(0); + return; + } + + // convert from ECEF to LLA if necessary, since osgEarth Terrain getHeight requires LLA + simCore::Coordinate llaCoord; + if (coord.coordinateSystem() == simCore::COORD_SYS_ECEF) + simCore::CoordinateConverter::convertEcefToGeodetic(coord, llaCoord); + else + llaCoord = coord; + + // If getting the elevation fails, default to 0 to clamp to sea level + double elevation = 0; + + // Both methods for getting terrain elevation have drawbacks that make them undesirable in certain situations. SIM-10423 + // getHeight() can give inaccurate results depending on how much map data is loaded into the scene graph, while ElevationEnvelope can be prohibitively slow if there are many clamped entities + if (useMaxElevPrec_ && envelope_.valid()) + { + double hae = envelope_->getElevation(llaCoord.lon()*simCore::RAD2DEG, llaCoord.lat()*simCore::RAD2DEG, context); + if (hae != NO_DATA_VALUE) + elevation = hae; + } + else + { + double hamsl = 0.0; // not used + double hae = 0.0; // height above ellipsoid, the rough elevation + + if (mapNode_->getTerrain()->getHeight(mapNode_->getMapSRS(), llaCoord.lon()*simCore::RAD2DEG, llaCoord.lat()*simCore::RAD2DEG, &hamsl, &hae)) + elevation = hae; + } + + llaCoord.setPositionLLA(llaCoord.lat(), llaCoord.lon(), elevation); + + // convert back to ECEF if necessary + if (coord.coordinateSystem() == simCore::COORD_SYS_ECEF) + simCore::CoordinateConverter::convertGeodeticToEcef(llaCoord, coord); + else + coord = llaCoord; +} + bool CoordSurfaceClamping::isValid() const { return mapNode_.valid(); diff --git a/SDK/simVis/Entity.h b/SDK/simVis/Entity.h index 1ff6a1ad5..0cb661384 100644 --- a/SDK/simVis/Entity.h +++ b/SDK/simVis/Entity.h @@ -74,6 +74,9 @@ namespace simVis */ void clampCoordToMapSurface(simCore::Coordinate& coord); + /** clampCoordToMapSurface() variation that accepts a context for optimization. */ + void clampCoordToMapSurface(simCore::Coordinate& coord, osgEarth::ElevationEnvelope::Context& context); + /** Return true if able to apply clamping, false otherwise */ bool isValid() const; diff --git a/SDK/simVis/EntityLabel.cpp b/SDK/simVis/EntityLabel.cpp index 066b3b9aa..0625acbcb 100644 --- a/SDK/simVis/EntityLabel.cpp +++ b/SDK/simVis/EntityLabel.cpp @@ -140,6 +140,7 @@ void EntityLabelNode::update(const simData::CommonPrefs& commonPrefs, const std: PB_FIELD_CHANGED(&lastLabelPrefs, &labelPrefs, textoutline) || PB_FIELD_CHANGED(&lastLabelPrefs, &labelPrefs, backdroptype) || PB_FIELD_CHANGED(&lastLabelPrefs, &labelPrefs, alignment) || + PB_FIELD_CHANGED(&lastLabelPrefs, &labelPrefs, priority) || PB_FIELD_CHANGED(&lastLabelPrefs, &labelPrefs, backdropimplementation); // update the style: @@ -151,6 +152,9 @@ void EntityLabelNode::update(const simData::CommonPrefs& commonPrefs, const std: ts->pixelOffset() = osg::Vec2s(labelPrefs.offsetx(), labelPrefs.offsety()); ts->encoding() = osgEarth::TextSymbol::ENCODING_UTF8; + // Declutter is off if priority is negative + ts->declutter() = (labelPrefs.priority() >= 0); + // text color: osg::Vec4 color = ColorUtils::RgbaToVec4(labelPrefs.color()); ts->fill() = osgEarth::Fill(color.r(), color.g(), color.b(), color.a()); diff --git a/SDK/simVis/GOG/Annotation.cpp b/SDK/simVis/GOG/Annotation.cpp index 21ddefdd7..e16fe5c11 100644 --- a/SDK/simVis/GOG/Annotation.cpp +++ b/SDK/simVis/GOG/Annotation.cpp @@ -44,8 +44,7 @@ GogNodeInterface* TextAnnotation::deserialize( p.parseGeometry(parsedShape); GogNodeInterface* rv = NULL; - osgEarth::LabelNode* label = NULL; - label = new osgEarth::LabelNode(text, p.style_); + osgEarth::LabelNode* label = new osgEarth::LabelNode(text, p.style_); label->setName("GOG Label"); if (nodeType == GOGNODE_GEOGRAPHIC) { diff --git a/SDK/simVis/GOG/GOGNode.h b/SDK/simVis/GOG/GOGNode.h index 534006b15..495619880 100644 --- a/SDK/simVis/GOG/GOGNode.h +++ b/SDK/simVis/GOG/GOGNode.h @@ -55,7 +55,8 @@ namespace simVis { namespace GOG GOG_POINT_SIZE_SET, GOG_LINE_PROJECTION_SET, GOG_TEXT_OUTLINE_COLOR_SET, - GOG_TEXT_OUTLINE_THICKNESS_SET + GOG_TEXT_OUTLINE_THICKNESS_SET, + GOG_ALTITUDE_MODE_SET }; // stipple defines diff --git a/SDK/simVis/GOG/GogNodeInterface.cpp b/SDK/simVis/GOG/GogNodeInterface.cpp index 4fb534663..cfa8a6912 100644 --- a/SDK/simVis/GOG/GogNodeInterface.cpp +++ b/SDK/simVis/GOG/GogNodeInterface.cpp @@ -539,6 +539,18 @@ void GogNodeInterface::serializeToStream(std::ostream& gogOutputStream) else if (metaData_.isSetExplicitly(GOG_TESSELLATE_SET)) gogOutputStream << "tessellate false\n"; + // altitude mode + simVis::GOG::AltitudeMode altMode; + if (getAltitudeMode(altMode) == 0) + { + if (altMode == simVis::GOG::ALTITUDE_NONE && metaData_.isSetExplicitly(GOG_ALTITUDE_MODE_SET)) + gogOutputStream << "altitudemode none\n"; + else if (altMode == simVis::GOG::ALTITUDE_GROUND_RELATIVE) + gogOutputStream << "altitudemode relativetoground\n"; + else if (altMode == simVis::GOG::ALTITUDE_GROUND_CLAMPED) + gogOutputStream << "altitudemode clamptoground\n"; + // simVis::GOG::ALTITUDE_EXTRUDE is covered by the extrude keyword + } // Follow data is not currently serialized out } @@ -679,6 +691,7 @@ int GogNodeInterface::getTextOutline(osg::Vec4f& outlineColor, simData::TextOutl void GogNodeInterface::setAltitudeMode(AltitudeMode altMode) { + metaData_.setExplicitly(GOG_ALTITUDE_MODE_SET); if (altMode_ == altMode) return; altMode_ = altMode; @@ -1108,6 +1121,11 @@ void GogNodeInterface::applyBackfaceCulling() // extrudes to a filled polygon instead of a 3D shape). bool isClosed3dShape = (shape() == GOG_SPHERE || shape() == GOG_ELLIPSOID || shape() == GOG_CYLINDER || shape() == GOG_LATLONALTBOX || shape() == GOG_CONE); + + // Semitransparent hemispheres without depth buffer need backface culling on else odd artifacts show through + if (shape() == GOG_HEMISPHERE && fillColor_.a() < 1.0 && !depthBuffer_) + isClosed3dShape = true; + const bool isLine = (shape() == GOG_LINE || shape() == GOG_LINESEGS); if (isClosed3dShape || (extruded_ && !isLine)) style_.getOrCreateSymbol()->backfaceCulling() = true; @@ -1504,6 +1522,7 @@ void FeatureNodeInterface::setTessellation(TessellationStyle style) void FeatureNodeInterface::setAltitudeMode(AltitudeMode altMode) { + metaData_.setExplicitly(GOG_ALTITUDE_MODE_SET); if (altMode_ == altMode) return; altMode_ = altMode; @@ -1708,10 +1727,6 @@ int LabelNodeInterface::getPosition(osg::Vec3d& position, osgEarth::GeoPoint* re int LabelNodeInterface::getTextOutline(osg::Vec4f& outlineColor, simData::TextOutline& outlineThickness) const { - if (!style_.has()) - return 1; - const osgEarth::TextSymbol* ts = style_.getSymbol(); - outlineColor = outlineColor_; outlineThickness = outlineThickness_; return 0; diff --git a/SDK/simVis/GOG/Parser.cpp b/SDK/simVis/GOG/Parser.cpp index b9d1268fe..48cc75763 100644 --- a/SDK/simVis/GOG/Parser.cpp +++ b/SDK/simVis/GOG/Parser.cpp @@ -620,8 +620,8 @@ bool Parser::parse(std::istream& input, std::vector& output, std::v { if (tokens.size() >= 2) { - currentMetaData.metadata += line + "\n"; state.altitudeMode_ = tokens[1]; + currentMetaData.setExplicitly(GOG_ALTITUDE_MODE_SET); } else { @@ -670,7 +670,7 @@ bool Parser::parse(std::istream& input, std::vector& output, std::v printError_(lineNumber, "angleunits command requires 1 argument"); } } - else if (tokens[0] == "verticaldatum" && tokens.size() >= 2) + else if (tokens[0] == "verticaldatum") { if (tokens.size() >= 2) { diff --git a/SDK/simVis/GOG/Utils.cpp b/SDK/simVis/GOG/Utils.cpp index 0ac598795..3f637f8ab 100644 --- a/SDK/simVis/GOG/Utils.cpp +++ b/SDK/simVis/GOG/Utils.cpp @@ -340,7 +340,7 @@ ParserData::ParserData(const ParsedShape& parsedShape, const GOGContext& context units_.altitudeUnits_.convertTo(simCore::Units::METERS, parsedShape.doubleValue(GOG_REF_ALT, 0.0))); } - // The centerLLA and centerXYZ doe not apply to points, lines, line segments and polygons + // The centerLLA and centerXYZ do not apply to points, lines, line segments and polygons if ((shape != GOG_POINTS) && (shape != GOG_POLYGON) && (shape != GOG_LINE) && (shape != GOG_LINESEGS)) { if (parsedShape.hasValue(GOG_CENTERLL)) @@ -355,12 +355,11 @@ ParserData::ParserData(const ParsedShape& parsedShape, const GOGContext& context parseAngle(p.x, 0.0), // latitude units_.altitudeUnits_.convertTo(simCore::Units::METERS, altitude)); } - - if (parsedShape.hasValue(GOG_CENTERXY)) + else if (parsedShape.hasValue(GOG_CENTERXY)) { const PositionStrings& p = parsedShape.positionValue(GOG_CENTERXY); // Convert Z value from string - double xyz[3] = {0.}; + double xyz[3] = { 0. }; simCore::isValidNumber(p.x, xyz[0]); simCore::isValidNumber(p.y, xyz[1]); simCore::isValidNumber(p.z, xyz[2]); @@ -373,6 +372,9 @@ ParserData::ParserData(const ParsedShape& parsedShape, const GOGContext& context if (!refPointLLA_.isSet()) refPointLLA_->set(context_.refPoint_->vec3d()); } + // annotations have a single center point but don't use centerxyz keyword for relative shapes, so make sure the refPointLLA_ is set for relative annotations + else if (shape == GOG_ANNOTATION && !refPointLLA_.isSet() && parsedShape.pointType() == ParsedShape::XYZ) + refPointLLA_->set(context_.refPoint_->vec3d()); } if (parsedShape.hasValue(GOG_LINEPROJECTION)) @@ -671,7 +673,7 @@ GeoPoint ParserData::getMapPosition(bool ignoreOffset) const // apply any xyz offset to the map position ref point if there is one simCore::CoordinateConverter cc; cc.setReferenceOrigin(refPointLLA_->y() * simCore::DEG2RAD, refPointLLA_->x() * simCore::DEG2RAD, refPointLLA_->z()); - simCore::Coordinate coord(simCore::COORD_SYS_ENU, simCore::Vec3(xyz.x(), xyz.y(), xyz.z())); + simCore::Coordinate coord(simCore::COORD_SYS_XEAST, simCore::Vec3(xyz.x(), xyz.y(), xyz.z())); simCore::Coordinate outCoord; cc.convert(coord, outCoord, simCore::COORD_SYS_LLA); diff --git a/SDK/simVis/GeoFence.cpp b/SDK/simVis/GeoFence.cpp index 8a9c7e7ec..c2178685c 100644 --- a/SDK/simVis/GeoFence.cpp +++ b/SDK/simVis/GeoFence.cpp @@ -21,6 +21,7 @@ */ #include "simVis/GeoFence.h" +#undef LC #define LC "[GeoFence] " namespace simVis { @@ -55,4 +56,4 @@ bool HorizonGeoFence::contains(const osg::Vec3d& p) const return dev >= minDeviation_; } -} \ No newline at end of file +} diff --git a/SDK/simVis/LayerRefreshCallback.cpp b/SDK/simVis/LayerRefreshCallback.cpp index c9e097e7c..7a670038b 100644 --- a/SDK/simVis/LayerRefreshCallback.cpp +++ b/SDK/simVis/LayerRefreshCallback.cpp @@ -42,7 +42,7 @@ class LayerRefreshCallback::MapUpdatedCallback : public osgEarth::MapCallback /** Watch a TerrainLayer when it's added */ virtual void onLayerAdded(osgEarth::Layer* layer, unsigned index) { - const osgEarth::TerrainLayer* terrainLayer = dynamic_cast(layer); + const osgEarth::TileLayer* terrainLayer = dynamic_cast(layer); if (terrainLayer != NULL) parent_.watchLayer_(terrainLayer); } @@ -50,7 +50,7 @@ class LayerRefreshCallback::MapUpdatedCallback : public osgEarth::MapCallback /** Forget a TerrainLayer when it's removed */ virtual void onLayerRemoved(osgEarth::Layer* layer, unsigned index) { - const osgEarth::TerrainLayer* terrainLayer = dynamic_cast(layer); + const osgEarth::TileLayer* terrainLayer = dynamic_cast(layer); if (terrainLayer != NULL) parent_.forgetLayer_(terrainLayer); } @@ -58,7 +58,7 @@ class LayerRefreshCallback::MapUpdatedCallback : public osgEarth::MapCallback /** Watch a TerrainLayer when it's enabled */ virtual void onLayerEnabled(osgEarth::Layer* layer) { - const osgEarth::TerrainLayer* terrainLayer = dynamic_cast(layer); + const osgEarth::TileLayer* terrainLayer = dynamic_cast(layer); if (terrainLayer != NULL) parent_.watchLayer_(terrainLayer); } @@ -66,7 +66,7 @@ class LayerRefreshCallback::MapUpdatedCallback : public osgEarth::MapCallback /** Forget a TerrainLayer when it's disabled */ virtual void onLayerDisabled(osgEarth::Layer* layer) { - const osgEarth::TerrainLayer* terrainLayer = dynamic_cast(layer); + const osgEarth::TileLayer* terrainLayer = dynamic_cast(layer); if (terrainLayer != NULL) parent_.forgetLayer_(terrainLayer); } @@ -136,7 +136,7 @@ void LayerRefreshCallback::runImpl_() // Loop through all watched layers for (auto it = watchedLayers_.begin(); it != watchedLayers_.end(); ++it) { - osg::observer_ptr layer = (*it).layer; + osg::observer_ptr layer = (*it).layer; if (!layer.valid() || !layer->getEnabled()) { assert(0); // Should not be watching a NULL or disabled layer @@ -157,8 +157,10 @@ void LayerRefreshCallback::runImpl_() SIM_DEBUG_FP << "simVis::LayerRefreshCallback::run() attempting to refresh layer \"" << layer->getName() << "\".\n"; const auto extents = layer->getDataExtents(); + std::vector layerVec; + layerVec.push_back(layer.get()); for (const auto& extent : extents) - terrainEngine->invalidateLayerRegion(layer.get(), extent); + terrainEngine->invalidateRegion(layerVec, extent); // Reset the timer for this layer it->elapsedTime.reset(); @@ -167,7 +169,7 @@ void LayerRefreshCallback::runImpl_() // NOTE: A call to terrainEngine->dirtyTerrain() is NOT required here } -void LayerRefreshCallback::watchLayer_(const osgEarth::TerrainLayer* layer) +void LayerRefreshCallback::watchLayer_(const osgEarth::TileLayer* layer) { if (layer == NULL) return; @@ -178,7 +180,7 @@ void LayerRefreshCallback::watchLayer_(const osgEarth::TerrainLayer* layer) watchedLayers_.push_back(info); } -void LayerRefreshCallback::forgetLayer_(const osgEarth::TerrainLayer* layer) +void LayerRefreshCallback::forgetLayer_(const osgEarth::TileLayer* layer) { if (layer == NULL) return; diff --git a/SDK/simVis/LayerRefreshCallback.h b/SDK/simVis/LayerRefreshCallback.h index dcb3bde71..a27bd0c0e 100644 --- a/SDK/simVis/LayerRefreshCallback.h +++ b/SDK/simVis/LayerRefreshCallback.h @@ -69,7 +69,7 @@ class LayerRefreshCallback : public osg::Callback /** Groups a TerrainLayer pointer and the elapsed time since last its refresh */ struct LayerInfo { - osg::observer_ptr layer; + osg::observer_ptr layer; osg::ElapsedTime elapsedTime; }; @@ -77,9 +77,9 @@ class LayerRefreshCallback : public osg::Callback void runImpl_(); /** Watch the given layer and refresh it when required a refresh is due */ - void watchLayer_(const osgEarth::TerrainLayer* layer); + void watchLayer_(const osgEarth::TileLayer* layer); /** Stop watching the given layer */ - void forgetLayer_(const osgEarth::TerrainLayer* layer); + void forgetLayer_(const osgEarth::TileLayer* layer); /** Get the interval for the given layer in seconds */ double getIntervalForLayer_(const osgEarth::Layer* layer) const; diff --git a/SDK/simVis/LocalGrid.cpp b/SDK/simVis/LocalGrid.cpp index a5618ccf3..93137e64a 100644 --- a/SDK/simVis/LocalGrid.cpp +++ b/SDK/simVis/LocalGrid.cpp @@ -26,9 +26,11 @@ #include "osg/Geometry" #include "osgText/Text" +#include "osgEarth/Color" +#include "osgEarth/GLUtils" +#include "osgEarth/PointDrawable" #include "osgEarth/Registry" #include "osgEarth/ShaderGenerator" -#include "osgEarth/Color" #include "simCore/Calc/Math.h" #include "simNotify/Notify.h" @@ -38,7 +40,6 @@ #include "simVis/Constants.h" #include "simVis/Locator.h" #include "simVis/Platform.h" -#include "simVis/PointSize.h" #include "simVis/Registry.h" #include "simVis/Types.h" #include "simVis/Utils.h" @@ -287,16 +288,15 @@ class Axis : public LineStrip }; /// Geometry for off-axis sectors in Polar and SpeedRing grid types. -class RadialPoints : public osg::Geometry +class RadialPoints : public osg::Group { public: RadialPoints(const osg::Vec4f& color, float sectorAngleDeg, unsigned int numRings) : sectorAngleDeg_(sectorAngleDeg), - numRings_(numRings) + numRings_(numRings), + points_(new osgEarth::PointDrawable) { setName("simVis::LocalGridNode::RadialPoints"); - setUseVertexBufferObjects(true); - setUseDisplayList(false); // determine how many vertices we'll actually use unsigned int vertexCount = 0; @@ -310,26 +310,15 @@ class RadialPoints : public osg::Geometry { if (osg::equivalent(static_cast(fmod(static_cast(i), RADIAL_VERTEX_FACTOR)), 0.f)) // don't overdraw the rings continue; - vertexCount++; + ++vertexCount; } } - osg::ref_ptr vertexArray = new osg::Vec3Array(osg::Array::BIND_PER_VERTEX, vertexCount); - setVertexArray(vertexArray.get()); - - osg::ref_ptr colorArray = new osg::Vec4Array(osg::Array::BIND_OVERALL, 1); - (*colorArray)[0] = color; - setColorArray(colorArray.get()); - - addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, vertexArray->size())); + points_->allocate(vertexCount); + points_->setColor(color); + addChild(points_); } void update(double sizeM) { - osg::ref_ptr vertexArray = dynamic_cast(getVertexArray()); - if (!vertexArray) - { - assert(0); - return; - } const float spacingM = sizeM / osg::maximum(1u, numRings_); const float radialVertexSpacing = spacingM / RADIAL_VERTEX_FACTOR; size_t index = 0; @@ -347,16 +336,17 @@ class RadialPoints : public osg::Geometry if (osg::equivalent(static_cast(fmod(static_cast(i), RADIAL_VERTEX_FACTOR)), 0.f)) // don't overdraw the rings continue; // if assert fails, re-check algorithm for determining vertexCount in constructor - assert(index < vertexArray->getNumElements()); - (*vertexArray)[index] = osg::Vec3(x * radialVertexSpacing * i, y * radialVertexSpacing * i, 0.f); - index++; + assert(index < points_->size()); + points_->setVertex(index, osg::Vec3f(x * radialVertexSpacing * i, y * radialVertexSpacing * i, 0.f)); + ++index; } } - vertexArray->dirty(); + points_->dirty(); } private: float sectorAngleDeg_; unsigned int numRings_; + osgEarth::PointDrawable* points_; }; /// Geometry for range rings in Polar, RangeRing and SpeedRing grid types. @@ -461,8 +451,7 @@ void LocalGridNode::rebuild_(const simData::LocalGridPrefs& prefs) { graphicsGroup_ = new osg::Geode(); graphicsGroup_->setName("simVis::LocalGridNode::GraphicsGeode"); - osg::StateSet* ss = graphicsGroup_->getOrCreateStateSet(); - PointSize::setValues(ss, 1.5f, osg::StateAttribute::ON); + osgEarth::GLUtils::setPointSize(graphicsGroup_->getOrCreateStateSet(), 2.f, osg::StateAttribute::ON); addChild(graphicsGroup_.get()); } @@ -673,7 +662,7 @@ void LocalGridNode::syncWithLocator() } // creates a Cartesian grid. -void LocalGridNode::createCartesian_(const simData::LocalGridPrefs& prefs, osg::Geode* geomGroup, osg::Geode* labelGroup) const +void LocalGridNode::createCartesian_(const simData::LocalGridPrefs& prefs, osg::Group* geomGroup, osg::Geode* labelGroup) const { const osgEarth::Units& sizeUnits = simVis::convertUnitsToOsgEarth(prefs.sizeunits()); // Note that size is halved; it's provided in diameter, and we need it as radius @@ -705,7 +694,7 @@ void LocalGridNode::createCartesian_(const simData::LocalGridPrefs& prefs, osg:: sub1->update(osg::Vec3(x, y0, 0.f), osg::Vec3(x, y0 + span, 0.f)); sub1->setName("simVis::LocalGridNode::GridSubDivision1"); sub1->setColor(subColor); - geomGroup->addDrawable(sub1); + geomGroup->addChild(sub1); } { const float y = y0 + subSpacing * s; @@ -713,7 +702,7 @@ void LocalGridNode::createCartesian_(const simData::LocalGridPrefs& prefs, osg:: sub2->update(osg::Vec3(x0, y, 0.f), osg::Vec3(x0 + span, y, 0.f)); sub2->setName("simVis::LocalGridNode::GridSubDivision2"); sub2->setColor(subColor); - geomGroup->addDrawable(sub2); + geomGroup->addChild(sub2); } } @@ -726,7 +715,7 @@ void LocalGridNode::createCartesian_(const simData::LocalGridPrefs& prefs, osg:: div1->update(osg::Vec3(x, y0, 0.f), osg::Vec3(x, y0 + span, 0.f)); div1->setName("simVis::LocalGridNode::GridDivision1"); div1->setColor(color); - geomGroup->addDrawable(div1); + geomGroup->addChild(div1); } // x-label: if (x < 0 && prefs.gridlabeldraw()) @@ -742,7 +731,7 @@ void LocalGridNode::createCartesian_(const simData::LocalGridPrefs& prefs, osg:: div2->update(osg::Vec3(x0, y, 0.f), osg::Vec3(x0 + span, y, 0.f)); div2->setName("simVis::LocalGridNode::GridDivision2"); div2->setColor(color); - geomGroup->addDrawable(div2); + geomGroup->addChild(div2); } // y-label if (y > 0 && prefs.gridlabeldraw()) @@ -755,7 +744,7 @@ void LocalGridNode::createCartesian_(const simData::LocalGridPrefs& prefs, osg:: } // creates a range-rings local grid with optional polar radials. -void LocalGridNode::createRangeRings_(const simData::LocalGridPrefs& prefs, osg::Geode* geomGroup, osg::Geode* labelGroup, bool includePolarRadials) const +void LocalGridNode::createRangeRings_(const simData::LocalGridPrefs& prefs, osg::Group* geomGroup, osg::Geode* labelGroup, bool includePolarRadials) const { const osgEarth::Units& sizeUnits = simVis::convertUnitsToOsgEarth(prefs.sizeunits()); // Note that size is halved; it's provided in diameter, and we need it as radius @@ -785,7 +774,7 @@ void LocalGridNode::createRangeRings_(const simData::LocalGridPrefs& prefs, osg: RangeRing* rangeRing = new RangeRing(i); rangeRing->setColor(isMajorRing ? color : subColor); - geomGroup->addDrawable(rangeRing); + geomGroup->addChild(rangeRing); rangeRing->update(prefs, sizeM); // label: @@ -807,26 +796,26 @@ void LocalGridNode::createRangeRings_(const simData::LocalGridPrefs& prefs, osg: { Axis* majorAxis = new Axis(true); majorAxis->setColor(color); - geomGroup->addDrawable(majorAxis); + geomGroup->addChild(majorAxis); majorAxis->update(sizeM); Axis* minorAxis = new Axis(false); minorAxis->setColor(color); - geomGroup->addDrawable(minorAxis); + geomGroup->addChild(minorAxis); minorAxis->update(sizeM); const float sectorAngle = prefs.gridsettings().sectorangle(); if (sectorAngle > 0.0f) { RadialPoints* points = new RadialPoints(subColor, sectorAngle, numRings); - geomGroup->addDrawable(points); + geomGroup->addChild(points); points->update(sizeM); } } } // creates a speed-rings local grid with optional polar radials. -void LocalGridNode::createSpeedRings_(const simData::LocalGridPrefs& prefs, osg::Geode* graphicsGroup, osg::Geode* labelGroup, bool drawSpeedLine) const +void LocalGridNode::createSpeedRings_(const simData::LocalGridPrefs& prefs, osg::Group* graphicsGroup, osg::Geode* labelGroup, bool drawSpeedLine) const { const osg::Vec4f& color = simVis::Color(prefs.gridcolor(), simVis::Color::RGBA); const osg::Vec4f& subColor = simVis::Color(color * 0.5f, 1.0f); @@ -839,7 +828,7 @@ void LocalGridNode::createSpeedRings_(const simData::LocalGridPrefs& prefs, osg: SpeedLine* speedLine = new SpeedLine(); speedLine->setDataVariance(osg::Object::DYNAMIC); speedLine->setColor(color); - graphicsGroup->addDrawable(speedLine); + graphicsGroup->addChild(speedLine); if (!prefs.gridlabeldraw()) return; @@ -849,11 +838,11 @@ void LocalGridNode::createSpeedRings_(const simData::LocalGridPrefs& prefs, osg: Axis* majorAxis = new Axis(true); majorAxis->setDataVariance(osg::Object::DYNAMIC); majorAxis->setColor(color); - graphicsGroup->addDrawable(majorAxis); + graphicsGroup->addChild(majorAxis); Axis* minorAxis = new Axis(false); minorAxis->setDataVariance(osg::Object::DYNAMIC); minorAxis->setColor(color); - graphicsGroup->addDrawable(minorAxis); + graphicsGroup->addChild(minorAxis); const float sectorAngle = prefs.gridsettings().sectorangle(); // draw polar radials for speed rings @@ -861,7 +850,7 @@ void LocalGridNode::createSpeedRings_(const simData::LocalGridPrefs& prefs, osg: { RadialPoints* points = new RadialPoints(subColor, sectorAngle, numRings); points->setDataVariance(osg::Object::DYNAMIC); - graphicsGroup->addDrawable(points); + graphicsGroup->addChild(points); } } @@ -873,7 +862,7 @@ void LocalGridNode::createSpeedRings_(const simData::LocalGridPrefs& prefs, osg: RangeRing* speedRing = new RangeRing(i); speedRing->setDataVariance(osg::Object::DYNAMIC); speedRing->setColor(isMajorRing ? color : subColor); - graphicsGroup->addDrawable(speedRing); + graphicsGroup->addChild(speedRing); } // labels are only added to major rings if (isMajorRing && prefs.gridlabeldraw()) diff --git a/SDK/simVis/LocalGrid.h b/SDK/simVis/LocalGrid.h index 2354fd457..e4d2d9d17 100644 --- a/SDK/simVis/LocalGrid.h +++ b/SDK/simVis/LocalGrid.h @@ -89,13 +89,13 @@ namespace simVis void configureLocator_(const simData::LocalGridPrefs& prefs); /// create Cartesian grid display - void createCartesian_(const simData::LocalGridPrefs& prefs, osg::Geode* geomGroup, osg::Geode* labelGroup) const; + void createCartesian_(const simData::LocalGridPrefs& prefs, osg::Group* geomGroup, osg::Geode* labelGroup) const; /// create polar ring or range ring display - void createRangeRings_(const simData::LocalGridPrefs& prefs, osg::Geode* geomGroup, osg::Geode* labelGroup, bool includePolarRadials) const; + void createRangeRings_(const simData::LocalGridPrefs& prefs, osg::Group* geomGroup, osg::Geode* labelGroup, bool includePolarRadials) const; /// create speed ring or speed line display - void createSpeedRings_(const simData::LocalGridPrefs& prefs, osg::Geode* geomGroup, osg::Geode* labelGroup, bool drawSpeedLine) const; + void createSpeedRings_(const simData::LocalGridPrefs& prefs, osg::Group* geomGroup, osg::Geode* labelGroup, bool drawSpeedLine) const; /// update the speed ring/line display for current data void updateSpeedRings_(const simData::LocalGridPrefs& prefs, double sizeM, double timeRadiusSeconds); diff --git a/SDK/simVis/ModelCache.cpp b/SDK/simVis/ModelCache.cpp index 18c91efcd..a4d5d1854 100644 --- a/SDK/simVis/ModelCache.cpp +++ b/SDK/simVis/ModelCache.cpp @@ -32,6 +32,7 @@ #include "osg/TexEnv" #include "osg/TexEnvCombine" #include "osg/ValueObject" +#include "osgDB/ReadFile" #include "osgSim/DOFTransform" #include "osgSim/LightPointNode" #include "osgSim/MultiSwitch" @@ -92,7 +93,9 @@ class SetRenderBinsToInherit : public osg::NodeVisitor virtual void apply(osg::Node& node) { osg::StateSet* ss = node.getStateSet(); - if (ss) + // Catch Creator's Superface/Subface condition, where they use POLYGONOFFSET and render bin + // to try to superimpose polygons. Avoid clearing render bins in that particular case. + if (ss && !ss->getAttribute(osg::StateAttribute::POLYGONOFFSET)) { // Fix regularly occuring alpha issues by negating explicit render bin assignments // in the loaded model diff --git a/SDK/simVis/OverheadMode.cpp b/SDK/simVis/OverheadMode.cpp index f059b9132..ebefaa2ee 100644 --- a/SDK/simVis/OverheadMode.cpp +++ b/SDK/simVis/OverheadMode.cpp @@ -139,8 +139,8 @@ namespace osg::ref_ptr _stateset; OceanOverheadModeCallback() + : _stateset(new osg::StateSet()) { - _stateset = new osg::StateSet(); // draw the ocean in the same render bin as the terrain _stateset->setRenderBinDetails(simVis::BIN_TERRAIN, simVis::BIN_GLOBAL_SIMSDK, osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); // disable depth buffer writes diff --git a/SDK/simVis/Platform.cpp b/SDK/simVis/Platform.cpp index 3cd553ae4..d8597d13f 100644 --- a/SDK/simVis/Platform.cpp +++ b/SDK/simVis/Platform.cpp @@ -37,6 +37,7 @@ #include "simVis/PlatformModel.h" #include "simVis/RadialLOSNode.h" #include "simVis/Registry.h" +#include "simVis/TimeTicks.h" #include "simVis/TrackHistory.h" #include "simVis/Utils.h" #include "simVis/VelocityVector.h" @@ -160,6 +161,7 @@ lastUpdateTime_(-std::numeric_limits::max()), firstHistoryTime_(std::numeric_limits::max()), expireModeGroupAttach_(expireModeGroupAttach), track_(NULL), +timeTicks_(NULL), localGrid_(NULL), bodyAxisVector_(NULL), inertialAxisVector_(NULL), @@ -205,6 +207,9 @@ PlatformNode::~PlatformNode() if (track_.valid()) expireModeGroup_->removeChild(track_); track_ = NULL; + if (timeTicks_.valid()) + expireModeGroup_->removeChild(timeTicks_); + timeTicks_ = NULL; if (expireModeGroupAttach_.valid()) expireModeGroupAttach_->removeChild(expireModeGroup_); @@ -279,12 +284,26 @@ void PlatformNode::setPrefs(const simData::PlatformPrefs& prefs) // remove or create track history if (showTrack_(prefs)) { + // first check time ticks + if (prefs.trackprefs().timeticks().drawstyle() != simData::TimeTickPrefs::NONE) + { + if (!timeTicks_.valid()) + createTimeTicks_(prefs); + } + else if (timeTicks_.valid()) + { + expireModeGroup_->removeChild(timeTicks_); + timeTicks_ = NULL; + } + if (!track_.valid()) createTrackHistoryNode_(prefs); else { // normal processing: update the track history data track_->setPrefs(prefs, lastProps_); + if (timeTicks_.valid()) + timeTicks_->setPrefs(prefs, lastProps_); // track_ cannot be valid without having had platform prefs set at least once; // if assert fails, check whether prefs are initialized correctly when platform is created @@ -296,15 +315,25 @@ void PlatformNode::setPrefs(const simData::PlatformPrefs& prefs) // track history is constrained by platform data limiting track_->reset(); track_->update(); + // time ticks follows data limiting same as track history + if (timeTicks_.valid()) + { + timeTicks_->reset(); + timeTicks_->update(); + } } - if (track_.valid()) - track_->setNodeMask(prefsDraw ? simVis::DISPLAY_MASK_TRACK_HISTORY : simVis::DISPLAY_MASK_NONE); + track_->setNodeMask(prefsDraw ? simVis::DISPLAY_MASK_TRACK_HISTORY : simVis::DISPLAY_MASK_NONE); + if (timeTicks_.valid()) + timeTicks_->setNodeMask(prefsDraw ? simVis::DISPLAY_MASK_TRACK_HISTORY : simVis::DISPLAY_MASK_NONE); } } else { expireModeGroup_->removeChild(track_); track_ = NULL; + // time ticks is always hidden if track history is hidden + expireModeGroup_->removeChild(timeTicks_); + timeTicks_ = NULL; } // validate localgrid prefs changes that might provide user notifications @@ -481,6 +510,8 @@ bool PlatformNode::updateFromDataStore(const simData::DataSliceBase* updateSlice if (!updateSlice->hasChanged() && !force && !forceUpdateFromDataStore_) { + if (timeTicks_.valid()) + timeTicks_->update(); // Even if the platform has not changed, the label can still change - entity name could change as a result of category data, for example. updateLabel_(lastPrefs_); return false; @@ -534,15 +565,28 @@ bool PlatformNode::updateFromDataStore(const simData::DataSliceBase* updateSlice { if (!track_.valid()) createTrackHistoryNode_(lastPrefs_); - else if (timeChanged || updateSlice->hasChanged()) - track_->update(); + else + { + if (timeChanged || updateSlice->hasChanged()) + track_->update(); + // always update time ticks + if (timeTicks_.valid()) + timeTicks_->update(); + } } - else if (track_.valid()) + else { - expireModeGroup_->removeChild(track_); - track_ = NULL; + if (track_.valid()) + { + expireModeGroup_->removeChild(track_); + track_ = NULL; + } + if (timeTicks_.valid()) + { + expireModeGroup_->removeChild(timeTicks_); + timeTicks_ = NULL; + } } - // avoid applying a null update over and over if (!updateSlice->current() && getNodeMask() == DISPLAY_MASK_NONE && !valid_) return false; @@ -605,8 +649,30 @@ bool PlatformNode::createTrackHistoryNode_(const simData::PlatformPrefs& prefs) track_->setPrefs(prefs, lastProps_, true); updateHostBounds_(prefs.scale()); track_->update(); + const bool prefsDraw = lastPrefs_.commonprefs().datadraw() && prefs.commonprefs().draw(); track_->setNodeMask(prefsDraw ? simVis::DISPLAY_MASK_TRACK_HISTORY : simVis::DISPLAY_MASK_NONE); + + // check to see if we need to create time ticks, since time ticks, like track history, can be turned on before platform is actually valid + if ((prefs.trackprefs().timeticks().drawstyle() != simData::TimeTickPrefs::NONE) && !timeTicks_.valid()) + createTimeTicks_(prefs); + + return true; +} + +bool PlatformNode::createTimeTicks_(const simData::PlatformPrefs& prefs) +{ + // if assert fails, check that callers only call on !valid() condition + assert(!timeTicks_.valid()); + + timeTicks_ = new TimeTicks(ds_, getLocator()->getSRS(), platformTspiFilterManager_, getId()); + expireModeGroup_->addChild(timeTicks_); + timeTicks_->setPrefs(prefs, lastProps_, true); + timeTicks_->update(); + + const bool prefsDraw = lastPrefs_.commonprefs().datadraw() && prefs.commonprefs().draw(); + timeTicks_->setNodeMask(prefsDraw ? simVis::DISPLAY_MASK_TRACK_HISTORY : simVis::DISPLAY_MASK_NONE); + return true; } @@ -615,6 +681,8 @@ void PlatformNode::updateClockMode(const simCore::Clock* clock) // notify the track history of a change in time direction if (track_.valid()) track_->updateClockMode(clock); + if (timeTicks_.valid()) + timeTicks_->updateClockMode(clock); } void PlatformNode::flush() diff --git a/SDK/simVis/Platform.h b/SDK/simVis/Platform.h index 9b37e5608..3db313d49 100644 --- a/SDK/simVis/Platform.h +++ b/SDK/simVis/Platform.h @@ -43,6 +43,7 @@ class PlatformModelNode; class PlatformTspiFilterManager; class ProjectorNode; class RadialLOSNode; +class TimeTicks; class TrackHistoryNode; class VelocityVector; @@ -295,6 +296,7 @@ class SDKVIS_EXPORT PlatformNode : public EntityNode void updateHostBounds_(double scale); void updateLabel_(const simData::PlatformPrefs& prefs); bool createTrackHistoryNode_(const simData::PlatformPrefs& prefs); + bool createTimeTicks_(const simData::PlatformPrefs& prefs); void updateOrRemoveBodyAxis_(bool prefsDraw, const simData::PlatformPrefs& prefs); void updateOrRemoveInertialAxis_(bool prefsDraw, const simData::PlatformPrefs& prefs); void updateOrRemoveVelocityVector_(bool prefsDraw, const simData::PlatformPrefs& prefs); @@ -323,6 +325,7 @@ class SDKVIS_EXPORT PlatformNode : public EntityNode osg::observer_ptr expireModeGroupAttach_; /// track history points osg::ref_ptr track_; + osg::ref_ptr timeTicks_; osg::ref_ptr localGrid_; osg::ref_ptr highlight_; osg::ref_ptr bodyAxisVector_; diff --git a/SDK/simVis/PointSize.cpp b/SDK/simVis/PointSize.cpp deleted file mode 100644 index eeee43642..000000000 --- a/SDK/simVis/PointSize.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* -*- mode: c++ -*- */ -/**************************************************************************** -***** ***** -***** Classification: UNCLASSIFIED ***** -***** Classified By: ***** -***** Declassify On: ***** -***** ***** -**************************************************************************** -* -* -* Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. -* EW Modeling & Simulation, Code 5773 -* 4555 Overlook Ave. -* Washington, D.C. 20375-5339 -* -* License for source code at https://simdis.nrl.navy.mil/License.aspx -* -* The U.S. Government retains all rights to use, duplicate, distribute, -* disclose, or release this software. -* -*/ -#include "osg/Point" -#include "osg/StateSet" -#include "osgEarth/Capabilities" -#include "osgEarth/Registry" -#include "osgEarth/VirtualProgram" -#include "simVis/Shaders.h" -#include "simVis/PointSize.h" - -namespace simVis { - -namespace -{ -const std::string POINT_SIZE_UNIFORM = "simvis_pointsize"; -} - -PointSize::PointSize() -{ -} - -PointSize::~PointSize() -{ -} - -void PointSize::installShaderProgram(osg::StateSet* intoStateSet) -{ - // Shader side: Install the shader. FFP: do nothing - if (osgEarth::Registry::capabilities().supportsGLSL(3.3f)) - { - osgEarth::VirtualProgram* vp = osgEarth::VirtualProgram::getOrCreate(intoStateSet); - simVis::Shaders shaders; - shaders.load(vp, shaders.pointSizeVertex()); - intoStateSet->setMode(GL_PROGRAM_POINT_SIZE, osg::StateAttribute::OFF); - intoStateSet->getOrCreateUniform(POINT_SIZE_UNIFORM, osg::Uniform::FLOAT)->set(1.f); - // Note that large point "rounding" to circles is not supported at this time - } -} - -void PointSize::setValues(osg::StateSet* stateset, float pointSize, int value) -{ - if (stateset == NULL) - return; - - // Need GLSL 3.3 to use point size shader, else fall back to FFP and hope for compatibility mode - const bool useShader = osgEarth::Registry::capabilities().supportsGLSL(3.3f); - if (useShader) - { - // GL 3.3 implementation uses a shader - stateset->setMode(GL_PROGRAM_POINT_SIZE, value); - osg::Uniform* uni = new osg::Uniform(POINT_SIZE_UNIFORM.c_str(), pointSize); - stateset->addUniform(uni, value); - } - else - { - // Fixed function pipeline; controlled by osg::StateAttribute - stateset->setAttributeAndModes(new osg::Point(pointSize), value); - } -} - -} diff --git a/SDK/simVis/PointSize.h b/SDK/simVis/PointSize.h deleted file mode 100644 index 425db84cf..000000000 --- a/SDK/simVis/PointSize.h +++ /dev/null @@ -1,58 +0,0 @@ -/* -*- mode: c++ -*- */ -/**************************************************************************** -***** ***** -***** Classification: UNCLASSIFIED ***** -***** Classified By: ***** -***** Declassify On: ***** -***** ***** -**************************************************************************** -* -* -* Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. -* EW Modeling & Simulation, Code 5773 -* 4555 Overlook Ave. -* Washington, D.C. 20375-5339 -* -* License for source code at https://simdis.nrl.navy.mil/License.aspx -* -* The U.S. Government retains all rights to use, duplicate, distribute, -* disclose, or release this software. -* -*/ -#ifndef SIMVIS_POINTSIZE_H -#define SIMVIS_POINTSIZE_H - -#include "osg/StateAttribute" -#include "simCore/Common/Common.h" - -namespace osg { class StateSet; } - -namespace simVis { - -/** -* OpenGL 3.3 shader implementation of PointSize. -* -* PointSize is not supported in the GL Core profile, so this is a simplified implementation of an -* point size shader that matches behavior in the fixed function pipeline (FFP) version of SIMDIS. -*/ -class SDKVIS_EXPORT PointSize -{ -public: - /** - * Before using this class, a call to installShaderProgram is required. This method installs - * the shader program and default uniform variables/defines for controlling the shader. - */ - static void installShaderProgram(osg::StateSet* intoStateSet); - - /** Static instance-less setting of point size on a state set. */ - static void setValues(osg::StateSet* stateset, float pointSize, int value = osg::StateAttribute::ON); - -protected: - /** Prevent construction/destruction, since there is no state. */ - PointSize(); - virtual ~PointSize(); -}; - -} - -#endif /* SIMVIS_POINTSIZE_H */ diff --git a/SDK/simVis/PointSize.vert.glsl b/SDK/simVis/PointSize.vert.glsl deleted file mode 100644 index 1dc4a83a1..000000000 --- a/SDK/simVis/PointSize.vert.glsl +++ /dev/null @@ -1,14 +0,0 @@ -#version 140 - -#pragma vp_entryPoint simvis_point_size -#pragma vp_location vertex_model -#pragma vp_order 1.0 - -// set a vp_order such that it executes before PointDrawable.glsl from osgEarth - -uniform float simvis_pointsize; - -void simvis_point_size(inout vec4 vertex) -{ - gl_PointSize = simvis_pointsize; -} diff --git a/SDK/simVis/Projector.cpp b/SDK/simVis/Projector.cpp index dae24dcbf..637ceefdf 100644 --- a/SDK/simVis/Projector.cpp +++ b/SDK/simVis/Projector.cpp @@ -128,7 +128,7 @@ namespace class UpdateProjMatrix : public osg::NodeCallback { public: - UpdateProjMatrix(simVis::ProjectorNode* node) : proj_(node) + explicit UpdateProjMatrix(simVis::ProjectorNode* node) : proj_(node) { //nop } @@ -207,9 +207,12 @@ void ProjectorNode::init_() projectorAlpha_ = new osg::Uniform(osg::Uniform::FLOAT, "projectorAlpha"); texProjPosUniform_ = new osg::Uniform(osg::Uniform::FLOAT_VEC3, "simProjPos"); texProjDirUniform_ = new osg::Uniform(osg::Uniform::FLOAT_VEC3, "simProjDir"); + useColorOverrideUniform_= new osg::Uniform(osg::Uniform::BOOL, "projectorUseColorOverride"); + colorOverrideUniform_ = new osg::Uniform(osg::Uniform::FLOAT_VEC4, "projectorColorOverride"); projectorActive_->set(false); projectorAlpha_->set(DEFAULT_ALPHA_VALUE); + useColorOverrideUniform_->set(false); // Set texture to default broken image texture_ = new osg::Texture2D(simVis::makeBrokenImage()); @@ -259,6 +262,26 @@ const simData::ProjectorUpdate* ProjectorNode::getLastUpdateFromDS() const return hasLastUpdate_ ? &lastUpdate_ : NULL; } +void ProjectorNode::addUniforms(osg::StateSet* stateSet) const +{ + stateSet->addUniform(projectorActive_.get()); + stateSet->addUniform(projectorAlpha_.get()); + stateSet->addUniform(texProjDirUniform_.get()); + stateSet->addUniform(texProjPosUniform_.get()); + stateSet->addUniform(useColorOverrideUniform_.get()); + stateSet->addUniform(colorOverrideUniform_.get()); +} + +void ProjectorNode::removeUniforms(osg::StateSet* stateSet) const +{ + stateSet->removeUniform(projectorActive_.get()); + stateSet->removeUniform(projectorAlpha_.get()); + stateSet->removeUniform(texProjDirUniform_.get()); + stateSet->removeUniform(texProjPosUniform_.get()); + stateSet->removeUniform(useColorOverrideUniform_.get()); + stateSet->removeUniform(colorOverrideUniform_.get()); +} + std::string ProjectorNode::popupText() const { if (hasLastUpdate_ && hasLastPrefs_) @@ -332,6 +355,8 @@ void ProjectorNode::setPrefs(const simData::ProjectorPrefs& prefs) projectorAlpha_->set(prefs.projectoralpha()); } + updateOverrideColor_(prefs); + // If override FOV changes, update the FOV with a sync-with-locator call bool syncAfterPrefsUpdate = false; if (!hasLastPrefs_ || PB_FIELD_CHANGED(&lastPrefs_, &prefs, overridefov) || @@ -349,6 +374,20 @@ void ProjectorNode::setPrefs(const simData::ProjectorPrefs& prefs) syncWithLocator(); } +void ProjectorNode::updateOverrideColor_(const simData::ProjectorPrefs& prefs) +{ + if (hasLastPrefs_ && + !PB_SUBFIELD_CHANGED(&lastPrefs_, &prefs, commonprefs, useoverridecolor) && + !PB_SUBFIELD_CHANGED(&lastPrefs_, &prefs, commonprefs, overridecolor) && + !PB_SUBFIELD_CHANGED(&lastPrefs_, &prefs, commonprefs, color)) + return; + + // using an override color? + auto color = simVis::Color(prefs.commonprefs().overridecolor(), simVis::Color::RGBA); + colorOverrideUniform_->set(color); + useColorOverrideUniform_->set(prefs.commonprefs().useoverridecolor()); +} + bool ProjectorNode::readVideoFile_(const std::string& filename) { osg::Node* result = NULL; @@ -629,10 +668,7 @@ void ProjectorNode::addProjectionToNode(osg::Node* node) // Set texture from projector into state set stateSet->setTextureAttribute(ProjectorManager::getTextureImageUnit(), getTexture()); - stateSet->addUniform(projectorActive_.get()); - stateSet->addUniform(projectorAlpha_.get()); - stateSet->addUniform(texProjDirUniform_.get()); - stateSet->addUniform(texProjPosUniform_.get()); + addUniforms(stateSet); // to compute the texture generation matrix: node->addCullCallback(projectOnNodeCallback_.get()); @@ -659,10 +695,7 @@ void ProjectorNode::removeProjectionFromNode(osg::Node* node) stateSet->removeTextureAttribute(ProjectorManager::getTextureImageUnit(), getTexture()); - stateSet->removeUniform(projectorActive_.get()); - stateSet->removeUniform(projectorAlpha_.get()); - stateSet->removeUniform(texProjDirUniform_.get()); - stateSet->removeUniform(texProjPosUniform_.get()); + removeUniforms(stateSet); } node->removeCullCallback(projectOnNodeCallback_.get()); diff --git a/SDK/simVis/Projector.frag.glsl b/SDK/simVis/Projector.frag.glsl index d26fbf8d7..92acec3b9 100644 --- a/SDK/simVis/Projector.frag.glsl +++ b/SDK/simVis/Projector.frag.glsl @@ -9,6 +9,9 @@ uniform bool projectorActive; uniform float projectorAlpha; uniform sampler2D simProjSampler; +uniform bool projectorUseColorOverride; +uniform vec4 projectorColorOverride; + in vec4 simProjTexCoord; in vec3 simProjToVert_VIEW; flat in vec3 simProjLookVector_VIEW; @@ -24,10 +27,18 @@ void sim_proj_frag(inout vec4 color) // clip to projected texture domain; otherwise the texture will project // even when the target is outside the projection frustum vec2 local = simProjTexCoord.st / simProjTexCoord.q; - if (clamp(local, 0.0, 1.0) == local) + if (clamp(local, 0.0, 1.0) == local) { vec4 textureColor = textureProj(simProjSampler, simProjTexCoord); - color.rgb = mix(color.rgb, textureColor.rgb, textureColor.a * projectorAlpha); + if (projectorUseColorOverride) + { + color.rgb = textureColor.rgb * projectorColorOverride.rgb; + color.a = textureColor.a * projectorAlpha; + } + else + { + color.rgb = mix(color.rgb, textureColor.rgb, textureColor.a * projectorAlpha); + } } } } @@ -54,10 +65,19 @@ void sim_proj_frag(inout vec4 color) { float vis = -dot(normalize(simProjToVert_VIEW), normalize(vp_Normal)); // >=0 is visible; <0 is over the horizon. if (vis <= 0) - discard; + discard; vec4 textureColor = texture(simProjSampler, local); outColor = vec4(textureColor.r, textureColor.g, textureColor.b, textureColor.a * projectorAlpha); + if (projectorUseColorOverride) + { + outColor.rgb = textureColor.rgb * projectorColorOverride.rgb; + outColor.a = textureColor.a * projectorAlpha; + } + else + { + outColor.rgb = mix(color.rgb, textureColor.rgb, textureColor.a * projectorAlpha); + } } } } diff --git a/SDK/simVis/Projector.h b/SDK/simVis/Projector.h index 971eeb51c..f6b32a743 100644 --- a/SDK/simVis/Projector.h +++ b/SDK/simVis/Projector.h @@ -111,6 +111,11 @@ class SDKVIS_EXPORT ProjectorNode : public EntityNode */ const simData::ProjectorUpdate* getLastUpdateFromDS() const; + /// Add projector uniforms to the given StateSet + void addUniforms(osg::StateSet* stateSet) const; + /// Remove projector uniforms from the given StateSet + void removeUniforms(osg::StateSet* stateSet) const; + public: // EntityNode interface /** * Whether the entity is active within the scenario at the current time. @@ -229,11 +234,11 @@ class SDKVIS_EXPORT ProjectorNode : public EntityNode osg::ref_ptr texProjPosUniform_; osg::ref_ptr texProjDirUniform_; osg::ref_ptr texProjSamplerUniform_; + osg::ref_ptr useColorOverrideUniform_; + osg::ref_ptr colorOverrideUniform_; osg::ref_ptr projectOnNodeCallback_; - friend class ProjectorManager; // manager wants access to the uniforms. - void getMatrices_( osg::Matrixd& out_projection, osg::Matrixd& out_locator, @@ -250,6 +255,8 @@ class SDKVIS_EXPORT ProjectorNode : public EntityNode /// Update label void updateLabel_(const simData::ProjectorPrefs& prefs); + /// Update override color + void updateOverrideColor_(const simData::ProjectorPrefs& prefs); }; } //namespace simVis diff --git a/SDK/simVis/ProjectorManager.cpp b/SDK/simVis/ProjectorManager.cpp index 32b2bb9b7..963ed48be 100644 --- a/SDK/simVis/ProjectorManager.cpp +++ b/SDK/simVis/ProjectorManager.cpp @@ -62,7 +62,7 @@ simData::ObjectId ProjectorManager::ProjectorLayer::id() const class UpdateProjMatrix : public osgEarth::Layer::TraversalCallback { public: - UpdateProjMatrix(ProjectorNode* node) : proj_(node) + explicit UpdateProjMatrix(ProjectorNode* node) : proj_(node) { //nop } @@ -193,10 +193,7 @@ void ProjectorManager::registerProjector(ProjectorNode* proj) // Set texture from projector into state set projStateSet->setTextureAttribute(PROJECTOR_TEXTURE_UNIT, proj->getTexture()); - projStateSet->addUniform(proj->projectorActive_.get()); - projStateSet->addUniform(proj->projectorAlpha_.get()); - projStateSet->addUniform(proj->texProjDirUniform_.get()); - projStateSet->addUniform(proj->texProjPosUniform_.get()); + proj->addUniforms(projStateSet); } void ProjectorManager::unregisterProjector(const ProjectorNode* proj) diff --git a/SDK/simVis/RFProp/Profile.cpp b/SDK/simVis/RFProp/Profile.cpp index 79241cac8..1793bc12d 100644 --- a/SDK/simVis/RFProp/Profile.cpp +++ b/SDK/simVis/RFProp/Profile.cpp @@ -21,13 +21,14 @@ */ #include "osg/MatrixTransform" #include "osg/Texture2D" +#include "osgEarth/GLUtils" #include "osgEarth/ImageUtils" +#include "osgEarth/PointDrawable" #include "osgEarth/NodeUtils" #include "simCore/Calc/Angle.h" #include "simCore/Calc/Calculations.h" #include "simCore/Calc/Interpolation.h" #include "simVis/Constants.h" -#include "simVis/PointSize.h" #include "simVis/Utils.h" #include "simVis/RFProp/CompositeProfileProvider.h" #include "simVis/RFProp/Profile.h" @@ -646,12 +647,17 @@ void Profile::init3D_() osg::Geometry* geometry = new osg::Geometry(); + osg::DrawElementsUInt* idx = new osg::DrawElementsUInt(GL_TRIANGLES); + const size_t idxSize = 36 * (maxHeightIndex - minHeightIndex) * (numRanges - 1); + idx->reserve(idxSize); + //Now build the indices that will actually be rendered for (unsigned int r = 0; r < numRanges - 1; r++) { const unsigned int nextR = r + 1; for (unsigned int h = minHeightIndex; h < maxHeightIndex; h++) { + // 36 indices / cube //Compute the indices of the 8 corners of the cube const unsigned int v0 = startIndex + r * heightIndexCount * 2 + (h - minHeightIndex) * 2; // front LR const unsigned int v1 = v0 + 1; // front LL @@ -663,33 +669,34 @@ void Profile::init3D_() const unsigned int v6 = v5 + 1; // back UR const unsigned int v7 = v6 + 1; // back UL - osg::DrawElementsUInt* idx = new osg::DrawElementsUInt(GL_TRIANGLE_STRIP); - idx->reserve(14); - // Wrap the voxel with a triangle strip - // Back Bottom - idx->push_back(v5); idx->push_back(v4); - - // Back to top - idx->push_back(v6); idx->push_back(v7); + // Front face + idx->push_back(v1); idx->push_back(v0); idx->push_back(v3); + idx->push_back(v0); idx->push_back(v2); idx->push_back(v3); - // Top to left - idx->push_back(v3); idx->push_back(v5); + // Back face + idx->push_back(v6); idx->push_back(v4); idx->push_back(v5); + idx->push_back(v7); idx->push_back(v6); idx->push_back(v5); - // Left to bottom - idx->push_back(v1); idx->push_back(v4); + // Top face + idx->push_back(v3); idx->push_back(v2); idx->push_back(v7); + idx->push_back(v2); idx->push_back(v6); idx->push_back(v7); - // Bottom to right - idx->push_back(v0); idx->push_back(v6); + // Bottom face + idx->push_back(v1); idx->push_back(v5); idx->push_back(v4); + idx->push_back(v1); idx->push_back(v4); idx->push_back(v0); - // Right to top - idx->push_back(v2); idx->push_back(v3); + // Left face + idx->push_back(v5); idx->push_back(v1); idx->push_back(v7); + idx->push_back(v1); idx->push_back(v3); idx->push_back(v7); - // Top to front - idx->push_back(v0); idx->push_back(v1); - - geometry->addPrimitiveSet(idx); + // Right face + idx->push_back(v0); idx->push_back(v4); idx->push_back(v6); + idx->push_back(v0); idx->push_back(v6); idx->push_back(v2); } } + // assertion fail means algorithm changed without correction to the reserve() + assert(idxSize == idx->size()); + geometry->addPrimitiveSet(idx); geometry->setDataVariance(osg::Object::DYNAMIC); geometry->setVertexArray(verts_.get()); @@ -988,9 +995,12 @@ void Profile::init3DPoints_() simCore::geodeticToSpherical(refCoord_.lat(), refCoord_.lon(), refCoord_.alt(), tpSphereXYZ); const unsigned int numVerts = (maxHeightIndex - minHeightIndex + 1) * numRanges; - verts_->reserve(numVerts); values_->reserve(numVerts); + osgEarth::PointDrawable* points = new osgEarth::PointDrawable(); + points->setDataVariance(osg::Object::DYNAMIC); + points->reserve(numVerts); + for (unsigned int r = 0; r < numRanges; r++) { const double range = minRange + rangeStep * r; @@ -1008,21 +1018,15 @@ void Profile::init3DPoints_() { adjustSpherical_(v, tpSphereXYZ); } - verts_->push_back(v); + points->pushVertex(v); values_->push_back(value); } } + points->setVertexAttribArray(osg::Drawable::ATTRIBUTE_6, values_.get()); + points->finish(); - osg::Geometry* geometry = new osg::Geometry(); - geometry->setDataVariance(osg::Object::DYNAMIC); - geometry->setVertexArray(verts_.get()); - geometry->setUseVertexBufferObjects(true); - - geometry->setVertexAttribArray(osg::Drawable::ATTRIBUTE_6, values_.get()); - - geometry->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, verts_->size())); - geode_->addDrawable(geometry); - simVis::PointSize::setValues(geode_->getOrCreateStateSet(), 3.f, osg::StateAttribute::ON); + geode_->addChild(points); + osgEarth::GLUtils::setPointSize(geode_->getOrCreateStateSet(), 3.f, osg::StateAttribute::ON); } osg::Image* Profile::createImage_() diff --git a/SDK/simVis/Registry.cpp b/SDK/simVis/Registry.cpp index ac42e16e7..76f75796e 100644 --- a/SDK/simVis/Registry.cpp +++ b/SDK/simVis/Registry.cpp @@ -191,6 +191,13 @@ simVis::Registry::Registry() modelExtensions_.push_back("bmp"); modelExtensions_.push_back("jpg"); + // Known OSG and SIMDIS pseudo loaders + pseudoLoaderExtensions_.insert("osgs"); // encode OSG file as filename + pseudoLoaderExtensions_.insert("pse"); // EW symbology + pseudoLoaderExtensions_.insert("rot"); // rotate model + pseudoLoaderExtensions_.insert("scale"); // scale model + pseudoLoaderExtensions_.insert("trans"); // translate model + // initialize the default NOTIFY level from the environment variable const std::string val = simCore::getEnvVar("SIM_NOTIFY_LEVEL"); if (!val.empty()) @@ -292,6 +299,16 @@ void simVis::Registry::setModelSearchExtensions(const FileExtensionList& list) modelExtensions_ = list; } +void simVis::Registry::getPseudoLoaderExtensions(std::set& out_list) const +{ + out_list = pseudoLoaderExtensions_; +} + +void simVis::Registry::setPseudoLoaderExtensions(const std::set& list) +{ + pseudoLoaderExtensions_ = list; +} + void simVis::Registry::setShareArticulatedIconModels(bool value) { modelCache_->setShareArticulatedIconModels(value); @@ -308,6 +325,14 @@ std::string simVis::Registry::findModelFile(const std::string& name) const if (it != modelFilenameCache_.end()) return it->second; + // File extension might be in the pseudo-loader list, in which case we skip the search + const std::string& extension = osgDB::getFileExtension(name); + if (pseudoLoaderExtensions_.find(extension) != pseudoLoaderExtensions_.end()) + { + modelFilenameCache_[name] = name; + return name; + } + // First check the extension std::string fileSearchName = fileSearch_->findFile(name, simCore::FileSearch::MODEL); // Fall back on osgDB if it doesn't find it @@ -322,7 +347,7 @@ std::string simVis::Registry::findModelFile(const std::string& name) const } // Now check via osgDB which has different search paths than fileSearch_ - if (osgDB::getFileExtension(name).empty()) + if (extension.empty()) { // if the name has no extension, try tacking on extensions and trying to find the paths. for (FileExtensionList::const_iterator i = modelExtensions_.begin(); i != modelExtensions_.end(); ++i) diff --git a/SDK/simVis/Registry.h b/SDK/simVis/Registry.h index d139fc986..37c95562d 100644 --- a/SDK/simVis/Registry.h +++ b/SDK/simVis/Registry.h @@ -23,6 +23,7 @@ #define SIMVIS_REGISTRY_H #include +#include #include "OpenThreads/ReentrantMutex" #include "osg/observer_ptr" #include "osg/ref_ptr" @@ -76,16 +77,28 @@ class SDKVIS_EXPORT Registry /** * Gets a copy of the list of extensions to look for when searching for a platform model. - * @param out_list List to populate with search extensions (output param) + * @param out_list List to populate with search extensions (output param); values do not have dots (e.g. "png") */ void getModelSearchExtensions(FileExtensionList& out_list) const; /** * Sets the list of extensions to look for when searching for a platform model. - * @param list Model file extensions list + * @param list Model file extensions list; values do not have dots (e.g. "png") */ void setModelSearchExtensions(const FileExtensionList& list); + /** + * Gets a copy of the list of extensions that are registered known pseudo-loader extensions, which might not represent a file on disk. + * @param out_list List to populate with pseudo loader extensions (output param); values do not have dots (e.g. "rot") + */ + void getPseudoLoaderExtensions(std::set& out_list) const; + + /** + * Sets the list of extensions that are registered known pseudo-loader extensions, which might not represent a file on disk. + * @param list Pseudo loader file extensions list; values do not have dots (e.g. "rot") + */ + void setPseudoLoaderExtensions(const std::set& list); + /** * Searches for the named model, using the model search path list and the extensions list. * This method is thread safe @@ -206,6 +219,7 @@ class SDKVIS_EXPORT Registry FilePathList modelPaths_; FileExtensionList modelExtensions_; + std::set pseudoLoaderExtensions_; ModelCache* modelCache_; // A mapping between the supplied file name and the actual file name diff --git a/SDK/simVis/Scenario.cpp b/SDK/simVis/Scenario.cpp index d066b6ca0..8f5143e5f 100644 --- a/SDK/simVis/Scenario.cpp +++ b/SDK/simVis/Scenario.cpp @@ -47,7 +47,6 @@ #include "simVis/PlatformFilter.h" #include "simVis/Platform.h" #include "simVis/PlatformModel.h" -#include "simVis/PointSize.h" #include "simVis/PolygonStipple.h" #include "simVis/Projector.h" #include "simVis/ProjectorManager.h" @@ -271,7 +270,8 @@ class ScenarioManager::SurfaceClamping : public PlatformTspiFilter if (!prefs.surfaceclamping() || !coordSurfaceClamping_.isValid()) return PlatformTspiFilterManager::POINT_UNCHANGED; - coordSurfaceClamping_.clampCoordToMapSurface(llaCoord); + osgEarth::ElevationEnvelope::Context& context = lut_[props.id()]; + coordSurfaceClamping_.clampCoordToMapSurface(llaCoord, context); return PlatformTspiFilterManager::POINT_CHANGED; } @@ -282,13 +282,21 @@ class ScenarioManager::SurfaceClamping : public PlatformTspiFilter coordSurfaceClamping_.setMapNode(map); } + /** Changes the flag for using maximum elevation precision */ void setUseMaxElevPrec(bool useMaxElev) { coordSurfaceClamping_.setUseMaxElevPrec(useMaxElev); } + /** Removes an entity from the optimization look-up table */ + void removeEntity(simData::ObjectId id) + { + lut_.erase(id); + } + private: CoordSurfaceClamping coordSurfaceClamping_; + std::map lut_; }; @@ -464,7 +472,6 @@ ScenarioManager::ScenarioManager(LocatorFactory* factory, ProjectorManager* proj LobGroupNode::installShaderProgram(stateSet); OverrideColor::installShaderProgram(stateSet); PolygonStipple::installShaderProgram(stateSet); - PointSize::installShaderProgram(stateSet); TrackHistoryNode::installShaderProgram(stateSet); } @@ -601,6 +608,9 @@ void ScenarioManager::removeEntity(simData::ObjectId id) EntityNode* entity = record->getEntityNode(); notifyToolsOfRemove_(entity); + // Remove it from the surface clamping algorithm + surfaceClamping_->removeEntity(id); + // If this is a projector node, delete this from the projector manager if (entity->type() == simData::PROJECTOR) { diff --git a/SDK/simVis/ScenarioDataStoreAdapter.cpp b/SDK/simVis/ScenarioDataStoreAdapter.cpp index 7b9bd396e..6173b2c89 100644 --- a/SDK/simVis/ScenarioDataStoreAdapter.cpp +++ b/SDK/simVis/ScenarioDataStoreAdapter.cpp @@ -63,7 +63,8 @@ class MyListener : public simData::DataStore::Listener /// entity with the given id and type will be removed after all notifications are processed virtual void onRemoveEntity(simData::DataStore *source, simData::ObjectId removedId, simData::ObjectType ot) { - scenarioManager_->removeEntity(removedId); + if (scenarioManager_.valid()) + scenarioManager_->removeEntity(removedId); } /// prefs for the given entity have been changed @@ -87,7 +88,8 @@ class MyListener : public simData::DataStore::Listener /// current time has been changed virtual void onTimeChange(simData::DataStore *source) { - scenarioManager_->update(source); + if (scenarioManager_.valid()) + scenarioManager_->update(source); } /// something has changed in the entity category data @@ -105,7 +107,8 @@ class MyListener : public simData::DataStore::Listener /// entity's data was flushed, 0 means entire scenario was flushed virtual void onFlush(simData::DataStore *source, simData::ObjectId flushedId) { - scenarioManager_->flush(flushedId); + if (scenarioManager_.valid()) + scenarioManager_->flush(flushedId); } /// The scenario is about to be deleted @@ -117,6 +120,8 @@ class MyListener : public simData::DataStore::Listener private: // methods void addPlatform_(simData::DataStore &ds, simData::ObjectId newId) const { + if (!scenarioManager_.valid()) + return; simData::PlatformProperties props; simData::DataStore::Transaction xaction; const simData::PlatformProperties *liveProps = ds.platformProperties(newId, &xaction); @@ -129,6 +134,8 @@ class MyListener : public simData::DataStore::Listener void addBeam_(simData::DataStore &ds, simData::ObjectId newId) const { + if (!scenarioManager_.valid()) + return; simData::BeamProperties props; simData::DataStore::Transaction xaction; @@ -142,6 +149,8 @@ class MyListener : public simData::DataStore::Listener void addGate_(simData::DataStore &ds, simData::ObjectId newId) const { + if (!scenarioManager_.valid()) + return; simData::GateProperties props; simData::DataStore::Transaction xaction; const simData::GateProperties *liveProps = ds.gateProperties(newId, &xaction); @@ -154,6 +163,8 @@ class MyListener : public simData::DataStore::Listener void addProjector_(simData::DataStore &ds, simData::ObjectId newId) const { + if (!scenarioManager_.valid()) + return; simData::ProjectorProperties props; simData::DataStore::Transaction xaction; const simData::ProjectorProperties *liveProps = ds.projectorProperties(newId, &xaction); @@ -166,6 +177,8 @@ class MyListener : public simData::DataStore::Listener void addLaser_(simData::DataStore &ds, simData::ObjectId newId) const { + if (!scenarioManager_.valid()) + return; simData::LaserProperties props; simData::DataStore::Transaction xaction; const simData::LaserProperties *liveProps = ds.laserProperties(newId, &xaction); @@ -178,6 +191,8 @@ class MyListener : public simData::DataStore::Listener void addLobGroup_(simData::DataStore &ds, simData::ObjectId newId) const { + if (!scenarioManager_.valid()) + return; simData::LobGroupProperties props; simData::DataStore::Transaction xaction; const simData::LobGroupProperties *liveProps = ds.lobGroupProperties(newId, &xaction); @@ -190,6 +205,8 @@ class MyListener : public simData::DataStore::Listener void addCustomRendering_(simData::DataStore &ds, simData::ObjectId newId) const { + if (!scenarioManager_.valid()) + return; simData::CustomRenderingProperties props; simData::DataStore::Transaction xaction; const simData::CustomRenderingProperties *liveProps = ds.customRenderingProperties(newId, &xaction); @@ -202,6 +219,8 @@ class MyListener : public simData::DataStore::Listener void changePlatformPrefs_(simData::DataStore &ds, simData::ObjectId id) { + if (!scenarioManager_.valid()) + return; simData::PlatformPrefs prefs; simData::DataStore::Transaction xaction; const simData::PlatformPrefs* livePrefs = ds.platformPrefs(id, &xaction); @@ -213,6 +232,8 @@ class MyListener : public simData::DataStore::Listener void changeBeamPrefs_(simData::DataStore &ds, simData::ObjectId id) { + if (!scenarioManager_.valid()) + return; simData::BeamPrefs prefs; simData::DataStore::Transaction xaction; const simData::BeamPrefs* livePrefs = ds.beamPrefs(id, &xaction); @@ -224,6 +245,8 @@ class MyListener : public simData::DataStore::Listener void changeGatePrefs_(simData::DataStore &ds, simData::ObjectId id) { + if (!scenarioManager_.valid()) + return; simData::GatePrefs prefs; simData::DataStore::Transaction xaction; const simData::GatePrefs* livePrefs = ds.gatePrefs(id, &xaction); @@ -235,6 +258,8 @@ class MyListener : public simData::DataStore::Listener void changeProjectorPrefs_(simData::DataStore &ds, simData::ObjectId id) { + if (!scenarioManager_.valid()) + return; simData::ProjectorPrefs prefs; simData::DataStore::Transaction xaction; const simData::ProjectorPrefs* livePrefs = ds.projectorPrefs(id, &xaction); @@ -246,6 +271,8 @@ class MyListener : public simData::DataStore::Listener void changeLaserPrefs_(simData::DataStore &ds, simData::ObjectId id) { + if (!scenarioManager_.valid()) + return; simData::LaserPrefs prefs; simData::DataStore::Transaction xaction; const simData::LaserPrefs* livePrefs = ds.laserPrefs(id, &xaction); @@ -257,6 +284,8 @@ class MyListener : public simData::DataStore::Listener void changeLobGroupPrefs_(simData::DataStore &ds, simData::ObjectId id) { + if (!scenarioManager_.valid()) + return; simData::LobGroupPrefs prefs; simData::DataStore::Transaction xaction; const simData::LobGroupPrefs* livePrefs = ds.lobGroupPrefs(id, &xaction); @@ -268,6 +297,8 @@ class MyListener : public simData::DataStore::Listener void changeCustomRenderingPrefs_(simData::DataStore &ds, simData::ObjectId id) { + if (!scenarioManager_.valid()) + return; simData::CustomRenderingPrefs prefs; simData::DataStore::Transaction xaction; const simData::CustomRenderingPrefs* livePrefs = ds.customRenderingPrefs(id, &xaction); @@ -278,7 +309,7 @@ class MyListener : public simData::DataStore::Listener } private: // data - simVis::ScenarioManager *scenarioManager_; + osg::observer_ptr scenarioManager_; }; // Observer for time clock mode changes diff --git a/SDK/simVis/SceneManager.cpp b/SDK/simVis/SceneManager.cpp index c6a9c1c08..4effc5c4c 100644 --- a/SDK/simVis/SceneManager.cpp +++ b/SDK/simVis/SceneManager.cpp @@ -462,9 +462,9 @@ void SceneManager::applyImageLayerDisplaySettings_(const osgEarth::ImageLayer& s destLayer->setEnabled(sourceLayer.getEnabled()); } -std::string SceneManager::getLayerHash_(osgEarth::TerrainLayer* layer) const +std::string SceneManager::getLayerHash_(osgEarth::TileLayer* layer) const { - // This method mimics the logic in osgEarth::TerrainLayer::setCache for generating a unique id for the layer + // This method mimics the logic in osgEarth::TileLayer::setCache for generating a unique id for the layer // system will generate a cacheId. technically, this is not quite right, we need to remove everything that's // an image layer property and just use the tilesource properties. diff --git a/SDK/simVis/SceneManager.h b/SDK/simVis/SceneManager.h index 45f2b3ffe..dd5e1f112 100644 --- a/SDK/simVis/SceneManager.h +++ b/SDK/simVis/SceneManager.h @@ -195,8 +195,8 @@ namespace simVis void init_(); /** Detects engine driver problems and sets internal state appropriately */ void detectTerrainEngineDriverProblems_(); - /** Returns the unique hash identifying the layer. Taken from osgEarth::TerrainLayer::setCache method */ - std::string getLayerHash_(osgEarth::TerrainLayer* layer) const; + /** Returns the unique hash identifying the layer. Taken from osgEarth::TileLayer::setCache method */ + std::string getLayerHash_(osgEarth::TileLayer* layer) const; /** Replace image layers in currentMap with image layers in newMap, unless the layer already exists in currentMap. Removes old layers not in newMap */ void updateImageLayers_(const osgEarth::Map& newMap, osgEarth::Map* currentMap); /** Replace elevation layers in currentMap with image layers in newMap, unless the layer already exists in currentMap. Removes old layers not in newMap */ diff --git a/SDK/simVis/SimdisMeasurement.cpp b/SDK/simVis/SimdisMeasurement.cpp index 9fe413f37..7963a5932 100644 --- a/SDK/simVis/SimdisMeasurement.cpp +++ b/SDK/simVis/SimdisMeasurement.cpp @@ -349,7 +349,7 @@ double HorizonMeasurement::calcAboveHorizon_(RangeToolState& state, simCore::Hor osgEarth::GeoPoint currGeoPoint(state.mapNode_->getMapSRS()->getGeographicSRS(), 0, 0, 0, osgEarth::ALTMODE_ABSOLUTE); // Iterate over the points, sampling the elevation at each until the target becomes invisible: - for (std::vector::const_iterator iter = points.begin(); iter != points.end(); iter++) + for (std::vector::const_iterator iter = points.begin(); iter != points.end(); ++iter) { currGeoPoint.x() = iter->lon() * simCore::RAD2DEG; currGeoPoint.y() = iter->lat() * simCore::RAD2DEG; diff --git a/SDK/simVis/SphericalVolume.cpp b/SDK/simVis/SphericalVolume.cpp index 2c8d17822..21069bf74 100644 --- a/SDK/simVis/SphericalVolume.cpp +++ b/SDK/simVis/SphericalVolume.cpp @@ -35,7 +35,6 @@ #include "simCore/Calc/Angle.h" #include "simCore/Calc/Math.h" #include "simVis/Constants.h" -#include "simVis/PointSize.h" #include "simVis/PolygonStipple.h" #include "simVis/SphericalVolume.h" #include "simVis/Types.h" @@ -560,8 +559,6 @@ namespace void svPyramidFactory::generateFaces_(osg::Geometry* faceGeom) { - std::vector& vertexMetaData = metaContainer_->vertMeta_; - // if we are drawing the face (not just the outline) add primitives that index into the vertex array const unsigned int numFaceElements = 2 * numPointsZ_; @@ -962,7 +959,6 @@ void SVFactory::createCone_(osg::Geode* geode, const SVData& d, const osg::Vec3& { // next, build the cone wall. we need out-facing normals. // yes this can be computed while we are building the faces but that is an optimization for later. - const int wallOffset = vptr; // ensure that cone is aligned to cap, since cap is drawn normally, but cone is drawn in alternating strips from bottom. bool evenSlice = ((numSlices % 2) == 0); @@ -1059,13 +1055,6 @@ void SVFactory::createCone_(osg::Geode* geode, const SVData& d, const osg::Vec3& // asserting that we used all the vertices we expected to // if assert fails, check numVerts calculation assert(numVerts == vptr); - - // highlights the face points for a visual effect: - if (SVData::DRAW_MODE_POINTS & d.drawMode_) - { - geom->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, wallOffset)); - PointSize::setValues(geom->getOrCreateStateSet(), 3.f, osg::StateAttribute::ON); - } } } diff --git a/SDK/simVis/SphericalVolume.h b/SDK/simVis/SphericalVolume.h index 2c0d5036f..415f882b3 100644 --- a/SDK/simVis/SphericalVolume.h +++ b/SDK/simVis/SphericalVolume.h @@ -49,8 +49,7 @@ struct SVData DRAW_MODE_SOLID = 1 << 0, ///< solid (probably translucent) DRAW_MODE_STIPPLE = 1 << 1, ///< stippled (many small dots) DRAW_MODE_WIRE = 1 << 2, ///< wireframe - DRAW_MODE_OUTLINE = 1 << 3, ///< outlines only - DRAW_MODE_POINTS = 1 << 4 ///< points at the key locations + DRAW_MODE_OUTLINE = 1 << 3 ///< outlines only }; /** Draw as a pyramid or cone */ diff --git a/SDK/simVis/TimeTicks.cpp b/SDK/simVis/TimeTicks.cpp new file mode 100644 index 000000000..93549a90b --- /dev/null +++ b/SDK/simVis/TimeTicks.cpp @@ -0,0 +1,759 @@ +/* -*- mode: c++ -*- */ +/**************************************************************************** + ***** ***** + ***** Classification: UNCLASSIFIED ***** + ***** Classified By: ***** + ***** Declassify On: ***** + ***** ***** + **************************************************************************** + * + * + * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. + * EW Modeling & Simulation, Code 5773 + * 4555 Overlook Ave. + * Washington, D.C. 20375-5339 + * + * License for source code at https://simdis.nrl.navy.mil/License.aspx + * + * The U.S. Government retains all rights to use, duplicate, distribute, + * disclose, or release this software. + * + */ +#include + +#include "osg/Depth" +#include "osgText/Text" +#include "osgEarth/Capabilities" +#include "osgEarth/Horizon" +#include "osgEarth/LabelNode" +#include "osgEarth/LineDrawable" +#include "osgEarth/Registry" +#include "osgEarth/VirtualProgram" + +#include "simCore/Calc/Angle.h" +#include "simCore/Calc/Calculations.h" +#include "simCore/Calc/Math.h" +#include "simCore/Time/TimeClass.h" +#include "simCore/Time/String.h" +#include "simData/DataTable.h" +#include "simVis/AlphaTest.h" +#include "simVis/Constants.h" +#include "simVis/Locator.h" +#include "simVis/OverheadMode.h" +#include "simVis/PlatformFilter.h" +#include "simVis/Registry.h" +#include "simVis/Shaders.h" +#include "simVis/TimeTicksChunk.h" +#include "simVis/Types.h" +#include "simVis/Utils.h" +#include "simVis/TimeTicks.h" + +namespace simVis +{ + +namespace +{ + // TODO: these will be prefs, SIM-4428 + static const double TICK_WIDTH = 40; + static const int LABEL_FONT_SIZE = 30; + + // follow track history flat mode + static const std::string SIMVIS_TIMETICKS_TRACK_FLATMODE = "simvis_track_flatmode"; +} + + +/// constructor. +TimeTicks::TimeTicks(const simData::DataStore& ds, const osgEarth::SpatialReference* srs, PlatformTspiFilterManager& platformTspiFilterManager, simData::ObjectId entityId) + : ds_(ds), + supportsShaders_(osgEarth::Registry::capabilities().supportsGLSL(3.3f)), + chunkSize_(64), // keep this lowish or your app won't scale. + color_(osg::Vec4f(1.0, 1.0, 1.0, 0.5)), + totalPoints_(0), + singlePoint_(false), + hasLastDrawTime_(false), + lastDrawTime_(0.0), + lastCurrentTime_(-1.0), + lastLargeTickTime_(-1.0), + largeTickInterval_(0.0), + lastLabelTime_(-1.0), + labelInterval_(0.0), + timeDirection_(simCore::FORWARD), + chunkGroup_(NULL), + labelGroup_(NULL), + platformTspiFilterManager_(platformTspiFilterManager), + entityId_(entityId), + currentPointChunk_(NULL) +{ + updateSliceBase_ = ds_.platformUpdateSlice(entityId); + assert(updateSliceBase_); // should be a valid update slice before time tick is created + + locator_ = new simVis::Locator(srs); + + setNodeMask(simVis::DISPLAY_MASK_TRACK_HISTORY); + + reset(); + + // configure the local state set + simVis::setLighting(getOrCreateStateSet(), 0); + + // flatten in overhead mode. + simVis::OverheadMode::enableGeometryFlattening(true, this); +} + +TimeTicks::~TimeTicks() +{ + chunkGroup_ = NULL; + labelGroup_ = NULL; + currentPointChunk_ = NULL; + locator_ = NULL; +} + +void TimeTicks::reset() +{ + // blow everything away + removeChildren(0, getNumChildren()); + labels_.clear(); + + hasLastDrawTime_ = false; + lastCurrentTime_ = -1.0; + totalPoints_ = 0; + chunkGroup_ = new osg::Group(); + labelGroup_ = new osg::Group(); + addChild(labelGroup_); + addChild(chunkGroup_); + currentPointChunk_ = NULL; + lastLargeTickTime_ = -1.0; + lastLabelTime_ = -1.0; + singlePoint_ = false; +} + +TimeTicksChunk* TimeTicks::getCurrentChunk_() +{ + // see if there's already a chunk we can use. + unsigned int num = chunkGroup_->getNumChildren(); + if (num > 0) + { + TimeTicksChunk* chunk = static_cast(chunkGroup_->getChild(num - 1)); + if (!chunk->isFull()) + { + // yes. + return chunk; + } + } + return NULL; +} + +TimeTicksChunk* TimeTicks::getLastChunk_() +{ + unsigned int num = chunkGroup_->getNumChildren(); + if (num > 0) + { + TimeTicksChunk* chunk = static_cast(chunkGroup_->getChild(num - 1)); + return chunk; + } + return NULL; +} + +TimeTicksChunk* TimeTicks::getFirstChunk_() +{ + unsigned int num = chunkGroup_->getNumChildren(); + if (num > 0) + { + TimeTicksChunk* chunk = static_cast(chunkGroup_->getChild(0)); + return chunk; + } + return NULL; +} + +double TimeTicks::toDrawTime_(double updateTime) const +{ + return updateTime * ((timeDirection_ == simCore::REVERSE) ? -1.0 : 1.0); +} + +void TimeTicks::addUpdate_(double tickTime) +{ + const simData::PlatformUpdateSlice* updateSlice = static_cast(updateSliceBase_); + if (updateSlice == NULL) + { + // a valid/active platform must have an updateSlice - if assert fails, ensure that time ticks is not being updated for a non valid platform + assert(0); + return; + } + auto iter = updateSlice->lower_bound(tickTime); + if (!iter.hasNext()) + return; + + bool hasPrevious = iter.hasPrevious(); + auto prevIter = iter; + const simData::PlatformUpdate* prev = prevIter.previous(); + const simData::PlatformUpdate* update = iter.next(); + osg::Matrix hostMatrix; + bool largeTick = false; + + // if tick is at first platform point, use that for position + if (!hasPrevious) + { + // if only a single point, use it + const simData::PlatformUpdate* next = iter.next(); + if (!next) + { + if (!getMatrix_(*update, hostMatrix)) + return; + // if drawing line ticks, set singlePoint_ flag since orientation may not be correct and will need to update once next point comes in + // this may occur in live mode + if (lastPlatformPrefs_.trackprefs().timeticks().drawstyle() == simData::TimeTickPrefs::LINE) + singlePoint_ = true; + } + // use the next point to calculate the correct orientation for the first tick + else + { + if (!getMatrix_(*next, *update, tickTime, hostMatrix)) + return; + singlePoint_ = false; + } + } + // not first tick, or not at first platform position, get the next position, possibly interpolated + else + { + singlePoint_ = false; + if (!getMatrix_(*prev, *update, tickTime, hostMatrix)) + return; + } + + // check to see if it is time for the next large tick + if (largeTickInterval_ > 0 && (lastLargeTickTime_ == -1 || abs(tickTime - lastLargeTickTime_) >= largeTickInterval_)) + { + lastLargeTickTime_ = tickTime; + largeTick = true; + } + + // add label for large tick + if (labelInterval_ > 0 && (lastLabelTime_ == -1.0 || abs(tickTime - lastLabelTime_) >= labelInterval_)) + { + lastLabelTime_ = tickTime; + + // get formatted time string + int refYear = 1970; + simData::DataStore::Transaction t; + const simData::ScenarioProperties* sp = ds_.scenarioProperties(&t); + if (sp) + refYear = sp->referenceyear(); + simCore::TimeStamp textTime(refYear, tickTime); + std::string labelText; + const simData::TimeTickPrefs& timeTicks = lastPlatformPrefs_.trackprefs().timeticks(); + const simData::ElapsedTimeFormat timeFormat = timeTicks.labeltimeformat(); + // show HH:MM:SS + if (timeFormat == simData::ELAPSED_HOURS) + { + simCore::HoursWrappedTimeFormatter formatter; + labelText = formatter.toString(textTime, refYear, 0); + } + + // show MM:SS + else if (timeFormat == simData::ELAPSED_MINUTES) + { + simCore::MinutesWrappedTimeFormatter formatter; + labelText = formatter.toString(textTime, refYear, 0); + } + + // show SS + else + { + simCore::SecondsTimeFormatter formatter; + labelText = formatter.toString(textTime, refYear, 0); + } + + osg::MatrixTransform* xform = new osg::MatrixTransform(); + osgText::Text* text = new osgText::Text(); + text->setPosition(osg::Vec3(0, 0, 0)); + text->setText(labelText); + std::string fileFullPath = simVis::Registry::instance()->findFontFile(timeTicks.labelfontname()); + if (!fileFullPath.empty()) // only set if font file found, use default font otherwise + text->setFont(fileFullPath); + else + text->setFont(osgEarth::Registry::instance()->getDefaultFont()); + text->setAutoRotateToScreen(true); + text->setCharacterSizeMode(osgText::TextBase::SCREEN_COORDS); + text->setAlignment(osgText::TextBase::RIGHT_BOTTOM); + text->setBackdropType(osgText::Text::DROP_SHADOW_BOTTOM_RIGHT); + text->setCharacterSize(timeTicks.labelfontpointsize()); + text->getOrCreateStateSet()->setRenderBinDetails(simVis::BIN_LABEL, simVis::BIN_TRAVERSAL_ORDER_SIMSDK); + osg::Depth* noDepthTest = new osg::Depth(osg::Depth::ALWAYS, 0, 1, false); + text->getOrCreateStateSet()->setAttributeAndModes(noDepthTest, 1); + text->setColor(color_); + xform->addChild(text); + xform->setMatrix(hostMatrix); + labelGroup_->addChild(xform); + labels_[toDrawTime_(tickTime)] = xform; + } + + // get a chunk to which to add the new point, creating a new one if necessary + TimeTicksChunk* chunk = getCurrentChunk_(); + if (!chunk) + { + // allocate a new chunk + const simData::TimeTickPrefs& timeTicks = lastPlatformPrefs_.trackprefs().timeticks(); + TimeTicksChunk::Type type = ((timeTicks.drawstyle() == simData::TimeTickPrefs::POINT) ? TimeTicksChunk::POINT_TICKS : TimeTicksChunk::LINE_TICKS); + chunk = new TimeTicksChunk(chunkSize_, type, timeTicks.linelength() / 2, timeTicks.linewidth(), timeTicks.largesizefactor()); + + // if there is a preceding chunk, duplicate its last point so there is no + // discontinuity from previous chunk to this new chunk - this matters for line drawing mode + int numc = chunkGroup_->getNumChildren(); + if (numc > 0) + { + osg::Matrix last; + if (getLastChunk_()->getEndMatrix(last) == 0) + { + double last_t = getLastChunk_()->getEndTime(); + chunk->addPoint(last, last_t, color_, largeTick); + } + } + + // add the new chunk and update its appearance + chunkGroup_->addChild(chunk); + chunk->addCullCallback(new osgEarth::HorizonCullCallback()); + } + + const double drawTime = toDrawTime_(tickTime); + + // add the point (along with its timestamp) + bool addSuccess = chunk->addPoint(hostMatrix, drawTime, color_, largeTick); + if (!addSuccess) + { + // if assert fails, check that getCurrentChunk_ and previous code ensure that either chunk is not full, or new chunk created + assert(0); + } + else + totalPoints_++; + + // record time of last draw update - must be an actual point time that can be found in the chunk + // in forward mode, lastDrawTime_ represents the newest time tick + // in reverse mode, lastDrawTime_ represents the earliest time tick + lastDrawTime_ = drawTime; + hasLastDrawTime_ = true; + +} + +void TimeTicks::updateClockMode(const simCore::Clock* clock) +{ + // STOP does not require any change in time ticks + if (clock->timeDirection() == simCore::STOP) + return; + + // we only care about fwd-rev, rev-fwd, including fwd-stop-rev and rev-stop-fwd + if (timeDirection_ != clock->timeDirection()) + { + // clear time ticks + reset(); + timeDirection_ = clock->timeDirection(); + update(); + } +} + +void TimeTicks::removePointsOlderThan_(double oldestDrawTime) +{ + if (!labels_.empty()) + { + auto iter = labels_.begin(); + while (!labels_.empty() && iter->first < oldestDrawTime) + { + labelGroup_->removeChild(iter->second); + labels_.erase(iter); + iter = labels_.begin(); + } + } + while (chunkGroup_->getNumChildren() > 0) + { + TimeTicksChunk* oldest = static_cast(chunkGroup_->getChild(0)); + unsigned int numRemoved = oldest->removePointsBefore(oldestDrawTime); + totalPoints_ -= numRemoved; + if (oldest->size() == 0) + { + chunkGroup_->removeChild(0, 1); + if (chunkGroup_->getNumChildren() > 0) + { + // Last point was duplicated to prevent discontinuity, remove it + static_cast(chunkGroup_->getChild(0))->removeOldestPoint(); + } + else // removal logic is faulty in chunk + assert(totalPoints_ == 0); + } + else + break; + } +} + +void TimeTicks::updateVisibility_(const simData::TrackPrefs& prefs) +{ + const bool invisible = (prefs.trackdrawmode() == simData::TrackPrefs::OFF); + setNodeMask(invisible ? simVis::DISPLAY_MASK_NONE : simVis::DISPLAY_MASK_TRACK_HISTORY); +} + +void TimeTicks::updateFlatMode_(bool flatMode) +{ + if (!supportsShaders_) + return; + + if (!flatModeUniform_.valid()) + { + if (flatMode == false) + return; // Does not exist and not needed so return; + + osg::StateSet* stateset = this->getOrCreateStateSet(); + flatModeUniform_ = stateset->getOrCreateUniform(SIMVIS_TIMETICKS_TRACK_FLATMODE, osg::Uniform::BOOL); + } + + flatModeUniform_->set(flatMode); +} + +void TimeTicks::setPrefs(const simData::PlatformPrefs& platformPrefs, const simData::PlatformProperties& platformProps, bool force) +{ + const simData::TrackPrefs& prefs = platformPrefs.trackprefs(); + // lastPlatformPrefs_ will not have data that represents current state on initial call + // force should be true in this case; + // in any case, if force is set, we should not test on lastPlatformPrefs_ + const simData::TrackPrefs& lastPrefs = lastPlatformPrefs_.trackprefs(); + const simData::TimeTickPrefs& timeTicks = prefs.timeticks(); + const simData::TimeTickPrefs& lastTimeTicks = lastPrefs.timeticks(); + + // platform should be deleting track when trackdrawmode turned off, this should never be called with trackdrawmode off + // if assert fails, check platform setPrefs logic that processes prefs.trackprefs().trackdrawmode() + assert(prefs.trackdrawmode() != simData::TrackPrefs_Mode_OFF); + bool resetRequested = false; + + if (force || PB_FIELD_CHANGED(&lastPrefs, &prefs, tracklength)) + { + // clear the time ticks and recreate + resetRequested = true; + } + + // check for override color + if (force || PB_FIELD_CHANGED(&lastPrefs, &prefs, usetrackoverridecolor) || PB_FIELD_CHANGED(&lastPrefs, &prefs, trackoverridecolor)) + { + resetRequested = true; + if (prefs.usetrackoverridecolor()) + color_ = simVis::Color(prefs.trackoverridecolor(), simVis::Color::RGBA); + else + color_ = simVis::Color(timeTicks.color(), simVis::Color::RGBA); + } + + if (force || PB_FIELD_CHANGED(&lastPrefs, &prefs, flatmode)) + { + updateFlatMode_(prefs.flatmode()); + } + + // check for any clamping changes, which will redraw ticks + if (force || PB_FIELD_CHANGED(&lastPlatformPrefs_, &platformPrefs, useclampalt) || + PB_FIELD_CHANGED(&lastPlatformPrefs_, &platformPrefs, clampvalaltmin) || + PB_FIELD_CHANGED(&lastPlatformPrefs_, &platformPrefs, clampvalaltmax) || + PB_FIELD_CHANGED(&lastPlatformPrefs_, &platformPrefs, surfaceclamping)) + { + // Did not test for the clamped angles since they are intended for stationary platforms + resetRequested = true; + } + + if (force || PB_FIELD_CHANGED(&lastTimeTicks, &timeTicks, linewidth)) + { + osg::StateSet* stateSet = this->getOrCreateStateSet(); + osgEarth::LineDrawable::setLineWidth(stateSet, timeTicks.linewidth()); + // need to redraw points if line width changed + if (timeTicks.drawstyle() == simData::TimeTickPrefs::POINT) + resetRequested = true; + } + + // use tick color if not overridden by track color + if ((force || PB_FIELD_CHANGED(&lastTimeTicks, &timeTicks, color)) && !prefs.usetrackoverridecolor()) + { + color_ = simVis::Color(timeTicks.color(), simVis::Color::RGBA); + resetRequested = true; + } + + if (force || PB_FIELD_CHANGED(&lastTimeTicks, &timeTicks, interval) || + PB_FIELD_CHANGED(&lastTimeTicks, &timeTicks, largeintervalfactor) || + PB_FIELD_CHANGED(&lastTimeTicks, &timeTicks, labelintervalfactor)) + { + double interval = timeTicks.interval(); + largeTickInterval_ = interval * timeTicks.largeintervalfactor(); + labelInterval_ = interval * timeTicks.labelintervalfactor(); + resetRequested = true; + } + // check on other changes that could force a redraw + if (force || PB_FIELD_CHANGED(&lastTimeTicks, &timeTicks, drawstyle) || + PB_FIELD_CHANGED(&lastTimeTicks, &timeTicks, linelength) || + PB_FIELD_CHANGED(&lastTimeTicks, &timeTicks, largesizefactor) || + PB_FIELD_CHANGED(&lastTimeTicks, &timeTicks, labelfontname) || + PB_FIELD_CHANGED(&lastTimeTicks, &timeTicks, labelfontpointsize) || + PB_FIELD_CHANGED(&lastTimeTicks, &timeTicks, labeltimeformat)) + { + resetRequested = true; + } + + lastPlatformPrefs_ = platformPrefs; + lastPlatformProps_ = platformProps; + + if (resetRequested) + { + reset(); + update(); + } + updateVisibility_(prefs); +} + +void TimeTicks::update() +{ + // tracklength 0 means no time ticks are shown + if (lastPlatformPrefs_.trackprefs().tracklength() == 0) + return; + + const simData::PlatformUpdateSlice* updateSlice = static_cast(updateSliceBase_); + if (updateSlice == NULL) + { + // a valid/active platform must have an updateSlice - if assert fails, ensure that time ticks is not being updated for a non valid platform + assert(0); + return; + } + + // if the current is not valid, and scenario is prior to first update time, nothing to do + if (updateSlice->current() == NULL && ds_.updateTime() < updateSlice->firstTime()) + { + // platform is not valid, this should only occur during platform creation + return; + } + + // ignore static platforms + if (updateSlice->current() && updateSlice->current()->time() == -1) + { + // time ticks should never be created for a static platform. - see PlatformNode::createTimeTicks_ + assert(0); + return; + } + + // update time ticks to match current time window + updateTrackData_(ds_.updateTime(), *updateSlice); +} + +void TimeTicks::updateTrackData_(double currentTime, const simData::PlatformUpdateSlice& updateSlice) +{ + // determine the time window that time ticks should display + double endTime = currentTime; + double beginTime = updateSlice.firstTime(); + int trackLength = lastPlatformPrefs_.trackprefs().tracklength(); + if (trackLength > 0 && (endTime - trackLength) > beginTime) + beginTime = endTime - trackLength; + + // first tick was built from a single point, so orientation may have been off, reset to ensure first tick is drawn with correct orientation + if (singlePoint_ && updateSlice.numItems() > 1) + reset(); + + // if there is an existing time ticks, determine if we can add only new points; this should be the case for normal time movement + if (hasLastDrawTime_) + { + if (timeDirection_ == simCore::FORWARD) + { + // backward jump (e.g. time slider move) while in forward mode in time requires reset() + if (currentTime < lastCurrentTime_) + { + reset(); + // assume now going in reverse, so switch direction + timeDirection_ = simCore::REVERSE; + } + else + { + // enforce tracklength/data limiting prefs - remove all points older than new begin time + removePointsOlderThan_(beginTime); + // if new window overlaps previous window, then reuse existing points, only add the new points + if (lastDrawTime_ >= beginTime) + { + // adjust beginTime so that we add only new points, avoid re-adding points that are still in the history + beginTime = FLT_EPSILON + lastDrawTime_; + } + } + } + else if (timeDirection_ == simCore::REVERSE) + { + // forward jump in time (e.g. time slider move) while in reverse mode requires reset + if (currentTime > lastCurrentTime_) + { + reset(); + // assume now going forward, so switch direction + timeDirection_ = simCore::FORWARD; + } + else + { + // remove all points with drawtime "older" than reverse mode end drawtime; i.e., remove all points with time newer than current time + removePointsOlderThan_(toDrawTime_(endTime)); + // if new window overlaps previous window, then reuse existing points, only add the new points + if (toDrawTime_(lastDrawTime_) <= endTime) + { + // adjust endTime so that we add only new points, and avoid re-adding points that are still in the history + endTime = -FLT_EPSILON + toDrawTime_(lastDrawTime_); + } + } + } + } + // check last draw time again, since it may have been updated in the block above + if (!hasLastDrawTime_) + { + // time ticks should all be referenced from 0:00:00.000 + double firstTime = 0.0; + + // update begin time to always count up to a valid draw time from first time in case data limiting is occurring + const double interval = lastPlatformPrefs_.trackprefs().timeticks().interval(); + if (beginTime != firstTime) + { + const double beginSpan = beginTime - firstTime; + double numIntervals = floor(beginSpan / interval); + beginTime = firstTime + (numIntervals * interval); + beginTime += interval; + + if (timeDirection_ == simCore::FORWARD) + { + // update last large tick time and labels to ensure they are always drawn consistently + numIntervals = floor(beginSpan / largeTickInterval_); + lastLargeTickTime_ = firstTime + (numIntervals * largeTickInterval_); + numIntervals = floor(beginSpan / labelInterval_); + lastLabelTime_ = firstTime + (numIntervals * labelInterval_); + } + } + if (timeDirection_ == simCore::REVERSE) + { + // set the end tick time to ensure it is at a valid interval from the first time + const double endSpan = endTime - firstTime; + double numIntervals = floor(endSpan / interval); + endTime = firstTime + (numIntervals * interval); + + // update last large tick time and labels to ensure they are always drawn consistently, make sure they are one interval past the current valid end time + numIntervals = floor(endSpan / largeTickInterval_); + lastLargeTickTime_ = firstTime + (numIntervals * largeTickInterval_); + lastLargeTickTime_ += largeTickInterval_; + numIntervals = floor(endSpan / labelInterval_); + lastLabelTime_ = firstTime + (numIntervals * labelInterval_); + lastLabelTime_ += labelInterval_; + } + } + + // store currentTime to enable time jump detection + lastCurrentTime_ = currentTime; + + // update time ticks with points in the requested window + backfillHistory_(endTime, beginTime); +} + +void TimeTicks::backfillHistory_(double endTime, double beginTime) +{ + double interval = lastPlatformPrefs_.trackprefs().timeticks().interval(); + if (timeDirection_ == simCore::FORWARD) + { + double tickTime = beginTime; + // if there is already a chunk, start counting from its end time + simVis::TimeTicksChunk* lastChunk = getLastChunk_(); + if (lastChunk) + tickTime = lastChunk->getEndTime() + interval; + + while (tickTime <= endTime) + { + addUpdate_(tickTime); + tickTime += interval; + } + } + else + { + double tickTime = endTime; + // if there is already a chunk, start counting from its end time + simVis::TimeTicksChunk* lastChunk = getLastChunk_(); + if (lastChunk) + tickTime = toDrawTime_(lastChunk->getEndTime()) - interval; + + while (tickTime >= beginTime) + { + addUpdate_(tickTime); + tickTime -= interval; + } + } +} + +bool TimeTicks::getMatrix_(const simData::PlatformUpdate& u, osg::Matrix& hostMatrix) +{ + simData::PlatformUpdate update = u; + if (platformTspiFilterManager_.filter(update, lastPlatformPrefs_, lastPlatformProps_) == PlatformTspiFilterManager::POINT_DROPPED) + return false; + + // update our locator for the current update + simCore::Coordinate ecefCoord( + simCore::COORD_SYS_ECEF, + simCore::Vec3(update.x(), update.y(), update.z()), + simCore::Vec3(update.psi(), update.theta(), update.phi())); + + locator_->setCoordinate(ecefCoord, u.time()); + + // fetch the positioning matrix from the locator we are tracking + if (!locator_->getLocatorMatrix(hostMatrix)) + { + // if assert fails, check that invalid platform updates are not being sent to track + assert(0); + return false; + } + + return true; +} + +bool TimeTicks::getMatrix_(const simData::PlatformUpdate& prevPoint, const simData::PlatformUpdate& curPoint, double time, osg::Matrix& hostMatrix) +{ + simData::PlatformUpdate prevUpdate = prevPoint; + simData::PlatformUpdate curUpdate = curPoint; + // apply filters, which may change position values + if (platformTspiFilterManager_.filter(curUpdate, lastPlatformPrefs_, lastPlatformProps_) == PlatformTspiFilterManager::POINT_DROPPED + || platformTspiFilterManager_.filter(prevUpdate, lastPlatformPrefs_, lastPlatformProps_) == PlatformTspiFilterManager::POINT_DROPPED) + return false; + + simData::Interpolator* li = ds_.interpolator(); + simData::PlatformUpdate platformUpdate = curUpdate; + if (curUpdate.time() != time) + li->interpolate(time, prevUpdate, curUpdate, &platformUpdate); + + simCore::Coordinate ecefCoordCur(simCore::COORD_SYS_ECEF, simCore::Vec3(platformUpdate.x(), platformUpdate.y(), platformUpdate.z())); + + // for point ticks, only need the position + if (lastPlatformPrefs_.trackprefs().timeticks().drawstyle() == simData::TimeTickPrefs::POINT) + { + locator_->setCoordinate(ecefCoordCur, time); + + // fetch the positioning matrix from the locator we are tracking + if (locator_->getLocatorMatrix(hostMatrix)) + return true; + else + { + // if assert fails, check that invalid platform updates are not being sent to track + assert(0); + return false; + } + } + + // for line ticks, need to calculate the orientation as well as the position + + simCore::Coordinate ecefCoordPrev(simCore::COORD_SYS_ECEF, simCore::Vec3(prevUpdate.x(), prevUpdate.y(), prevUpdate.z())); + simCore::Coordinate llaCoordPrev; + simCore::CoordinateConverter::convertEcefToGeodetic(ecefCoordPrev, llaCoordPrev); + simCore::Coordinate llaCoordCur; + simCore::CoordinateConverter::convertEcefToGeodetic(ecefCoordCur, llaCoordCur); + + double az = 0; + simCore::sodanoInverse(llaCoordPrev.lat(), llaCoordPrev.lon(), 0, llaCoordCur.lat(), llaCoordCur.lon(), &az); + simCore::Vec3 ecefOri; + simCore::CoordinateConverter::convertGeodeticOriToEcef(llaCoordCur.position(), simCore::Vec3(az, 0, 0), ecefOri); + // update our locator for the current update + simCore::Coordinate finalCoord(simCore::COORD_SYS_ECEF, simCore::Vec3(platformUpdate.x(), platformUpdate.y(), platformUpdate.z()), ecefOri); + + locator_->setCoordinate(finalCoord, time); + + // fetch the positioning matrix from the locator we are tracking + if (!locator_->getLocatorMatrix(hostMatrix)) + { + // if assert fails, check that invalid platform updates are not being sent to track + assert(0); + return false; + } + return true; +} + +} diff --git a/SDK/simVis/TimeTicks.h b/SDK/simVis/TimeTicks.h new file mode 100644 index 000000000..b9106c508 --- /dev/null +++ b/SDK/simVis/TimeTicks.h @@ -0,0 +1,220 @@ +/* -*- mode: c++ -*- */ +/**************************************************************************** + ***** ***** + ***** Classification: UNCLASSIFIED ***** + ***** Classified By: ***** + ***** Declassify On: ***** + ***** ***** + **************************************************************************** + * + * + * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. + * EW Modeling & Simulation, Code 5773 + * 4555 Overlook Ave. + * Washington, D.C. 20375-5339 + * + * License for source code at https://simdis.nrl.navy.mil/License.aspx + * + * The U.S. Government retains all rights to use, duplicate, distribute, + * disclose, or release this software. + * + */ +#ifndef SIMVIS_TIME_TICKS_H +#define SIMVIS_TIME_TICKS_H + +#include "simCore/Time/Clock.h" +#include "simData/DataSlice.h" +#include "simData/DataTable.h" +#include "osg/Geode" +#include "osg/MatrixTransform" +#include "simVis/Types.h" + +//---------------------------------------------------------------------------- +namespace osgEarth +{ + class LineDrawable; + class SpatialReference; +} +namespace simData +{ + class DataStore; + class PlatformPrefs; + class TrackPrefs; + class DataSliceBase; +} + +namespace simVis +{ +class Locator; +class TimeTicksChunk; +class PlatformTspiFilterManager; + +/** + * Scene graph node that depicts a time ticks trail for a platform + */ +class SDKVIS_EXPORT TimeTicks : public osg::Group +{ +public: + TimeTicks(const simData::DataStore& ds, const osgEarth::SpatialReference* srs, PlatformTspiFilterManager& manager, simData::ObjectId entityId); + + /** + * Reset the time ticks visualization, erasing everything that exists + * so it can start building again from scratch. + */ + void reset(); + + /** + * Accesses the updates for the associated platform and adds points, using current prefs settings + * This method is intended to update the time ticks in normal operation, as well as to recreate the time ticks in response to user action + * This may be slow if track history preferences are set to display many points, since time ticks match track history for data limiting. + * Time ticks will be created from the first available update time, factoring in data limiting and track length, up to the current scenario time + * or up to the last update time if the scenario time is past the end of the data history, interpolating at a specified interval. + */ + void update(); + + /** + * Update the time ticks based on the change in the Clock mode, e.g. to + * change the time direction. + * @param clock Clock that might have changed. + */ + void updateClockMode(const simCore::Clock* clock); + + /** + * Sets new preferences for this object. + * @param[in ] platformPrefs Preferences to apply + * @param[in ] platformProps Properties for the platform + * @param[in ] force Apply them even if the current settings already match + */ + void setPrefs(const simData::PlatformPrefs& platformPrefs, const simData::PlatformProperties& platformProps, bool force = false); + + /** Return the proper library name */ + virtual const char* libraryName() const { return "simVis"; } + + /** Return the class name */ + virtual const char* className() const { return "TimeTicks"; } + +protected: // methods + /// osg::Referenced-derived + virtual ~TimeTicks(); + +private: // methods + + /** Copy constructor, not implemented or available. */ + TimeTicks(const TimeTicks&); + + /** + * Return a chunk to which you can add a new point + * @return chunk that can accept a new point, or NULL if a new one needs to be created + */ + TimeTicksChunk* getCurrentChunk_(); + + /** + * Return the last chunk in the group, + * @return last chunk, or NULL if group is empty + */ + TimeTicksChunk* getLastChunk_(); + + /** + * Return the first chunk in the group + * @return first chunk, or NULL if group is empty + */ + TimeTicksChunk* getFirstChunk_(); + + /// Update the "flat mode" setting that zeros out the time ticks altitude; initialize shader programs if necessary + void updateFlatMode_(bool enabled); + + /// Update the draw flag + void updateVisibility_(const simData::TrackPrefs& prefs); + + /** + * Remove all points with draw times older than specified time + * @param oldestDrawTime oldest draw time that will remain in time ticks + */ + void removePointsOlderThan_(double oldestDrawTime); + + /** + * Determines the time window that time ticks should display, then + * determines what needs to be done to display that window, then + * adds required datapoints. + * @param currentTime latest time to display + * @param updateSlice platform update slice for associated platform to get current time + */ + void updateTrackData_(double currentTime, const simData::PlatformUpdateSlice& updateSlice); + + /** + * Accesses the updates for the associated platform and adds points for the interval [beginTime, endTime] + * This may be slow depending on how may points must be backfilled + * @param endTime last time to be added + * @param beginTime starting time for points to be added + */ + void backfillHistory_(double endTime, double beginTime); + + /** + * Update the time ticks visuals with a point to correspond to the specified time + * @param tickTime time to draw a tick + */ + void addUpdate_(double tickTime); + + /** + * Convert update time to draw time + * To support REVERSE playback mode, we play a little trick and simply negate + * the time so that time always appears to be increasing from the perspective + * of the rendering code. We do this to avoid adding complex logic in the rendering + * code for handling bi-directional track drawing. + * Throughout this class, we use the term "draw time" to represent the adjusted + * unidirectional time, versus "update time" which is the actual time in the + * data store. + * @param updateTime platform update time + * @return draw time that corresponds to the update time + */ + double toDrawTime_(double updateTime) const; + + /// utility function to get an OSG ENU matrix that corresponds to platform update's position and orientation + bool getMatrix_(const simData::PlatformUpdate& u, osg::Matrix& hostMatrix); + /// utility function to get an OSG ENU matrix that corresponds to the platform position at time, interpolated between the prevPoint and curPoint + bool getMatrix_(const simData::PlatformUpdate& prevPoint, const simData::PlatformUpdate& curPoint, double time, osg::Matrix& hostMatrix); + +private: // data + /// data store for initializing data slice + const simData::DataStore& ds_; + /// flag indicates if current system supports using shaders + bool supportsShaders_; + + simData::PlatformPrefs lastPlatformPrefs_; + simData::PlatformProperties lastPlatformProps_; + unsigned int chunkSize_; + osg::Vec4f color_; + unsigned int totalPoints_; + + /// keep track of the single point state which may initiate a reset + bool singlePoint_; + // "draw time" is the same as the clock's update time, but adjusted + // for time direction. i.e. it will be negated in the case of simCore::REVERSE. + bool hasLastDrawTime_; + double lastDrawTime_; + double lastCurrentTime_; + double lastLargeTickTime_; + double largeTickInterval_; + double lastLabelTime_; + double labelInterval_; + + // Playback direction (follows a datastore-bound Clock). + simCore::TimeDirection timeDirection_; + + osg::ref_ptr flatModeUniform_; + osg::ref_ptr chunkGroup_; + osg::ref_ptr labelGroup_; + std::map labels_; + + const simData::DataSliceBase* updateSliceBase_; + PlatformTspiFilterManager& platformTspiFilterManager_; + /// entity id for the platform + simData::ObjectId entityId_; + + osg::ref_ptr currentPointChunk_; + osg::ref_ptr locator_; +}; + +} // namespace simVis + +#endif // SIMVIS_TIME_TICKS_H diff --git a/SDK/simVis/TimeTicksChunk.cpp b/SDK/simVis/TimeTicksChunk.cpp new file mode 100644 index 000000000..3eef8ae28 --- /dev/null +++ b/SDK/simVis/TimeTicksChunk.cpp @@ -0,0 +1,210 @@ +/* -*- mode: c++ -*- */ +/**************************************************************************** + ***** ***** + ***** Classification: UNCLASSIFIED ***** + ***** Classified By: ***** + ***** Declassify On: ***** + ***** ***** + **************************************************************************** + * + * + * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. + * EW Modeling & Simulation, Code 5773 + * 4555 Overlook Ave. + * Washington, D.C. 20375-5339 + * + * License for source code at https://simdis.nrl.navy.mil/License.aspx + * + * The U.S. Government retains all rights to use, duplicate, distribute, + * disclose, or release this software. + * + */ +#include +#include "osg/Geode" +#include "osg/Geometry" +#include "osgText/Text" +#include "osgEarth/GeoData" +#include "osgEarth/GLUtils" +#include "osgEarth/LineDrawable" +#include "osgEarth/PointDrawable" +#include "simVis/Types.h" +#include "simVis/TimeTicksChunk.h" + +namespace simVis +{ + +TimeTicksChunk::TimeTicksChunk(unsigned int maxSize, Type type, double lineLength, double pointSize, unsigned int largeFactor) + : TrackPointsChunk(maxSize), + type_(type), + lineLength_(lineLength), + pointSize_(pointSize), + largeSizeFactor_(largeFactor) +{ + allocate_(); +} + +TimeTicksChunk::~TimeTicksChunk() +{ + geode_ = NULL; + line_ = NULL; + point_ = NULL; + largePoint_ = NULL; + line_ = NULL; +} + +bool TimeTicksChunk::addPoint(const osg::Matrix& matrix, double time, const osg::Vec4& color, bool large) +{ + // first make sure there's room. + if (isFull()) + return false; + + // if this is the first point added, set up the localization matrix. + if (offset_ == 0 && count_ == 0) + { + world2local_.invert(matrix); + setMatrix(matrix); + } + + // record the timestamp and world coords + times_[offset_ + count_] = time; + worldCoords_[offset_ + count_] = matrix; + + // resolve the localized point and append it to the various geometries. + append_(matrix, color, large); + + // advance the counter and update the psets. + count_++; + updatePrimitiveSets_(); + + return true; +} + +int TimeTicksChunk::getBeginMatrix(osg::Matrix& begin) const +{ + if (count_ < 1) + return 1; + begin = worldCoords_[offset_]; + return 0; +} + +int TimeTicksChunk::getEndMatrix(osg::Matrix& end) const +{ + if (count_ < 1) + return 1; + end = worldCoords_[offset_ + count_-1]; + return 0; +} + +void TimeTicksChunk::allocate_() +{ + // clear existing: + removeChildren(0, getNumChildren()); + + // timestamp vector. + times_.resize(maxSize_); + times_[0] = 0.0; + worldCoords_.resize(maxSize_); + + // pointers into the points list. + offset_ = 0; + count_ = 0; + + if (type_ == POINT_TICKS) + { + // large points + largePoint_ = new osgEarth::PointDrawable(); + largePoint_->setDataVariance(osg::Object::DYNAMIC); + largePoint_->allocate(maxSize_); + largePoint_->setColor(osg::Vec4f(0.f, 0.f, 0.f, 0.f)); + largePoint_->finish(); + largePoint_->setFirst(offset_); + largePoint_->setCount(count_); + addChild(largePoint_.get()); + osgEarth::GLUtils::setPointSize(largePoint_->getOrCreateStateSet(), pointSize_ * largeSizeFactor_, osg::StateAttribute::ON); + + // points + point_ = new osgEarth::PointDrawable(); + point_->setDataVariance(osg::Object::DYNAMIC); + point_->allocate(maxSize_); + point_->setColor(simVis::Color::White); + point_->finish(); + point_->setFirst(offset_); + point_->setCount(count_); + addChild(point_.get()); + osgEarth::GLUtils::setPointSize(point_->getOrCreateStateSet(), pointSize_, osg::StateAttribute::ON); + } + else if (type_ == LINE_TICKS) + { + // geode to hold all line geometry: + geode_ = new osgEarth::LineGroup(); + addChild(geode_.get()); + + // cross hatch line ticks + line_ = new osgEarth::LineDrawable(GL_LINES); + line_->setDataVariance(osg::Object::DYNAMIC); + line_->allocate(2 * maxSize_); + geode_->addChild(line_.get()); + } + + // reset to identity matrices + world2local_ = osg::Matrixd::identity(); +} + +void TimeTicksChunk::append_(const osg::Matrix& matrix, const osg::Vec4& color, bool large) +{ + // calculate the local point. + const osg::Vec3d& world = matrix.getTrans(); + const osg::Vec3f local = world * world2local_; + + // insertion index: + const unsigned int i = offset_ + count_; + + if (type_ == POINT_TICKS) + { + if (large) + { + largePoint_->setVertex(i, local); + largePoint_->setColor(i, color); + } + point_->setVertex(i, local); + point_->setColor(i, color); + } + else if (type_ == LINE_TICKS) + { + // add a new cross hatch tick + const osg::Matrix posMatrix = matrix * world2local_; + double width = lineLength_ * (large ? largeSizeFactor_ : 1); + + const osg::Vec3f left = osg::Vec3d(-width, 0.0, 0.0) * posMatrix; + const osg::Vec3f right = osg::Vec3d(width, 0.0, 0.0) * posMatrix; + + line_->setVertex(2 * i, left); + line_->setVertex(2 * i + 1, right); + + for (unsigned int c = 0; c < 2; ++c) + line_->setColor(2 * i + c, color); + } +} + +void TimeTicksChunk::fixGraphicsAfterRemoval_() +{ + // no-op +} + +void TimeTicksChunk::updatePrimitiveSets_() +{ + if (type_ == POINT_TICKS) + { + point_->setFirst(offset_); + point_->setCount(count_); + largePoint_->setFirst(offset_); + largePoint_->setCount(count_); + } + else if (type_ == LINE_TICKS) + { + line_->setFirst(2 * offset_); + line_->setCount(2 * count_); + } +} + +} diff --git a/SDK/simVis/TimeTicksChunk.h b/SDK/simVis/TimeTicksChunk.h new file mode 100644 index 000000000..9b6e8c5d5 --- /dev/null +++ b/SDK/simVis/TimeTicksChunk.h @@ -0,0 +1,125 @@ +/* -*- mode: c++ -*- */ +/**************************************************************************** + ***** ***** + ***** Classification: UNCLASSIFIED ***** + ***** Classified By: ***** + ***** Declassify On: ***** + ***** ***** + **************************************************************************** + * + * + * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. + * EW Modeling & Simulation, Code 5773 + * 4555 Overlook Ave. + * Washington, D.C. 20375-5339 + * + * License for source code at https://simdis.nrl.navy.mil/License.aspx + * + * The U.S. Government retains all rights to use, duplicate, distribute, + * disclose, or release this software. + * + */ +#ifndef SIMVIS_TIME_TICKS_CHUNK_H +#define SIMVIS_TIME_TICKS_CHUNK_H + +#include +#include +#include "osg/ref_ptr" +#include "osg/MatrixTransform" +#include "simData/DataTypes.h" +#include "simVis/TrackChunkNode.h" + +namespace simVis +{ + +/** Implementation of the TrackPointsChunk to draw track history time ticks */ +class SDKVIS_EXPORT TimeTicksChunk : public TrackPointsChunk +{ +public: + /// Draw mode for the time ticks + enum Type + { + POINT_TICKS, + LINE_TICKS + }; + + /** + * Create a new chunk with a maximum size + * @param maxSize maximum chunk size, in points + * @param type draw style for rendering ticks + * @param lineLength width in meters of line tick to draw + * @param pointSize pixel size of points tick to draw + * @param largeFactor large tick factor for line and point, multiple of lineLength for line, multiple of pointSize for point + */ + TimeTicksChunk(unsigned int maxSize, Type type, double lineLength, double pointSize, unsigned int largeFactor); + + /** + * Add a new point to the chunk + * @param matrix the position matrix that corresponds to the platform position (may be interpolated) + * @param t time that corresponds to the platform update, seconds since scenario ref year + * @param color color to render this chunk + * @param large indicates if this is a large tick + * @return true if point was added + */ + bool addPoint(const osg::Matrix& matrix, double t, const osg::Vec4& color, bool large); + + /** + * Get the earliest position matrix added to the chunk, accounting for data limiting + * @param position matrix that corresponds to a platform position (may be interpolated) + * @return 0 on success, non-zero if chunk is empty + */ + int getBeginMatrix(osg::Matrix& first) const; + + /** + * Get the latest position matrix added to the chunk, accounting for data limiting + * @param position matrix that corresponds to a platform position (may be interpolated) + * @return 0 on success, non-zero if chunk is empty + */ + int getEndMatrix(osg::Matrix& last) const; + + /** Return the proper library name */ + virtual const char* libraryName() const { return "simVis"; } + + /** Return the class name */ + virtual const char* className() const { return "TimeTicksChunk"; } + +protected: + virtual ~TimeTicksChunk(); + +private: + /// Allocate the graphical elements for this chunk. + void allocate_(); + + /// Appends a new local point to each geometry set. + void append_(const osg::Matrix& matrix, const osg::Vec4& color, bool large); + + virtual void fixGraphicsAfterRemoval_(); + /// Update the offset and count on each primitive set to draw the proper data. + virtual void updatePrimitiveSets_(); + +private: + /// draw type + Type type_; + /// line tick length + double lineLength_; + /// point tick size + double pointSize_; + /// large tick size factor + double largeSizeFactor_; + ///container for drawables + osg::ref_ptr geode_; + /// point graphic + osg::ref_ptr point_; + /// point graphic for large points + osg::ref_ptr largePoint_; + /// line graphic + osg::ref_ptr line_; + /// matrix to convert from world to local coords + osg::Matrixd world2local_; + /// cache the world coordinates for quick access + std::vector worldCoords_; +}; + +} // namespace simVis + +#endif // SIMVIS_TIME_TICKS_CHUNK_H diff --git a/SDK/simVis/TrackChunkNode.cpp b/SDK/simVis/TrackChunkNode.cpp index 929e9e48a..1606b7b40 100644 --- a/SDK/simVis/TrackChunkNode.cpp +++ b/SDK/simVis/TrackChunkNode.cpp @@ -21,19 +21,101 @@ */ #include #include "osg/Geode" -#include "osg/Geometry" #include "osgEarth/GeoData" #include "osgEarth/LineDrawable" +#include "osgEarth/PointDrawable" #include "simVis/Types.h" #include "simVis/TrackChunkNode.h" namespace simVis { +//---------------------------------------------------------------------------- +TrackPointsChunk::TrackPointsChunk(unsigned int maxSize) + : maxSize_(maxSize) +{ +} + +/// is this chunk full? i.e. no room for more points? +bool TrackPointsChunk::isFull() const +{ + return (offset_ + count_) >= maxSize_; +} + +/// how many points are rendered by this chunk? +unsigned int TrackPointsChunk::size() const +{ + return count_; +} + + +/// remove the oldest point in this chunk. +bool TrackPointsChunk::removeOldestPoint() +{ + if (count_ == 0) + return false; + + offset_++; + count_--; + updatePrimitiveSets_(); + // don't bother updating the bound. + + fixGraphicsAfterRemoval_(); + return true; +} + +/// remove points from the tail; return the number of points removed. +unsigned int TrackPointsChunk::removePointsBefore(double t) +{ + const unsigned int origOffset = offset_; + while (count_ > 0 && times_[offset_] < t) + { + offset_++; + count_--; + } + + if (origOffset != offset_) + { + updatePrimitiveSets_(); + // would normally dirtyBound(), but don't bother. + + // this does dirtyBound if ribbon mode + fixGraphicsAfterRemoval_(); + } + + return offset_ - origOffset; +} + +void TrackPointsChunk::reset() +{ + times_[0] = 0.0; + offset_ = 0; + count_ = 0; +} + +/// time of the first point in this chunk +double TrackPointsChunk::getBeginTime() const +{ + return count_ >= 1 ? times_[offset_] : -1.0; +} + +/// time of the last point in this chunk +double TrackPointsChunk::getEndTime() const +{ + return count_ >= 1 ? times_[offset_ + count_ - 1] : -1.0; +} + +/// is this chunk empty? +bool TrackPointsChunk::isEmpty_() const +{ + return count_ == 0; +} + + //---------------------------------------------------------------------------- /** Creates a new chunk with a maximum size. */ TrackChunkNode::TrackChunkNode(unsigned int maxSize, const osgEarth::SpatialReference* srs, simData::TrackPrefs_Mode mode) - : maxSize_(maxSize), + : TrackPointsChunk(maxSize), srs_(srs), mode_(mode) { @@ -50,18 +132,6 @@ TrackChunkNode::~TrackChunkNode() srs_ = NULL; } -/// is this chunk full? i.e. no room for more points? -bool TrackChunkNode::isFull() const -{ - return (offset_ + count_) >= maxSize_; -} - -/// how many points are rendered by this chunk? -unsigned int TrackChunkNode::size() const -{ - return count_; -} - /// add a new point to the chunk. bool TrackChunkNode::addPoint(const osg::Matrix& matrix, double t, const osg::Vec4& color, const osg::Vec2& hostBounds) { @@ -99,51 +169,6 @@ bool TrackChunkNode::getNewestData(osg::Matrix& out_matrix, double& out_time) co return true; } -/// remove the oldest point in this chunk. -bool TrackChunkNode::removeOldestPoint() -{ - if (count_ == 0) - return false; - - offset_++; - count_--; - updatePrimitiveSets_(); - // don't bother updating the bound. - - // this does dirtyBound if ribbon mode - fixRibbon_(); - return true; -} - -/// remove points from the tail; return the number of points removed. -unsigned int TrackChunkNode::removePointsBefore(double t) -{ - const unsigned int origOffset = offset_; - while (count_ > 0 && times_[offset_] < t) - { - offset_++; - count_--; - } - - if (origOffset != offset_) - { - updatePrimitiveSets_(); - // would normally dirtyBound(), but don't bother. - - // this does dirtyBound if ribbon mode - fixRibbon_(); - } - - return offset_ - origOffset; -} - -void TrackChunkNode::reset() -{ - times_[0] = 0.0; - offset_ = 0; - count_ = 0; -} - /// allocate the graphical elements for this chunk. void TrackChunkNode::allocate_() { @@ -161,18 +186,14 @@ void TrackChunkNode::allocate_() if (mode_ == simData::TrackPrefs_Mode_POINT) { // center line (point mode) - centerPoints_ = new osg::Geometry(); - centerPoints_->setUseVertexBufferObjects(true); - centerPoints_->setUseDisplayList(false); + centerPoints_ = new osgEarth::PointDrawable(); centerPoints_->setDataVariance(osg::Object::DYNAMIC); - osg::Vec3Array* verts = new osg::Vec3Array(); - verts->assign(maxSize_, osg::Vec3()); - centerPoints_->setVertexArray(verts); - osg::Vec4Array* colors = new osg::Vec4Array(); - colors->setBinding(osg::Array::BIND_PER_VERTEX); - colors->assign(maxSize_, simVis::Color::White); - centerPoints_->setColorArray(colors); - centerPoints_->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, offset_, count_)); + centerPoints_->allocate(maxSize_); + centerPoints_->setColor(simVis::Color::White); + // finish() will create the primitive set, allowing us to micromanage first/count + centerPoints_->finish(); + centerPoints_->setFirst(offset_); + centerPoints_->setCount(count_); this->addChild(centerPoints_.get()); } else @@ -207,41 +228,6 @@ void TrackChunkNode::allocate_() world2local_ = osg::Matrixd::identity(); } -/// time of the first point in this chunk -double TrackChunkNode::getStartTime_() const -{ - return count_ >= 1 ? times_[offset_] : -1.0; -} - -/// time of the last point in this chunk -double TrackChunkNode::getEndTime_() const -{ - return count_ >= 1 ? times_[offset_+count_-1] : -1.0; -} - -/// is this chunk empty? -bool TrackChunkNode::isEmpty_() const -{ - return count_ == 0; -} - -/// remove all the points in this chunk that occur after the timestamp -unsigned int TrackChunkNode::removePointsAtAndBeyond_(double t) -{ - const unsigned int origCount = count_; - while (count_ > 0 && times_[offset_+count_-1] >= t) - { - count_--; - } - - if (origCount != count_) - { - updatePrimitiveSets_(); - } - - return origCount - count_; -} - /// appends a new local point to each geometry set. void TrackChunkNode::append_(const osg::Matrix& matrix, const osg::Vec4& color, const osg::Vec2& hostBounds) { @@ -255,13 +241,8 @@ void TrackChunkNode::append_(const osg::Matrix& matrix, const osg::Vec4& color, if (mode_ == simData::TrackPrefs_Mode_POINT) { // and update the center points track as well: - osg::Vec3Array& centerPointsVerts = static_cast(*centerPoints_->getVertexArray()); - centerPointsVerts[i] = local; - centerPointsVerts.dirty(); - osg::Vec4Array& centerPointsColors = static_cast(*centerPoints_->getColorArray()); - centerPointsColors[i] = color; - centerPointsColors.dirty(); - centerPoints_->dirtyBound(); + centerPoints_->setVertex(i, local); + centerPoints_->setColor(i, color); return; } @@ -314,9 +295,8 @@ void TrackChunkNode::updatePrimitiveSets_() { if (mode_ == simData::TrackPrefs_Mode_POINT) { - osg::DrawArrays& centerPointsPrimSet = static_cast(*centerPoints_->getPrimitiveSet(0)); - centerPointsPrimSet.setFirst(offset_); - centerPointsPrimSet.setCount(count_); + centerPoints_->setFirst(offset_); + centerPoints_->setCount(count_); return; } @@ -338,7 +318,7 @@ void TrackChunkNode::updatePrimitiveSets_() } // only to be called when points are deleted, so that ribbon visual can be fixed to not show links to deleted point -void TrackChunkNode::fixRibbon_() +void TrackChunkNode::fixGraphicsAfterRemoval_() { if (mode_ == simData::TrackPrefs_Mode_RIBBON && !isEmpty_() && offset_ > 0) { diff --git a/SDK/simVis/TrackChunkNode.h b/SDK/simVis/TrackChunkNode.h index 5497feac1..328398923 100644 --- a/SDK/simVis/TrackChunkNode.h +++ b/SDK/simVis/TrackChunkNode.h @@ -24,147 +24,173 @@ #include "osg/ref_ptr" #include "osg/MatrixTransform" +#include "osgEarth/LineDrawable" +#include "osgEarth/PointDrawable" #include "simData/DataTypes.h" -namespace osgEarth -{ - class LineDrawable; - class SpatialReference; -} +namespace osg { class Geode; } +namespace osgEarth { class SpatialReference; } namespace simVis { +/** + * Node that renders a "chunk" of the track history trail points + * A full track history trail is segmented into chunks. This allows a + * number of things: + * + * - efficiently add points to the track (only need to update the last chunk) + * - efficiently maintain a limited track size (restricting the total number of points) + * - dealing with long tracks (eliminate jitter and improve culling) + * - manage memory effectively (always allocate buffer objects of exactly the same size, which OSG likes) + * + * Each chunk will hold a limited (and specific) number of points. Once + * the capacity of a chunk is exceeded, a new chunk gets appended to the + * graph. Similarly, when point-limiting is in effect, we can adjust the + * oldest chunk to "drop" points from the end of the track. + * + * Each chunk lives under its own MT to prevent single-precision jitter + * effects in a geocentric map. + * + * Note: Choose the Chunk Size carefully. Each chunk pre-allocates all the memory + * it will possibly need, so if you have a large number of entities with track + * histories, you can quickly run out of memory. +*/ +class SDKVIS_EXPORT TrackPointsChunk : public osg::MatrixTransform +{ +public: /** - * Node that renders a "chunk" of the track history - * A full track history trail is segmented into chunks. This allows a - * number of things: - * - * - efficiently add points to the track (only need to update the last chunk) - * - efficiently maintain a limited track size (restricting the total number of points) - * - dealing with long tracks (eliminate jitter and improve culling) - * - manage memory effectively (always allocate buffer objects of exactly the same size, which OSG likes) - * - * Each chunk will hold a limited (and specific) number of points. Once - * the capacity of a chunk is exceeded, a new chunk gets appended to the - * graph. Similarly, when point-limiting is in effect, we can adjust the - * oldest chunk to "drop" points from the end of the track. - * - * Each chunk lives under its own MT to prevent single-precision jitter - * effects in a geocentric map. - * - * Note: Choose the Chunk Size carefully. Each chunk pre-allocates all the memory - * it will possibly need, so if you have a large number of entities with track - * histories, you can quickly run out of memory. + * Create a new chunk with a maximum size + * @param maxSize maximum chunk size, in points */ - class TrackChunkNode : public osg::MatrixTransform - { - public: - /** - * Create a new chunk with a maximum size - * @param maxSize maximum chunk size, in points - * @param srs spatial reference that is being used for track data - * @param mode track draw mode that this chunk will display - */ - TrackChunkNode(unsigned int maxSize, const osgEarth::SpatialReference* srs, simData::TrackPrefs_Mode mode = simData::TrackPrefs_Mode_POINT); - - /** - * Is this chunk full? i.e. no room for more points? - * @return true if this chunk is full - */ - bool isFull() const; - - /** - * How many points are rendered by this chunk? - * @return number of active points in this chunk - */ - unsigned int size() const; - - /** - * Add a new point to the chunk - * @param matrix the position matrix that corresponds to the platform update position - * @param t time that corresponds to the platform update - * @param color color to render this chunk - * @param hostBounds left and right boundaries of the host model - * @return true if point was added - */ - bool addPoint(const osg::Matrix& matrix, double t, const osg::Vec4& color, const osg::Vec2& hostBounds); - - /** - * Get the matrix and time associated with the newest point in this chunk - * @param out_matrix position matrix for the newest point in the chunk - * @param out_time update time of the newest point in the chunk - * @return true if matrix and time are valid - */ - bool getNewestData(osg::Matrix& out_matrix, double& out_time) const; - - /** - * Remove the oldest point in this chunk - * @return true if point was removed - */ - bool removeOldestPoint(); - - /** - * Remove points from the tail - * @param t earliest time that will remain in this chunk - * @return number of points removed - */ - unsigned int removePointsBefore(double t); - - /** Allows the node to be re-used */ - void reset(); - - /** Return the proper library name */ - virtual const char* libraryName() const { return "simVis"; } - - /** Return the class name */ - virtual const char* className() const { return "TrackChunkNode"; } - - protected: - virtual ~TrackChunkNode(); - - private: - /// Allocate the graphical elements for this chunk. - void allocate_(); - - /// Return time of the first point in this chunk - double getStartTime_() const; - - /// Return time of the last point in this chunk - double getEndTime_() const; - - /// Is this chunk empty? - bool isEmpty_() const; - - /// Remove all the points in this chunk that occur after the timestamp - unsigned int removePointsAtAndBeyond_(double t); - - /// Appends a new local point to each geometry set. - void append_(const osg::Matrix& matrix, const osg::Vec4& color, const osg::Vec2& hostBounds); - - /// Update the offset and count on each primitive set to draw the proper data. - void updatePrimitiveSets_(); - - /// Fix the ribbon visual after points deletion to not show links to deleted point - void fixRibbon_(); - - private: - std::vector times_; /// timestamp of each point - unsigned int offset_; /// offset into the point list to the start of points to render - unsigned int count_; /// number of points to render - unsigned int maxSize_; /// maximum allowable number of points in chunk - osg::ref_ptr geode_; - - osg::ref_ptr centerLine_; - osg::ref_ptr centerPoints_; - - osg::ref_ptr ribbon_; - - osg::ref_ptr drop_; - - osg::ref_ptr srs_; - osg::Matrixd world2local_; - simData::TrackPrefs_Mode mode_; /// track draw mode that this chunk will display - }; + TrackPointsChunk(unsigned int maxSize); + + /** + * Is this chunk full? i.e. no room for more points? + * @return true if this chunk is full + */ + bool isFull() const; + + /** + * How many points are rendered by this chunk? + * @return number of active points in this chunk + */ + unsigned int size() const; + + /** + * Return time of the first point in this chunk in seconds since ref year, accounting for data limiting + * @return begin time in seconds + */ + double getBeginTime() const; + + /** + * Return time of the last point in this chunk in seconds since ref year, accounting for data limiting + * @return end time in seconds + */ + double getEndTime() const; + + /** + * Remove the oldest point in this chunk + * @return true if point was removed + */ + bool removeOldestPoint(); + + /** + * Remove points from the tail + * @param t earliest time that will remain in this chunk + * @return number of points removed + */ + unsigned int removePointsBefore(double t); + + /** Allows the node to be re-used */ + void reset(); + +protected: + virtual ~TrackPointsChunk() {} + + /// Is this chunk empty? + bool isEmpty_() const; + + /// Fix graphics after points are removed + virtual void fixGraphicsAfterRemoval_() = 0; + + /// Update the offset and count on each primitive set to draw the proper data. + virtual void updatePrimitiveSets_() = 0; + + // data + /// timestamp of each point + std::vector times_; + /// offset into the point list to the start of points to render + unsigned int offset_; + /// number of points to render + unsigned int count_; + /// maximum allowable number of points in chunk + unsigned int maxSize_; +}; + +/** Implementation of the TrackPointsChunk for drawing track history update points */ +class SDKVIS_EXPORT TrackChunkNode : public TrackPointsChunk +{ +public: + /** + * Create a new chunk with a maximum size + * @param maxSize maximum chunk size, in points + * @param srs spatial reference that is being used for track data + * @param mode track draw mode that this chunk will display + */ + TrackChunkNode(unsigned int maxSize, const osgEarth::SpatialReference* srs, simData::TrackPrefs_Mode mode = simData::TrackPrefs_Mode_POINT); + + /** + * Add a new point to the chunk + * @param matrix the position matrix that corresponds to the platform update position + * @param t time that corresponds to the platform update + * @param color color to render this chunk + * @param hostBounds left and right boundaries of the host model + * @return true if point was added + */ + bool addPoint(const osg::Matrix& matrix, double t, const osg::Vec4& color, const osg::Vec2& hostBounds); + + /** + * Get the matrix and time associated with the newest point in this chunk + * @param out_matrix position matrix for the newest point in the chunk + * @param out_time update time of the newest point in the chunk + * @return true if matrix and time are valid + */ + bool getNewestData(osg::Matrix& out_matrix, double& out_time) const; + + /** Return the proper library name */ + virtual const char* libraryName() const { return "simVis"; } + + /** Return the class name */ + virtual const char* className() const { return "TrackChunkNode"; } + +protected: + virtual ~TrackChunkNode(); + + /// Update the offset and count on each primitive set to draw the proper data. + virtual void updatePrimitiveSets_(); + + /// Fix the ribbon visual after points deletion to not show links to deleted point + virtual void fixGraphicsAfterRemoval_(); + +private: + /// Allocate the graphical elements for this chunk. + void allocate_(); + /// Appends a new local point to each geometry set. + void append_(const osg::Matrix& matrix, const osg::Vec4& color, const osg::Vec2& hostBounds); + +private: + osg::ref_ptr geode_; + osg::ref_ptr centerLine_; + osg::ref_ptr centerPoints_; + osg::ref_ptr ribbon_; + osg::ref_ptr drop_; + + osg::ref_ptr srs_; + osg::Matrixd world2local_; + + /// track draw mode that this chunk will display + simData::TrackPrefs_Mode mode_; +}; } // namespace simVis diff --git a/SDK/simVis/TrackHistory.cpp b/SDK/simVis/TrackHistory.cpp index 01dd9faed..c5a7b64a6 100644 --- a/SDK/simVis/TrackHistory.cpp +++ b/SDK/simVis/TrackHistory.cpp @@ -22,6 +22,7 @@ #include #include "osgEarth/Capabilities" +#include "osgEarth/GLUtils" #include "osgEarth/Horizon" #include "osgEarth/LineDrawable" #include "osgEarth/Registry" @@ -33,7 +34,6 @@ #include "simVis/Locator.h" #include "simVis/OverheadMode.h" #include "simVis/PlatformFilter.h" -#include "simVis/PointSize.h" #include "simVis/Shaders.h" #include "simVis/Types.h" #include "simVis/Utils.h" @@ -576,7 +576,7 @@ void TrackHistoryNode::setPrefs(const simData::PlatformPrefs& platformPrefs, con const double lineWidth = osg::clampAbove(prefs.linewidth(), 1.0); osg::StateSet* stateSet = this->getOrCreateStateSet(); osgEarth::LineDrawable::setLineWidth(stateSet, lineWidth); - PointSize::setValues(stateSet, lineWidth, osg::StateAttribute::ON); + osgEarth::GLUtils::setPointSize(stateSet, lineWidth, osg::StateAttribute::ON); } if (force || PB_FIELD_CHANGED(&lastPrefs, &prefs, tracklength)) diff --git a/SDK/simVis/Utils.cpp b/SDK/simVis/Utils.cpp index 70e54389d..709e0611b 100644 --- a/SDK/simVis/Utils.cpp +++ b/SDK/simVis/Utils.cpp @@ -32,7 +32,7 @@ #include "osgDB/Registry" #include "osgSim/DOFTransform" #include "osgUtil/RenderBin" -#include "osgUtil/TriStripVisitor" +#include "osgUtil/MeshOptimizers" #include "osgViewer/ViewerEventHandlers" #include "osgEarth/Capabilities" @@ -1166,8 +1166,8 @@ void FixDeprecatedDrawModes::apply(osg::Geometry& geom) if (primSet->getMode() == GL_POLYGON || primSet->getMode() == GL_QUADS || primSet->getMode() == GL_QUAD_STRIP) { // Turn deprecated geometry into tri-strips; affects whole geometry - osgUtil::TriStripVisitor triStrip; - triStrip.stripify(geom); + osgUtil::IndexMeshVisitor mesher; + geom.accept(mesher); break; } } diff --git a/SDK/simVis/VelocityVector.cpp b/SDK/simVis/VelocityVector.cpp index ac9197bee..51093b818 100644 --- a/SDK/simVis/VelocityVector.cpp +++ b/SDK/simVis/VelocityVector.cpp @@ -67,7 +67,7 @@ int VelocityVector::rebuild_(const simData::PlatformPrefs& prefs) createVelocityVector_(prefs, geode.get()); // disable lighting - osg::StateSet* stateSet = geode->getOrCreateStateSet(); + simVis::setLighting(geode->getOrCreateStateSet(), osg::StateAttribute::OFF); setNodeMask(DISPLAY_MASK_PLATFORM); this->addChild(geode.get()); diff --git a/Testing/SimCore/AngleTest.cpp b/Testing/SimCore/AngleTest.cpp index 93d78ec3a..c88ff2191 100644 --- a/Testing/SimCore/AngleTest.cpp +++ b/Testing/SimCore/AngleTest.cpp @@ -575,6 +575,38 @@ int testSim7284() return rv; } +int test360() +{ + int rv = 0; + + // test precision + rv += SDK_ASSERT(simCore::getAngleString(360.0 * simCore::DEG2RAD, simCore::FMT_DEGREES, true, 0, simCore::DEG_SYM_NONE, 0, 0) == "0"); + rv += SDK_ASSERT(simCore::getAngleString(360.0 * simCore::DEG2RAD, simCore::FMT_DEGREES, true, 1, simCore::DEG_SYM_NONE, 0, 0) == "0.0"); + rv += SDK_ASSERT(simCore::getAngleString(360.0 * simCore::DEG2RAD, simCore::FMT_DEGREES, true, 2, simCore::DEG_SYM_NONE, 0, 0) == "0.00"); + rv += SDK_ASSERT(simCore::getAngleString(360.0 * simCore::DEG2RAD, simCore::FMT_DEGREES, true, 3, simCore::DEG_SYM_NONE, 0, 0) == "0.000"); + rv += SDK_ASSERT(simCore::getAngleString(360.0 * simCore::DEG2RAD, simCore::FMT_DEGREES, true, 4, simCore::DEG_SYM_NONE, 0, 0) == "0.0000"); + rv += SDK_ASSERT(simCore::getAngleString(360.0 * simCore::DEG2RAD, simCore::FMT_DEGREES, true, 5, simCore::DEG_SYM_NONE, 0, 0) == "0.00000"); + rv += SDK_ASSERT(simCore::getAngleString(360.0 * simCore::DEG2RAD, simCore::FMT_DEGREES, true, 6, simCore::DEG_SYM_NONE, 0, 0) == "0.000000"); + rv += SDK_ASSERT(simCore::getAngleString(360.0 * simCore::DEG2RAD, simCore::FMT_DEGREES, true, 7, simCore::DEG_SYM_NONE, 0, 0) == "0.0000000"); + + // test degrees + rv += SDK_ASSERT(simCore::getAngleString(360.0005 * simCore::DEG2RAD, simCore::FMT_DEGREES, true, 3, simCore::DEG_SYM_NONE, 0, 0) == "0.000"); + rv += SDK_ASSERT(simCore::getAngleString(359.9995 * simCore::DEG2RAD, simCore::FMT_DEGREES, true, 3, simCore::DEG_SYM_NONE, 0, 0) == "0.000"); + rv += SDK_ASSERT(simCore::getAngleString(359.99949 * simCore::DEG2RAD, simCore::FMT_DEGREES, true, 3, simCore::DEG_SYM_NONE, 0, 0) == "359.999"); + + // test degree-minutes + rv += SDK_ASSERT(simCore::getAngleString(dmsAsRadian(359.0, 59.9995, 0.0), simCore::FMT_DEGREES_MINUTES, true, 3, simCore::DEG_SYM_NONE, 0, 0) == "0 00.000"); + rv += SDK_ASSERT(simCore::getAngleString(dmsAsRadian(359.0, 59.9995, 0.0), simCore::FMT_DEGREES_MINUTES, true, 3, simCore::DEG_SYM_NONE, 0, 0) == "0 00.000"); + rv += SDK_ASSERT(simCore::getAngleString(dmsAsRadian(359.0, 59.99949, 0.0), simCore::FMT_DEGREES_MINUTES, true, 3, simCore::DEG_SYM_NONE, 0, 0) == "359 59.999"); + + // test degree-minute-seconds + rv += SDK_ASSERT(simCore::getAngleString(dmsAsRadian(359.0, 59.0, 59.9995), simCore::FMT_DEGREES_MINUTES_SECONDS, true, 3, simCore::DEG_SYM_NONE, 0, 0) == "0 00 00.000"); + rv += SDK_ASSERT(simCore::getAngleString(dmsAsRadian(359.0, 59.0, 59.9995), simCore::FMT_DEGREES_MINUTES_SECONDS, true, 3, simCore::DEG_SYM_NONE, 0, 0) == "0 00 00.000"); + rv += SDK_ASSERT(simCore::getAngleString(dmsAsRadian(359.0, 59.0, 59.99949), simCore::FMT_DEGREES_MINUTES_SECONDS, true, 3, simCore::DEG_SYM_NONE, 0, 0) == "359 59 59.999"); + + return rv; +} + } int AngleTest(int argc, char* argv[]) @@ -589,6 +621,7 @@ int AngleTest(int argc, char* argv[]) rv += testSim2511(); rv += testSim4481(); rv += testSim7284(); + rv += test360(); return rv; } diff --git a/Testing/SimCore/CMakeLists.txt b/Testing/SimCore/CMakeLists.txt index 1ce764438..d7fb78841 100644 --- a/Testing/SimCore/CMakeLists.txt +++ b/Testing/SimCore/CMakeLists.txt @@ -34,6 +34,7 @@ create_test_sourcelist(SimCoreTestFiles SimCoreTests.cpp GogToGeoFenceTest.cpp CalculateLibTest.cpp MagneticVarianceTest.cpp + CsvReaderTest.cpp ) add_executable(SimCoreTests ${SimCoreTestFiles}) @@ -45,7 +46,7 @@ if(TARGET simUtil) endif() set_target_properties(SimCoreTests PROPERTIES FOLDER "Unit Tests" - PROJECT_LABEL "Unit Tests - SimCore" + PROJECT_LABEL "simCore Test" ) add_test(NAME VersionTest COMMAND SimCoreTests VersionTest) @@ -73,6 +74,7 @@ add_test(NAME CoreUnitsFormatter COMMAND SimCoreTests UnitsFormatter) add_test(NAME GogToGeoFenceTest COMMAND SimCoreTests GogToGeoFenceTest) add_test(NAME CalculateLibTest COMMAND SimCoreTests CalculateLibTest ${SimCore_UnitTests_SOURCE_DIR}/CalculateInput.txt) add_test(NAME MagneticVarianceTest COMMAND SimCoreTests MagneticVarianceTest) +add_test(NAME CsvReaderTest COMMAND SimCoreTests CsvReaderTest) # Try to locate the correct file for the RCS test... set(FILE_LOCATIONS diff --git a/Testing/SimCore/CalculateInput.txt b/Testing/SimCore/CalculateInput.txt index c7d94bce5..a7c1aeb23 100644 --- a/Testing/SimCore/CalculateInput.txt +++ b/Testing/SimCore/CalculateInput.txt @@ -1,3 +1,4 @@ +#CommentsDenotedByPoundSymbols #TokensSeparatedByWhitespace SlantWGS84 0.0 0.0 0.0 -1.510 -2.7578684014 871.0 -1.132313619501204 0.84955592154 9.0 3124784.7428872650489211082458496 @@ -117,21 +118,22 @@ AltitudeWGS84 0.0 0.0 0.0 -1.433629385640828 2.159265359 -.21 1.183185307179587 -2.3 -23 -22.78999999999999914734871708788 +#DataGeneratedFromFunctionOutput #ValidatedByComparingToVisuals RelAzElWGS84 0.0 0.0 0.0 -1.510 -2.7578684014 871.0 -1.510 -2.7578684014 871.0 -1.132313619501204 0.84955592154 9.0 -1.811564973914540077 0.398742617007379 1.34924549366009 +1.811564973914540077 0.398742617007379 1.7923471599273946 RelAzElWGS84 0.0 0.0 0.0 0.6840734641 -2.6637061436 51.1 0.6840734641 -2.6637061436 51.1 -2.30044407846 0.1 121.3 --0.048195906359514699424106964897874 0.45782027167140448842275191054796 2.6814213599191192294313168531517 +-0.048195906359514699424106964897874 0.45782027167140448842275191054796 0.46017129367829990 RelAzElWGS84 0.0 0.0 0.0 0.35666117692 -1.657549333841125 -11.1023 0.35666117692 -1.657549333841125 -11.1023 2.632143634355323 -0.043985825177 3.0 --0.34565999739436814586213131406112 -0.8506080014202469508077797399892 2.2401976393892812922103985329159 +-0.34565999739436814586213131406112 -0.8506080014202469508077797399892 0.90139501420067314 RelAzElWGS84 0.0 0.0 0.0 -1.433629385640828 2.159265359 -.21 -1.433629385640828 2.159265359 -.21 1.183185307179587 -2.3 -23 -2.8872070110227614492259817779996 0.73938453896693745459600677349954 0.77402240546564760048653397461749 +2.8872070110227614492259817779996 0.73938453896693745459600677349954 2.3675702481027798 AspectAngleWGS84 0.0 0.0 0.0 0 0 0 0 0.001 0 0 0 0 @@ -350,21 +352,22 @@ AltitudeTangentPlaneWGS84 2.3 -70.0 0.0 -1.433629385640828 2.159265359 -.21 1.183185307179587 -2.3 -23 -12267014.51663341 +#DataGeneratedFromFunctionOutput #ValidatedByComparingToVisuals RelAzElTangentPlaneWGS84 2.3 -70.0 0.0 -1.510 -2.7578684014 871.0 -1.510 -2.7578684014 871.0 -1.132313619501204 0.84955592154 9.0 -1.8115649739145400776152428079513 0.3987426170073795628212565134163 1.3492454936600932757784221394104 +1.8115649739145400776152428079513 0.3987426170073795628212565134163 1.7923471599273946 RelAzElTangentPlaneWGS84 2.3 -70.0 0.0 0.6840734641 -2.6637061436 51.1 0.6840734641 -2.6637061436 51.1 -2.30044407846 0.1 121.3 --0.048195906359514699424106964897874 0.45782027167140448842275191054796 2.6814213599191192294313168531517 +-0.048195906359514699424106964897874 0.45782027167140448842275191054796 0.46017129367829990 RelAzElTangentPlaneWGS84 2.3 -70.0 0.0 0.35666117692 -1.657549333841125 -11.1023 0.35666117692 -1.657549333841125 -11.1023 2.632143634355323 -0.043985825177 3.0 --0.34565999739436814586213131406112 -0.8506080014202469508077797399892 2.2401976393892812922103985329159 +-0.34565999739436814586213131406112 -0.8506080014202469508077797399892 0.90139501420067314 RelAzElTangentPlaneWGS84 2.3 -70.0 0.0 -1.433629385640828 2.159265359 -.21 -1.433629385640828 2.159265359 -.21 1.183185307179587 -2.3 -23 -2.8872070110227614492259817779996 0.73938453896693745459600677349954 0.77402240546564760048653397461749 +2.8872070110227614492259817779996 0.73938453896693745459600677349954 2.3675702481027798 AbsAzElTangentPlaneWGS84 2.3 -70.0 0.0 -1.510 -2.7578684014 871.0 -1.132313619501204 0.84955592154 9.0 diff --git a/Testing/SimCore/CalculateLibTest.cpp b/Testing/SimCore/CalculateLibTest.cpp index 0d8f5e38c..f12bcd316 100644 --- a/Testing/SimCore/CalculateLibTest.cpp +++ b/Testing/SimCore/CalculateLibTest.cpp @@ -352,6 +352,10 @@ int readNextTest(std::istream& fd, bool& doneReading) return 0; } + // Check for comments + if (test.compare(0, 1, "#") == 0) + return 0; + int rv = 0; simCore::EarthModelCalculations earth = simCore::PERFECT_SPHERE; // set coordinate system / reference frame diff --git a/Testing/SimCore/CalculationTest.cpp b/Testing/SimCore/CalculationTest.cpp index 0d0fd5012..89408b8fc 100644 --- a/Testing/SimCore/CalculationTest.cpp +++ b/Testing/SimCore/CalculationTest.cpp @@ -1641,29 +1641,84 @@ int testTaos_intercept() return rv; } +int testAoaSideslipTotalAoa() +{ + int rv = 0; + + double testParams[24][9] = { + // Test data generated by the TAOS application. NOTE: "Expected SS" values are multiplied by -1 since SIMDIS looks at the angle from the opposite perspective + // Yaw (rad), Pitch (rad), Roll (rad), EastVel (any), NorthVel (any), UpVel (any), Expected AOA (rad), Expected SS (rad), Expected TotalAOA (rad) + { 1.5708, 1.10174, -0, 821.82, 0.00, 2361.06, -0.134094, 0.0, 0.134094}, + { 1.5708, 1.11942, 0, 1586.49, 0.00, 3415.72, -0.0165457, 0.0, 0.0165457}, + { -0.610534, 1.17927, 1.23123, -177.65, 253.79, 750.82, 0.0, 0.000226893, 0.000226893}, + { -0.61104, 1.17183, 2.55434, -234.34, 334.76, 969.44, -0.0, 0.000122173, 0.000122173}, + { -0.613221, 1.06195, -0.967192, -718.09, 1019.66, 2236.20, -0.000191986, -0.0, 0.000191986}, // 5 + { -0.613954, 1.04884, -1.68339, -797.25, 1131.13, 2406.03, 0.0, 0.0, 0.0}, + { -0.610499, 1.19477, 0.208253, -97.99, 139.93, 434.25, -0.00118682, 0.000401426, 0.00125664}, + { -0.609713, 1.18536, 0.393799, -119.45, 170.64, 519.99, -0.0039619, 0.00205949, 0.00445059}, + // Test values taken from UtilsRestricted::testCalculateAngleOfAttack. + // NOTE: Existing data only contained values for TotalAOA, so AOA and SS values are filled in based on results here + // Yaw (rad), Pitch (rad), Roll (rad), EastVel (any), NorthVel (any), UpVel (any), Expected AOA (rad), Expected SS (rad), Expected TotalAOA (rad) + { 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Should return all zeroes, since no Velocity + { 0.49037, 3.76566, 5.4944, 0, 0, 0, 0, 0, 0}, // 10 + { 0, 0, 0, 16644.39016, 29208.15583, -28846.88083, 0.709169, -0.517958, 0.85083}, + { 0.49037, 3.76566, 5.4944, 16644.39016, 29208.15583, -28846.88083, -0.734134, -1.89301, 1.808114}, + { 0.49037, 3.76566, 5.4944, 1, 1, -1, -0.504567, -1.91646, 1.871923}, + { 3.14159, 3.14159, 3.14159, 1, 1, -1, 0.615483, -0.785401, 0.95532}, + { 1, 1, 1, 1, 1, -1, 0.744443, -1.64536, 1.625611}, // 15 + { 10.84689, -9.9035, -0.86838, 1, 1, -1, -0.156555, -1.24436, 1.248493}, + { 10.84689, -9.9035, -0.86838, 0, 0, -1, -0.610694, -2.16793, 2.049518}, + { 10.84689, -9.9035, -0.86838, 1, 0, -1, -0.766407, -1.14895, 1.271375}, + { 10.84689, -9.9035, -0.86838, 0, 1, -1, 0.0972667, -1.80684, 1.805702}, + { 10.84689, -9.9035, -0.86838, 1, 1, 0, 0.216204, -0.751484, 0.776078}, // 20 + { 10.84689, -9.9035, -0.86838, 0, 1, 0, 0.7906, -1.38277, 1.43893}, + { -5.60301, 10.17163, 11.52466, 43166.64503, 15583.04935, -7141.456008, 0.094936, -2.11559, 2.112865}, + { -10.49650, 2.62059, -6.6306, 13531.13069, 13212.05930, -19061.39684, -0.791297, 2.43112, 2.132763}, + { -0.40684, -11.84026, -9.24658, 6985.30190, 37348.59157, -15209.51840, -0.754879, 1.10146, 1.235094} // 24 + }; + + const double tolerance = 0.01 * simCore::DEG2RAD; + size_t testCaseCount = sizeof(testParams) / sizeof(testParams[0]); + for (size_t i = 0; i < testCaseCount; ++i) + { + double* val = testParams[i]; + const simCore::Vec3 yprVec((*val), (*(val + 1)), (*(val + 2))); + const simCore::Vec3 enuVec(*(val + 3), *(val + 4), *(val + 5)); + double aoa; + double ss; + double totalAoa; + simCore::calculateAoaSideslipTotalAoa(enuVec, yprVec, true, &aoa, &ss, &totalAoa); + SDK_ASSERT(simCore::areAnglesEqual(aoa, *(val + 6), tolerance)); + SDK_ASSERT(simCore::areAnglesEqual(ss, *(val + 7), tolerance)); + SDK_ASSERT(simCore::areAnglesEqual(totalAoa, *(val + 8), tolerance)); + } + + return rv; +} + int testBoresightAlphaBeta() { - int rv = 0; // Test Data uses X-East coordinates in meters; Yaw, Pitch, and Roll as well as expected Azimuth, Elevation, and Composite are in degrees - // Before passed into calculateRelAzEl(), X-East coordinates are converted to LLA (decimal degrees) and degrees are converted into radians + int rv = 0; // Test Data uses X-East coordinates in meters; Yaw, Pitch, and Roll as well as expected Azimuth, Elevation, and Composite are in degrees + // Before passed into calculateRelAzEl(), X-East coordinates are converted to LLA (decimal degrees) and degrees are converted into radians //{ FromX, FromY, FromZ, Yaw, Pitch, Roll, ToX, ToY, ToZ, ExpAzim, ExpElev, ExpComAng} double paramsTest[15][12] = { { 0.0, 0.0, 0.0, 0.00, 0.00, 0.00, 0.0, 0.0, 0.0, 0.00, 0.00, 0.0}, // Changing Lat & Long { 0.0, 0.0, 0.0, 0.00, 0.00, 0.00, 0.0, 1000.0, 0.0, 0.00, 0.00, 0.0}, - { 0.0, 0.0, 0.0, 0.00, 0.00, 0.00, 1000.0, 1000.0, 0.0, 45.00, 0.00, 45.00}, + { 0.0, 0.0, 0.0, 0.00, 0.00, 0.00, 1000.0, 1000.0, 0.0, 45.00, 0.00, 45.00}, { 0.0, 0.0, 0.0, 0.00, 0.00, 0.00, 1000.0, 0.0, 0.0, 90.00, 0.00, 90.00}, { 0.0, 0.0, 0.0, 0.00, 0.00, 0.00, 1000.0, -1000.0, 0.0, 135.00, 0.00, 135.00}, // #5 { 0.0, 0.0, 0.0, 0.00, 0.00, 0.00, 0.0, 1000.0, 1000.0, 0.00, 45.00, 45.00}, // Changing Alt - { 0.0, 0.0, 0.0, 0.00, 0.00, 0.00, 1000.0, 1000.0, 1000.0, 45.00, 35.26, 54.73}, + { 0.0, 0.0, 0.0, 0.00, 0.00, 0.00, 1000.0, 1000.0, 1000.0, 45.00, 35.26, 54.73}, { 0.0, 0.0, 1000.0, 0.00, 0.00, 0.00, 1000.0, 0.0, 0.0, 90.00, -45.00, 90.00}, { 0.0, 1000.0, 1000.0, 0.00, 0.00, 0.00, 1000.0, -2000.0, 0.0, 161.56, -17.55, 154.75}, - { -3000.0, 4000.0, 3000.0, 0.00, 0.00, 0.00, -6000.0, -1000.0, 0.0, -149.01, -27.24, 139.65}, // #10 + { -3000.0, 4000.0, 3000.0, 0.00, 0.00, 0.00, -6000.0, -1000.0, 0.0, -149.01, -27.24, 139.65}, // #10 - { 0.0, 0.0, 0.0, 90.00, 0.00, 0.00, -1000.0, -1000.0, 0.0, 135.00, 0.00, 135.00}, // Changing YPR - { 0.0, 0.0, 0.0, 45.00, 45.00, 0.00, 0.0, -1000.0, 1000.0, 73.67, 58.60, 81.57}, + { 0.0, 0.0, 0.0, 90.00, 0.00, 0.00, -1000.0, -1000.0, 0.0, 135.00, 0.00, 135.00}, // Changing YPR + { 0.0, 0.0, 0.0, 45.00, 45.00, 0.00, 0.0, -1000.0, 1000.0, 73.67, 58.60, 81.57}, { 1000.0, -3000.0, 5000.0, 20.00, 20.00, -80.00, 1000.0, 1000.0, 2000.0, -59.67, 7.27, 59.94}, { 2000.0, 6000.0, 20000.0, -120.00, -30.00, 0.00, 1000.0, 1000.0, 0.0, -16.47, -49.30, 51.30}, - { 0.0, -4000.0, 0.0, 160.00, 45.00, 90.00, -3000.0, 2000.0, 5000.0, -98.15, 5.25, 98.11} // #15 - }; + { 0.0, -4000.0, 0.0, 160.00, 45.00, 90.00, -3000.0, 2000.0, 5000.0, -98.15, 5.25, 98.11} // #15 + }; const double tolerance = 0.01 * simCore::DEG2RAD; simCore::CoordinateConverter cc; @@ -1730,6 +1785,7 @@ int CalculationTest(int argc, char* argv[]) rv += testMidPointHighRes(); rv += testRandom(); rv += testTaos_intercept(); + rv += testAoaSideslipTotalAoa(); rv += testBoresightAlphaBeta(); return rv; diff --git a/Testing/SimCore/CoordConvertLibTest.cpp b/Testing/SimCore/CoordConvertLibTest.cpp index a3e55a161..78b80ba77 100644 --- a/Testing/SimCore/CoordConvertLibTest.cpp +++ b/Testing/SimCore/CoordConvertLibTest.cpp @@ -970,6 +970,56 @@ int testScaledFlatEarth() return rv; } +int testStringFunctions() +{ + int rv = 0; + + // To-string testing + rv += SDK_ASSERT(simCore::coordinateSystemToString(simCore::COORD_SYS_NED) == "Topo_NED"); + rv += SDK_ASSERT(simCore::coordinateSystemToString(simCore::COORD_SYS_NWU) == "Topo_NWU"); + rv += SDK_ASSERT(simCore::coordinateSystemToString(simCore::COORD_SYS_ENU) == "Topo_ENU"); + rv += SDK_ASSERT(simCore::coordinateSystemToString(simCore::COORD_SYS_LLA) == "LLA_DD"); + rv += SDK_ASSERT(simCore::coordinateSystemToString(simCore::COORD_SYS_ECEF) == "ECEF_WGS84"); + rv += SDK_ASSERT(simCore::coordinateSystemToString(simCore::COORD_SYS_ECI) == "ECI_WGS84"); + rv += SDK_ASSERT(simCore::coordinateSystemToString(simCore::COORD_SYS_XEAST) == "TangentPlane_XEast"); + rv += SDK_ASSERT(simCore::coordinateSystemToString(simCore::COORD_SYS_GTP) == "TangentPlane_Generic"); + + // From-string testing + simCore::CoordinateSystem coordSys; + rv += SDK_ASSERT(simCore::coordinateSystemFromString("Topo_NED", coordSys) == 0); + rv += SDK_ASSERT(coordSys == simCore::COORD_SYS_NED); + // Test capitalization + rv += SDK_ASSERT(simCore::coordinateSystemFromString("topo_ned", coordSys) == 0); + rv += SDK_ASSERT(coordSys == simCore::COORD_SYS_NED); + rv += SDK_ASSERT(simCore::coordinateSystemFromString("TOPO_NED", coordSys) == 0); + rv += SDK_ASSERT(coordSys == simCore::COORD_SYS_NED); + + rv += SDK_ASSERT(simCore::coordinateSystemFromString("Topo_NWU", coordSys) == 0); + rv += SDK_ASSERT(coordSys == simCore::COORD_SYS_NWU); + rv += SDK_ASSERT(simCore::coordinateSystemFromString("Topo_ENU", coordSys) == 0); + rv += SDK_ASSERT(coordSys == simCore::COORD_SYS_ENU); + rv += SDK_ASSERT(simCore::coordinateSystemFromString("LLA_DD", coordSys) == 0); + rv += SDK_ASSERT(coordSys == simCore::COORD_SYS_LLA); + rv += SDK_ASSERT(simCore::coordinateSystemFromString("ECEF_WGS84", coordSys) == 0); + rv += SDK_ASSERT(coordSys == simCore::COORD_SYS_ECEF); + rv += SDK_ASSERT(simCore::coordinateSystemFromString("ECI_WGS84", coordSys) == 0); + rv += SDK_ASSERT(coordSys == simCore::COORD_SYS_ECI); + rv += SDK_ASSERT(simCore::coordinateSystemFromString("TangentPlane_XEast", coordSys) == 0); + rv += SDK_ASSERT(coordSys == simCore::COORD_SYS_XEAST); + rv += SDK_ASSERT(simCore::coordinateSystemFromString("TangentPlane_Generic", coordSys) == 0); + rv += SDK_ASSERT(coordSys == simCore::COORD_SYS_GTP); + + // Test the oddball LLA legacy strings + rv += SDK_ASSERT(simCore::coordinateSystemFromString("LLA_DMD", coordSys) == 0); + rv += SDK_ASSERT(coordSys == simCore::COORD_SYS_LLA); + rv += SDK_ASSERT(simCore::coordinateSystemFromString("LLA_DMS", coordSys) == 0); + rv += SDK_ASSERT(coordSys == simCore::COORD_SYS_LLA); + + std::cout << std::endl << "String Functions test case: "; + std::cout << (rv==0 ? "PASSED" : "FAILED") << std::endl; + return rv; +} + } //=========================================================================== @@ -1038,5 +1088,6 @@ int CoordConvertLibTest(int _argc_, char *_argv_[]) rv += testGtpRotation(); rv += testScaledFlatEarthPole(); rv += testScaledFlatEarth(); + rv += testStringFunctions(); return rv; } diff --git a/Testing/SimCore/CsvReaderTest.cpp b/Testing/SimCore/CsvReaderTest.cpp new file mode 100644 index 000000000..d995bd3d1 --- /dev/null +++ b/Testing/SimCore/CsvReaderTest.cpp @@ -0,0 +1,239 @@ +/* -*- mode: c++ -*- */ +/**************************************************************************** + ***** ***** + ***** Classification: UNCLASSIFIED ***** + ***** Classified By: ***** + ***** Declassify On: ***** + ***** ***** + **************************************************************************** + * + * + * Developed by: Naval Research Laboratory, Tactical Electronic Warfare Div. + * EW Modeling & Simulation, Code 5773 + * 4555 Overlook Ave. + * Washington, D.C. 20375-5339 + * + * License for source code at https://simdis.nrl.navy.mil/License.aspx + * + * The U.S. Government retains all rights to use, duplicate, distribute, + * disclose, or release this software. + * + */ +#include +#include "simCore/Common/SDKAssert.h" +#include "simCore/String/CsvReader.h" + +namespace { + +int testCsvReadLine() +{ + int rv = 0; + + std::istringstream stream("one,two,three\nfour,five,six"); + + simCore::CsvReader reader(stream); + std::vector tokens; + + // Test basic stream + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 3); + rv += SDK_ASSERT(tokens[0] == "one"); + rv += SDK_ASSERT(tokens[1] == "two"); + rv += SDK_ASSERT(tokens[2] == "three"); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 3); + rv += SDK_ASSERT(tokens[0] == "four"); + rv += SDK_ASSERT(tokens[1] == "five"); + rv += SDK_ASSERT(tokens[2] == "six"); + rv += SDK_ASSERT(reader.readLine(tokens) == 1); + + // Test rows of differing lengths + stream.clear(); + stream.str("one,two\nthree,four,five\nsix,seven"); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 2); + rv += SDK_ASSERT(tokens[0] == "one"); + rv += SDK_ASSERT(tokens[1] == "two"); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 3); + rv += SDK_ASSERT(tokens[0] == "three"); + rv += SDK_ASSERT(tokens[1] == "four"); + rv += SDK_ASSERT(tokens[2] == "five"); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 2); + rv += SDK_ASSERT(tokens[0] == "six"); + rv += SDK_ASSERT(tokens[1] == "seven"); + rv += SDK_ASSERT(reader.readLine(tokens) == 1); + + // Test stream with empty lines + stream.clear(); + stream.str("one,two\n \nthree,four,five\n \nsix,seven"); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 2); + rv += SDK_ASSERT(tokens[0] == "one"); + rv += SDK_ASSERT(tokens[1] == "two"); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 3); + rv += SDK_ASSERT(tokens[0] == "three"); + rv += SDK_ASSERT(tokens[1] == "four"); + rv += SDK_ASSERT(tokens[2] == "five"); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 2); + rv += SDK_ASSERT(tokens[0] == "six"); + rv += SDK_ASSERT(tokens[1] == "seven"); + rv += SDK_ASSERT(reader.readLine(tokens) == 1); + + // Test basic stream with odd whitespace thrown in + stream.clear(); + stream.str("one , two,thr ee\n four , five,six"); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 3); + rv += SDK_ASSERT(tokens[0] == "one "); + rv += SDK_ASSERT(tokens[1] == " two"); + rv += SDK_ASSERT(tokens[2] == "thr ee"); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 3); + rv += SDK_ASSERT(tokens[0] == " four "); + rv += SDK_ASSERT(tokens[1] == " five"); + rv += SDK_ASSERT(tokens[2] == "six"); + rv += SDK_ASSERT(reader.readLine(tokens) == 1); + + return rv; +} + +int testCsvReadLineTrimmed() +{ + int rv = 0; + + // Same leading and trailing whitespace test cases from testCsvReadLine(), but using readLineTrimmed + std::istringstream stream("one , two,thr ee\n four , five,six"); + + simCore::CsvReader reader(stream); + std::vector tokens; + + // Test basic stream + rv += SDK_ASSERT(reader.readLineTrimmed(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 3); + rv += SDK_ASSERT(tokens[0] == "one"); + rv += SDK_ASSERT(tokens[1] == "two"); + rv += SDK_ASSERT(tokens[2] == "thr ee"); + rv += SDK_ASSERT(reader.readLineTrimmed(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 3); + rv += SDK_ASSERT(tokens[0] == "four"); + rv += SDK_ASSERT(tokens[1] == "five"); + rv += SDK_ASSERT(tokens[2] == "six"); + rv += SDK_ASSERT(reader.readLineTrimmed(tokens) == 1); + + return 0; +} + +int testCsvWithComments() +{ + int rv = 0; + + std::istringstream stream("#column 1, column 2, column 3\none,two,three\nfour,five,six"); + + simCore::CsvReader reader(stream); + std::vector tokens; + + // Test comments + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 3); + rv += SDK_ASSERT(tokens[0] == "one"); + rv += SDK_ASSERT(tokens[1] == "two"); + rv += SDK_ASSERT(tokens[2] == "three"); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 3); + rv += SDK_ASSERT(tokens[0] == "four"); + rv += SDK_ASSERT(tokens[1] == "five"); + rv += SDK_ASSERT(tokens[2] == "six"); + rv += SDK_ASSERT(reader.readLine(tokens) == 1); + + // Test changing the comment char + reader.setCommentChar('$'); + stream.clear(); + stream.str("$column 1, column 2, column 3\none,two,three\nfour,five,six"); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 3); + rv += SDK_ASSERT(tokens[0] == "one"); + rv += SDK_ASSERT(tokens[1] == "two"); + rv += SDK_ASSERT(tokens[2] == "three"); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(tokens.size() == 3); + rv += SDK_ASSERT(tokens[0] == "four"); + rv += SDK_ASSERT(tokens[1] == "five"); + rv += SDK_ASSERT(tokens[2] == "six"); + rv += SDK_ASSERT(reader.readLine(tokens) == 1); + + return rv; +} + + +int testCsvLineNumber() +{ + int rv = 0; + + std::istringstream stream("#col 1, col 2, col3\none,two\n \n \nthree,four,five\nsix,seven"); + + simCore::CsvReader reader(stream); + std::vector tokens; + + rv += SDK_ASSERT(reader.lineNumber() == 0); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + // Skips comment line + rv += SDK_ASSERT(reader.lineNumber() == 2); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + // Skips empty lines + rv += SDK_ASSERT(reader.lineNumber() == 5); + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(reader.lineNumber() == 6); + rv += SDK_ASSERT(reader.readLine(tokens) == 1); + + return rv; +} + +int testReadEmptyLines() +{ + int rv = 0; + + std::istringstream stream(" \n#col 1, col 2, col3\none,two\n \nthree,four,five\n \nsix,seven"); + + simCore::CsvReader reader(stream); + std::vector tokens; + + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(reader.lineNumber() == 3); + rv += SDK_ASSERT(tokens.size() == 2); // [one, two] + + // Read line skipping empty lines, will skip line 4 + rv += SDK_ASSERT(reader.readLine(tokens) == 0); + rv += SDK_ASSERT(reader.lineNumber() == 5); + rv += SDK_ASSERT(tokens.size() == 3); // [three, four, five] + + // Read line without skipping empty lines + rv += SDK_ASSERT(reader.readLine(tokens, false) == 0); + rv += SDK_ASSERT(reader.lineNumber() == 6); + rv += SDK_ASSERT(tokens.empty()); // empty line + + rv += SDK_ASSERT(reader.readLine(tokens, false) == 0); + rv += SDK_ASSERT(reader.lineNumber() == 7); + rv += SDK_ASSERT(tokens.size() == 2); // [six, seven] + rv += SDK_ASSERT(reader.readLine(tokens, false) == 1); + + return rv; +} + +} + +int CsvReaderTest(int argc, char *argv[]) +{ + int rv = 0; + + rv += SDK_ASSERT(testCsvReadLine() == 0); + rv += SDK_ASSERT(testCsvReadLineTrimmed() == 0); + rv += SDK_ASSERT(testCsvWithComments() == 0); + rv += SDK_ASSERT(testCsvLineNumber() == 0); + rv += SDK_ASSERT(testReadEmptyLines() == 0); + + return rv; +} diff --git a/Testing/SimData/CMakeLists.txt b/Testing/SimData/CMakeLists.txt index a8e3dbc3f..cdac06bf2 100644 --- a/Testing/SimData/CMakeLists.txt +++ b/Testing/SimData/CMakeLists.txt @@ -33,7 +33,7 @@ add_executable(SimDataTests ${SimDataTestFiles}) target_link_libraries(SimDataTests PRIVATE simData simUtil) set_target_properties(SimDataTests PROPERTIES FOLDER "Unit Tests" - PROJECT_LABEL "Unit Tests - SimData" + PROJECT_LABEL "simData Test" ) # simQt is used in CategoryDataTest for its Regular Expression implementation diff --git a/Testing/SimData/DataStorePerformanceTest/CMakeLists.txt b/Testing/SimData/DataStorePerformanceTest/CMakeLists.txt index 22376be51..94d201d58 100644 --- a/Testing/SimData/DataStorePerformanceTest/CMakeLists.txt +++ b/Testing/SimData/DataStorePerformanceTest/CMakeLists.txt @@ -11,5 +11,5 @@ add_executable(DataStorePerformanceTest DataStorePerformanceTest.cpp) target_link_libraries(DataStorePerformanceTest PRIVATE simData simUtil) set_target_properties(DataStorePerformanceTest PROPERTIES FOLDER "Performance Tests" - PROJECT_LABEL "Performance Tests - DataStore" + PROJECT_LABEL "DataStore Test" ) diff --git a/Testing/SimData/MemoryDataTableTest.cpp b/Testing/SimData/MemoryDataTableTest.cpp index 8e5632c52..2f39afdc0 100644 --- a/Testing/SimData/MemoryDataTableTest.cpp +++ b/Testing/SimData/MemoryDataTableTest.cpp @@ -1240,6 +1240,111 @@ int dataLimitingTest() return rv; }; +int getTimeRangeTest() +{ + int rv = 0; + + simUtil::DataStoreTestHelper testHelper; + simData::DataStore* ds = testHelper.dataStore(); + uint64_t plat1 = testHelper.addPlatform(); + ds->setDataLimiting(true); + simData::DataStore::Transaction t; + simData::PlatformPrefs* prefs = ds->mutable_platformPrefs(plat1, &t); + prefs->mutable_commonprefs()->set_datalimitpoints(6); // start out limiting to 6 points + t.commit(); + + simData::DataTable* table = NULL; + rv += SDK_ASSERT(ds->dataTableManager().addDataTable(plat1, "Data Limit Test Table", &table).isSuccess()); + + // Add a column + simData::TableColumn* column1 = NULL; + rv += SDK_ASSERT(table->addColumn("1", VT_INT32, 0, &column1).isSuccess()); + + // add some rows + simData::TableRow newRow; + newRow.setTime(1.0); + newRow.setValue(column1->columnId(), 40); + rv += SDK_ASSERT(table->addRow(newRow).isSuccess()); + newRow.clear(); + newRow.setTime(2.0); + newRow.setValue(column1->columnId(), 50); + rv += SDK_ASSERT(table->addRow(newRow).isSuccess()); + + // Verify expected results from getTimeRange() (all data in fresh) + double begin; + double end; + rv += SDK_ASSERT(column1->getTimeRange(begin, end) == 0); + rv += SDK_ASSERT(begin == 1.0); + rv += SDK_ASSERT(end == 2.0); + + newRow.clear(); + newRow.setTime(3.0); + newRow.setValue(column1->columnId(), 60); + rv += SDK_ASSERT(table->addRow(newRow).isSuccess()); + // Verify expected results from getTimeRange() (all data in stale) + rv += SDK_ASSERT(column1->getTimeRange(begin, end) == 0); + rv += SDK_ASSERT(begin == 1.0); + rv += SDK_ASSERT(end == 3.0); + + newRow.clear(); + newRow.setTime(4.0); + newRow.setValue(column1->columnId(), 70); + rv += SDK_ASSERT(table->addRow(newRow).isSuccess()); + newRow.clear(); + newRow.setTime(5.0); + newRow.setValue(column1->columnId(), 80); + rv += SDK_ASSERT(table->addRow(newRow).isSuccess()); + + // Verify expected results from getTimeRange() (data split between fresh and stale) + rv += SDK_ASSERT(column1->getTimeRange(begin, end) == 0); + rv += SDK_ASSERT(begin == 1.0); + rv += SDK_ASSERT(end == 5.0); + + // Test again, with data being added in reverse. Creates situation where the + // DoubleBufferTimeContainer's FRESH bin has earlier times than the STALE bin + simData::TableColumn* column2 = NULL; + rv += SDK_ASSERT(table->addColumn("2", VT_INT32, 0, &column2).isSuccess()); + + newRow.clear(); + newRow.setTime(5.0); + newRow.setValue(column2->columnId(), 40); + rv += SDK_ASSERT(table->addRow(newRow).isSuccess()); + newRow.clear(); + newRow.setTime(4.0); + newRow.setValue(column2->columnId(), 50); + rv += SDK_ASSERT(table->addRow(newRow).isSuccess()); + + // Verify expected results from getTimeRange() (all data in fresh) + rv += SDK_ASSERT(column2->getTimeRange(begin, end) == 0); + rv += SDK_ASSERT(begin == 4.0); + rv += SDK_ASSERT(end == 5.0); + + newRow.clear(); + newRow.setTime(3.0); + newRow.setValue(column2->columnId(), 60); + rv += SDK_ASSERT(table->addRow(newRow).isSuccess()); + // Verify expected results from getTimeRange() (all data in stale) + rv += SDK_ASSERT(column2->getTimeRange(begin, end) == 0); + rv += SDK_ASSERT(begin == 3.0); + rv += SDK_ASSERT(end == 5.0); + + newRow.clear(); + newRow.setTime(2.0); + newRow.setValue(column2->columnId(), 70); + rv += SDK_ASSERT(table->addRow(newRow).isSuccess()); + newRow.clear(); + newRow.setTime(1.0); + newRow.setValue(column2->columnId(), 80); + rv += SDK_ASSERT(table->addRow(newRow).isSuccess()); + + // Verify expected results from getTimeRange() (data split between fresh and stale) + rv += SDK_ASSERT(column2->getTimeRange(begin, end) == 0); + rv += SDK_ASSERT(begin == 1.0); + rv += SDK_ASSERT(end == 5.0); + + return rv; +} + int subTableIterationTest(simData::MemoryTable::TimeContainer* newTimeContainer) { // Create a mini typedef to reduce typing @@ -2114,6 +2219,41 @@ int doubleBufferTimeContainerTest() return rv; } +int testPartialFlush() +{ + simData::MemoryDataStore ds; + simData::DataTableManager& mgr = ds.dataTableManager(); + simData::DataTable* table = NULL; + int rv = 0; + rv += SDK_ASSERT(mgr.addDataTable(1, "Test Table", &table).isSuccess()); + // Create two columns and add data to both + simData::TableColumn* column1; + simData::TableColumn* column2; + table->addColumn("Test Column 1", simData::VT_DOUBLE, 0, &column1); + table->addColumn("Test Column 2", simData::VT_DOUBLE, 0, &column2); + rv += SDK_ASSERT(table->columnCount() == 2); + rv += SDK_ASSERT(column1->empty()); + rv += SDK_ASSERT(column2->empty()); + for (double i = 0; i < 10; ++i) + { + simData::TableRow row; + row.setTime(i); + row.setValue(column1->columnId(), i); + row.setValue(column2->columnId(), i); + table->addRow(row); + } + rv += SDK_ASSERT(column1->size() == 10); + rv += SDK_ASSERT(column2->size() == 10); + + // Flush only the first column + table->flush(column1->columnId()); + rv += SDK_ASSERT(column1->size() == 0); + rv += SDK_ASSERT(column2->size() == 10); + rv += SDK_ASSERT(table->columnCount() == 2); + + return rv; +} + } int MemoryDataTableTest(int argc, char* argv[]) @@ -2132,5 +2272,7 @@ int MemoryDataTableTest(int argc, char* argv[]) rv += subTableIterationTest(new simData::MemoryTable::DoubleBufferTimeContainer()); rv += testColumnIteration(); rv += doubleBufferTimeContainerTest(); + rv += testPartialFlush(); + rv += getTimeRangeTest(); return rv; } diff --git a/Testing/SimNotify/CMakeLists.txt b/Testing/SimNotify/CMakeLists.txt index f913f49b6..9f5ff1fbe 100644 --- a/Testing/SimNotify/CMakeLists.txt +++ b/Testing/SimNotify/CMakeLists.txt @@ -16,7 +16,7 @@ add_executable(SimNotifyTests ${SimNotifyTestFiles} NotifySupport.h NotifySuppor target_link_libraries(SimNotifyTests PRIVATE simNotify simCore) set_target_properties(SimNotifyTests PROPERTIES FOLDER "Unit Tests" - PROJECT_LABEL "Unit Tests - SimNotify" + PROJECT_LABEL "simNotify Test" ) add_test(NAME TestNotify1 COMMAND SimNotifyTests TestNotify) add_test(NAME TestNotify2 COMMAND SimNotifyTests NotifyTest) diff --git a/Testing/SimQt/CMakeLists.txt b/Testing/SimQt/CMakeLists.txt index 2743e7bd3..20117f8a5 100644 --- a/Testing/SimQt/CMakeLists.txt +++ b/Testing/SimQt/CMakeLists.txt @@ -38,7 +38,7 @@ target_link_libraries(SimQtTests PRIVATE simQt simCore) target_include_directories(SimQtTests PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) set_target_properties(SimQtTests PROPERTIES FOLDER "Unit Tests" - PROJECT_LABEL "Unit Tests - SimQt" + PROJECT_LABEL "simQt Test" ) VSI_QT_USE_MODULES(SimQtTests LINK_PRIVATE Widgets) diff --git a/Testing/SimUtil/CMakeLists.txt b/Testing/SimUtil/CMakeLists.txt index f294f80fd..e30b15aa1 100644 --- a/Testing/SimUtil/CMakeLists.txt +++ b/Testing/SimUtil/CMakeLists.txt @@ -13,7 +13,7 @@ add_executable(SimUtilTests ${SimUtilTestFiles}) target_link_libraries(SimUtilTests PRIVATE simUtil) set_target_properties(SimUtilTests PROPERTIES FOLDER "Unit Tests" - PROJECT_LABEL "Unit Tests - SimUtil" + PROJECT_LABEL "simUtil Test" ) add_test(NAME IdMapperTest COMMAND SimUtilTests IdMapperTest) diff --git a/Testing/SimVis/CMakeLists.txt b/Testing/SimVis/CMakeLists.txt index 2cdd29ee5..ee3ac9a99 100644 --- a/Testing/SimVis/CMakeLists.txt +++ b/Testing/SimVis/CMakeLists.txt @@ -14,7 +14,7 @@ add_executable(SimVisTests ${SimVisTestFiles}) target_link_libraries(SimVisTests PRIVATE simCore simVis) set_target_properties(SimVisTests PROPERTIES FOLDER "Unit Tests" - PROJECT_LABEL "Unit Tests - SimVis" + PROJECT_LABEL "simVis Test" ) add_test(NAME LocatorTest COMMAND SimVisTests LocatorTest) diff --git a/swig/CMakeLists.txt b/swig/CMakeLists.txt new file mode 100644 index 000000000..8bb452cce --- /dev/null +++ b/swig/CMakeLists.txt @@ -0,0 +1,70 @@ +if(NOT SWIG_FOUND OR NOT TARGET PYTHON3) + return() +endif() + +# Avoid making SWIG targets with older CMake, to avoid extra complications +if("${CMAKE_VERSION}" VERSION_LESS 3.13) + return() +endif() + +project(SIMDIS_SDK_SWIG) + +mark_as_advanced(SWIG_EXECUTABLE) + +include(${SWIG_USE_FILE}) +set(CMAKE_SWIG_FLAGS "") +set(CMAKE_SWIG_OUTDIR "${CMAKE_CURRENT_BINARY_DIR}/bin") +# Linux uses LIBRARY for .so, Windows uses RUNTIME for pyd +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin") +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin") +include_directories("${PYTHON3_LIBRARY_INCLUDE_PATH}") +include_directories("${SIMDIS_SDK_SOURCE_DIR}") + +#macro for installing the specified Python file and the library +macro(InstallPythonFiles TARGET) + + if(WIN32) + set(PYD_EXTENSION "pyd") + set(PYD_DESTINATION "lib/${BUILD_SYSTEM_ARCH}-nt/python3.${PYTHON3_MINOR}") + else() + set(PYD_EXTENSION "so") + set(PYD_DESTINATION "lib/amd64-linux/python3.${PYTHON3_MINOR}/lib-dynload") + endif() + + install(FILES ${CMAKE_SWIG_OUTDIR}/${TARGET}.py + DESTINATION bin/pythonScripts + COMPONENT SIMDIS + ) + + if(WIN32) + install(PROGRAMS + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/Release/_${TARGET}.${PYD_EXTENSION} + DESTINATION ${PYD_DESTINATION} + COMPONENT SIMDIS + CONFIGURATIONS Release + ) + install(PROGRAMS + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/RelWithDebInfo/_${TARGET}.${PYD_EXTENSION} + DESTINATION ${PYD_DESTINATION} + COMPONENT SIMDIS + CONFIGURATIONS RelWithDebInfo + ) + install(PROGRAMS + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/Debug/_${TARGET}_d.${PYD_EXTENSION} + DESTINATION ${PYD_DESTINATION} + COMPONENT SIMDIS + CONFIGURATIONS Debug + ) + else() + # The debug library does not have a _d so only one install is needed + install(PROGRAMS + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/_${TARGET}.${PYD_EXTENSION} + DESTINATION ${PYD_DESTINATION} + COMPONENT SIMDIS + ) + endif() + +endmacro() + +add_subdirectory(simCore) +add_subdirectory(Testing) diff --git a/swig/Testing/CMakeLists.txt b/swig/Testing/CMakeLists.txt new file mode 100644 index 000000000..d625dfb22 --- /dev/null +++ b/swig/Testing/CMakeLists.txt @@ -0,0 +1,41 @@ +# Early return if no unit testing +if(NOT ENABLE_CDASH_PROJECTS AND NOT ENABLE_UNIT_TESTING) + return() +endif() + +# Pull the version from CMakeImport/ImportPython3.cmake +set(DESIRED_PYTHON_VERSION ${PYTHON3_MAJOR}.${PYTHON3_MINOR}.${PYTHON3_PATCH}) + +# FindPython3.cmake was added in 3.12 and is better than PythonInterp +if(WIN32) + # Need to match the version exactly on Windows for some reason or crash + find_package(Python3 ${DESIRED_PYTHON_VERSION} EXACT COMPONENTS Interpreter QUIET) + # Python3's find_package() EXACT does not work as advertised in all cases. + if(NOT DESIRED_PYTHON_VERSION VERSION_EQUAL Python3_VERSION) + return() + endif() +else() + # Linux is more forgiving + find_package(Python3 3.6 COMPONENTS Interpreter QUIET) +endif() + +# Return if there's no interpreter +if(NOT Python3_Interpreter_FOUND) + return() +endif() + +# Add the tests +add_test(NAME simCore_SWIG_Test COMMAND "${Python3_EXECUTABLE}" "TestSimCore.py" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + +# Windows insists on installing under a CONFIG directory under the runtime output directory +if(WIN32) + set(PYTHON_PATH "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}\;${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/$") +else() + set(PYTHON_PATH "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") +endif() + +set_tests_properties( + simCore_SWIG_Test + PROPERTIES + ENVIRONMENT "PYTHONPATH=${PYTHON_PATH}" +) diff --git a/swig/Testing/TestSimCore.py b/swig/Testing/TestSimCore.py new file mode 100644 index 000000000..9e3e66b8f --- /dev/null +++ b/swig/Testing/TestSimCore.py @@ -0,0 +1,588 @@ +import os, sys, math, timeit + +# Try to use SIMDIS_DIR to set up import paths +if 'SIMDIS_DIR' in os.environ: + # For _module shared object: + if os.name == "nt": + sys.path.append(os.environ['SIMDIS_DIR'] + '/lib/amd64-nt/python3.8') + try: + # Python 3.8 does not want to respect PATH for loading dependent DLLs. It introduces + # a new method to attempt to fix the problem. Try/except ignores errors in older Python. + # Without this, the _simCore.pyd needs to go in the same place as simNotify/simCore. + os.add_dll_directory(os.environ['SIMDIS_DIR'] + '/bin/amd64-nt') + except: + pass + pass + else: + sys.path.append(os.environ['SIMDIS_DIR'] + '/lib/amd64-linux/python3.8/lib-dynload') + # For module wrapper: + sys.path.append(os.environ['SIMDIS_DIR'] + '/bin/pythonScripts') + +import simCore + +# simCore/Common + +############################# +# Exception.h +e = simCore.Exception("ExceptionFromPython", "Test exception successfully generated. Ignore Error.", 37) +assert(e is not None) +assert(e.rawWhat() == "Test exception successfully generated. Ignore Error.") +assert(e.line() == 37) +e = None + +############################# +# Time.h +# Wrapper function that sleeps for a second and takes no args, so that it can be passed through python's timeit() function easily. +# Sleeping for more than a second, as function occasionally executes in slightly less than a tenth of a second if given 100 ms as a parameter. +def sleepWrapper(): + simCore.Sleep(100) +print("Sleeping for about a tenth of a second.") +assert(timeit.timeit(sleepWrapper, number=1) >= .05) + +# simCore/Calc + +############################# +# Vec3.h +assert(simCore.Vec3() is not None) +v = simCore.Vec3(1, 2, 3) +assert(v is not None) +assert(v.x() == 1) +assert(v.y() == 2) +assert(v.z() == 3) +v.set(3, 1, 2) +assert(v.x() == 3) +assert(v.y() == 1) +assert(v.z() == 2) +v.scale(2) +assert(v.x() == 6) +assert(v.y() == 2) +assert(v.z() == 4) + +############################# +# Angle.h +assert(simCore.angFix360(720) == 0) +assert(simCore.areAnglesEqual(0, math.pi * 2) == True) +angleExtents = simCore.ANGLEEXTENTS_TWOPI +assert(angleExtents is not None) +assert(simCore.angFixDegrees(360, angleExtents) == 0) + +############################# +# CoordinateSystem.h +assert(simCore.WGS_84 is not None) +assert(simCore.GEOMETRIC_HORIZON is not None) +assert(simCore.WGS_E == 0.0818191908426) +assert(simCore.WGS_ESQ == simCore.WGS_E * simCore.WGS_E) +assert(simCore.EARTH_ROTATION_RATE == 7292115.1467e-11) +coordSystem = simCore.COORD_SYS_NED +assert(coordSystem is not None) +assert(simCore.coordinateSystemToString(coordSystem) == "Topo_NED") +rv, coordSystem = simCore.coordinateSystemFromString("Topo_NED") +assert(rv == 0) +assert(coordSystem == simCore.COORD_SYS_NED) + +############################# +# Coordinate.h +coordinateOne = simCore.Coordinate() +coordinateTwo = simCore.Coordinate(simCore.COORD_SYS_LLA, v) +assert(coordinateOne is not None) +assert(coordinateTwo is not None) +assert(coordinateOne.hasAcceleration() == False) +assert(coordinateTwo.x() == 6) +assert(coordinateTwo.y() == 2) +assert(coordinateTwo.z() == 4) + +############################# +# Math.h +assert(simCore.intSdkMax(-1, 2) == 2) +assert(simCore.intSdkMin(-1, 2) == -1) +assert(simCore.doubleSdkMax(-1, 2) == 2) +assert(simCore.doubleSdkMin(-1.0, 2) == -1) +assert(simCore.intSquare(2) == 4) +assert(simCore.intSign(2) == 1) +assert(simCore.intSign(0) == 0) +assert(simCore.doubleSign(-1.3) == -1) +assert(simCore.doubleSdkMax(-1.3, 2.0) == 2.0) +assert(simCore.doubleSdkMin(-1.3, 2.0) == -1.3) +assert(simCore.doubleSquare(2.0) == 4.0) +assert(simCore.doubleSign(-1.2) == -1) +assert(simCore.doubleSign(0.0) == 0) +assert(simCore.rint(1.1) == 1) +assert(simCore.rint(1.5) == 2) +assert(simCore.rint(2.5) == 2) +assert(simCore.rint(3.5) == 4) +assert(simCore.rint(2) == 2) +assert(simCore.round(1) == 1) +assert(simCore.round(1.1) == 1) +assert(simCore.round(1.5) == 2) +assert(not simCore.odd(2)) +assert(simCore.odd(3)) +assert(simCore.areEqual(1, 1.0000001)) +assert(simCore.guessStepSize(10, 0) == 1) +assert(simCore.isFinite(v)) +v2 = simCore.Vec3(1, 2, 3) +assert(v2 is not None) +assert(simCore.v3Distance(v, v2) == math.sqrt(26)) +v2 = simCore.v3Scale(2, v) +assert(v2.x() == 12) +assert(v2.y() == 4) +assert(v2.z() == 8) +vecLen = simCore.v3Unit(v2) +assert(vecLen is not None) +assert(vecLen == math.sqrt(224)) +assert(v2.x() == 12 / vecLen) +assert(v2.y() == 4 / vecLen) +assert(v2.z() == 8 / vecLen) +mantissa, numZeroes = simCore.toScientific(12345) +assert(mantissa is not None) +assert(numZeroes is not None) +mantissa, numZeroes = simCore.toScientific(12345) +assert(mantissa is not None) +assert(mantissa == 1.2345) +assert(numZeroes is not None) +assert(numZeroes == 4) + +############################# +# CoordinateConverter.h +coordConverter = simCore.CoordinateConverter() +assert(coordConverter is not None) +assert(not coordConverter.hasReferenceOrigin()) +refOrigin = simCore.Vec3(44.544, -72.782, 1699) +assert(refOrigin.x() == 44.544) +assert(refOrigin.y() == -72.782) +assert(refOrigin.z() == 1699) +coordConverter.setReferenceOriginDegrees(refOrigin) +assert(coordConverter.hasReferenceOrigin()) +assert(simCore.Coordinate() is not None) +llaCoordinate = simCore.Coordinate(simCore.COORD_SYS_LLA, simCore.Vec3(0, 45, 1000)) +assert(llaCoordinate is not None) +assert(llaCoordinate.coordinateSystem() == simCore.COORD_SYS_LLA) +assert(llaCoordinate.position() is not None) +assert(coordConverter.lonRadius() is not None) +assert(coordConverter.latRadius() is not None) +assert(coordConverter.tangentPlaneOffsetX() is not None) +assert(coordConverter.tangentPlaneOffsetY() is not None) +assert(coordConverter.tangentPlaneRotation() is not None) +assert(coordConverter.setTangentPlaneOffsetX(1.0) is None) +assert(coordConverter.setTangentPlaneOffsetY(1.0) is None) +assert(coordConverter.setTangentPlaneRotation(1.0) is None) +assert(coordConverter.setReferenceOriginDegrees() is None) +assert(coordConverter.setReferenceOriginDegrees(1.0) is None) +assert(coordConverter.setReferenceOriginDegrees(1.0, 2.0) is None) +assert(coordConverter.setReferenceOriginDegrees(1.0, 2.0, 3.0) is None) +assert(coordConverter.setReferenceOriginDegrees(simCore.Vec3(1.0, 2.0, 3.0)) is None) +assert(coordConverter.setReferenceOrigin() is None) +assert(coordConverter.setReferenceOrigin(1.0) is None) +assert(coordConverter.setReferenceOrigin(1.0, 2.0) is None) +assert(coordConverter.setReferenceOrigin(1.0, 2.0, 3.0) is None) +assert(coordConverter.setReferenceOrigin(simCore.Vec3(1.0, 2.0, 3.0)) is None) +assert(coordConverter.setTangentPlaneOffsets(1.0, 2.0, 3.0) is None) +assert(coordConverter.setTangentPlaneOffsets(1.0, 2.0) is None) +rv, outCoord = coordConverter.convert(llaCoordinate, simCore.COORD_SYS_ECEF) +assert(rv is not None and outCoord is not None) +assert(outCoord.coordinateSystem() == simCore.COORD_SYS_ECEF) +# Note, static conversion methods are called by CoordinateConverter.convert() internally. They +# are not explicitly tested in this script. + +############################# +# Gars.h +isValid, err, lonBand, latPrimaryIdx, latSecondaryIdx, quad15, key5 = simCore.Gars.isValidGars("718KR45") +assert(isValid is not None and err is not None and lonBand is not None and latPrimaryIdx is not None and latSecondaryIdx is not None + and quad15 is not None and key5 is not None) +rv, latRad, lonRad, err = simCore.Gars.convertGarsToGeodetic("718KR45") +assert(rv is not None and latRad is not None and lonRad is not None and err is not None) +rv, gars, err = simCore.Gars.convertGeodeticToGars(1.0, 1.1, simCore.Gars.GARS_5) +assert(rv is not None and gars is not None and err is not None) +rv, gars, err = simCore.Gars.convertGeodeticToGars(1.0, 1.1) +assert(rv is not None and gars is not None and err is not None) + +############################# +# Geometry.h +v1 = simCore.Vec3(0, 0, 0) +v2 = simCore.Vec3(0, 0, 0) +v3 = simCore.Vec3(0, 0, 0) +p = simCore.Plane(v1, v2, v3) +assert(p is not None) +v4 = simCore.Vec3(0, 0, 0) +assert(p.distance(v4) is not None) +poly = simCore.Polytope() +assert(poly is not None) +assert(poly.contains(v4) is not None) +fence = simCore.GeoFence() +assert(fence is not None) +assert(fence.valid() is not None) +assert(fence.contains(v4) is not None) +assert(fence.contains(coordinateTwo) is not None) + +############################# +# Interpolation.h +assert(simCore.getFactor(10, 10, 10) is not None) +assert(simCore.nearestNeighborInterpolate(10, 10, 10) is not None) +outputVec = simCore.Vec3LinearInterpolate(v1, v2, 3, 4, 5) +assert(outputVec is not None and outputVec.x() is not None) +assert(simCore.DoubleLinearInterpolate(2.0, 4.0, 10.0, 25.0, 30.0) == 3.5) +assert(simCore.linearInterpolateAngle(1, 2, 3, 4, 5) is not None) +assert(simCore.intBilinearInterpolate(1, 2, 3, 4, 5, 6) is not None) +assert(simCore.doubleBilinearInterpolate(1, 2, 3, 4, 5, 6) is not None) + +############################# +# MagneticVariance.h +assert(simCore.MAGVAR_TRUE == 1) +wmm = simCore.WorldMagneticModel() +assert(wmm is not None) +rv, varianceRad = wmm.calculateMagneticVariance(v, 1, 2) +assert(rv is not None and varianceRad is not None) + +############################# +# Mgrs.h +mgrs = simCore.Mgrs() +rv, lat, lon, err = mgrs.convertMgrsToGeodetic("33CWM1974418352") +assert(rv is not None and lat is not None and lon is not None and err is not None) +rv, zone, gzdLetters, easting, northing, err = mgrs.breakMgrsString("33CWM1974418352") +assert(rv is not None and zone is not None and gzdLetters is not None and easting is not None + and northing is not None and err is not None) +# rv, northPole, utmEasting, utmNorthing, err = mgrs.convertMgrsToUtm(33, "CWM", 19744.0, 18352.0) +# assert(rv is not None and northPole is not None and utmEasting is not None +# and utmNorthing is not None and err is not None) +rv, lat, lon, err = mgrs.convertUtmToGeodetic(33, False, 19744.0, 18352.0) +assert(rv is not None and lat is not None and lon is not None and err is not None) +# rv, northPole, upsEasting, upsNorthing, err = mgrs.convertMgrsToUps("33CWM1974418352", 19744.0, 18352.0) +# assert(rv is not None and northPole is not None and upsEasting is not None +# and upsNorthing is not None and err is not None) +rv, lat, lon, err = mgrs.convertUpsToGeodetic(False, 19744.0, 18352.0) +assert(rv is not None and lat is not None and lon is not None and err is not None) + +############################# +# MultiFrameCoordinate.h +mfc = simCore.MultiFrameCoordinate() +assert(mfc is not None) +coord = simCore.Coordinate() +rv = mfc.setCoordinate(coord) +assert(rv is not None) +rv = mfc.setCoordinate(coord, coordConverter) +assert(rv is not None) +retCoord = mfc.llaCoordinate() +assert(retCoord is not None) +assert(retCoord.coordinateSystem() is not None) + +############################# +# NumericalAnalysis.h +assert(simCore.SEARCH_MAX_ITER is not None) +biSearch = simCore.BisectionSearch() +assert(biSearch is not None) +assert(biSearch.count() is not None) +indicator, x, xlo, xhi = biSearch.searchX(1, 2, 3, 4, simCore.SEARCH_INIT_X) +assert(indicator is not None and x is not None and xlo is not None and xhi is not None) +liSearch = simCore.LinearSearch() +assert(liSearch is not None) +indicator, x = liSearch.searchX(1, 2, 3, 4, 5, simCore.SEARCH_INIT_X) +assert(indicator is not None and x is not None) +times = simCore.DoubleArray(3) +values = simCore.DoubleArray(3) +for k in range(0, 3): + times[k] = k + values[k] = k + 3 +rv, value = simCore.newtonInterp(1.0, times, values) +assert(rv is not None) +assert(value == 4.0) +rv, value = simCore.invLinearInterp(4.5, times, values, 0.1) +assert(rv is not None) +assert(value == 1.5) + +############################# +# Calculations.h +# TODO: Following fail, fix output +sphereVec, sphereTpOrigin = simCore.tangentPlane2Sphere(v1, v2) +assert(sphereVec is not None and sphereTpOrigin is not None) +assert(simCore.geodeticToSpherical(0, 0, 0) is not None) +# success, fromPos, toPos = simCore.convertLocations(simCore.Coordinate(), simCore.Coordinate(), simCore.WGS_84, coordConverter) +# assert(success is not None and fromPos is not None and toPos is not None) +assert(simCore.calculateBodyUnitX(1, 1) is not None) +assert(simCore.calculateBodyUnitY(1, 1, 1) is not None) +assert(simCore.calculateBodyUnitZ(1, 1, 1) is not None) +assert(simCore.calculateVelFromGeodeticPos(v1, v2, 1) is not None) +success, velOut, oriOut = simCore.calculateVelOriFromPos(v1, v2, 1, simCore.WGS_84, v3) +assert(success is not None and velOut is not None and oriOut is not None) +success, velOut, oriOut = simCore.calculateVelOriFromPos(v1, v2, 1, simCore.WGS_84, v3, simCore.COORD_SYS_XEAST) +assert(success is not None and velOut is not None and oriOut is not None) +assert(simCore.calculateGeodeticOriFromRelOri(v1, v2) is not None) +assert(simCore.calculateGeodeticOffsetPos(v1, v2, v3) is not None) +assert(simCore.calculateGeodeticEndPoint(v1, 1, 1, 1) is not None) +# midPoint, wrapsDateline = simCore.calculateGeodeticMidPoint(v1, v2, False) +# assert(midPoint is not None and wrapsDateline is not None) +assert(simCore.calculateFlightPathAngles(v1) is not None) +assert(simCore.calculateVelocity(1, 1, 1) is not None) +aoa, ss, totalAoa = simCore.calculateAoaSideslipTotalAoa(v1, v2, True) +assert(aoa is not None and ss is not None and totalAoa is not None) +distance, closestLla = simCore.getClosestPoint(v1, v2, v3) +assert(distance is not None and closestLla is not None) +azim, elev, cmp = simCore.calculateRelAzEl(v1, v2, v3, simCore.WGS_84, coordConverter) +assert(azim is not None and elev is not None and cmp is not None) +azim, elev, cmp = simCore.calculateAbsAzEl(v1, v2, simCore.WGS_84, coordConverter) +assert(azim is not None and elev is not None and cmp is not None) +assert(simCore.calculateSlant(v1, v2, simCore.WGS_84, coordConverter) is not None) +nst, dr, xr = simCore.calculateGeodesicDRCR(v1, 1, v2) +assert(nst is not None and dr is not None and xr is not None) +v5 = simCore.Vec3(1, 1, 1) +assert(simCore.calculateRangeRate(v, v1, v2, v3, simCore.WGS_84, coordConverter, v4, v5) is not None) +azim, elev, cmp = simCore.calculateRelAng(v1, v2) +assert(azim is not None and elev is not None and cmp is not None) +azim, elev, cmp = simCore.calculateRelAngToTrueAzEl(1, 2, v1) +assert(azim is not None and elev is not None and cmp is not None) +assert(simCore.positionInGate(v1, v2, 1, 2, 3, 4, 5, 6, simCore.WGS_84, coordConverter) is not None) +assert(simCore.laserInGate(v1, v2, 1, 2, 3, 4, 5, 6, 7, 8, 9, simCore.WGS_84, coordConverter) is not None) +yaw, pitch = simCore.calculateYawPitchFromBodyUnitX(v1) +assert(yaw is not None and pitch is not None) +lat, lon, azbck = simCore.sodanoDirect(1, 2, 3, 100.0, 3.14159) +assert(lat is not None and lon is not None and azbck is not None) +distance, azfwd, azbck, = simCore.sodanoInverse(1, 2, 3, 2, 3) +assert(distance is not None and azfwd is not None and azbck is not None) +assert(simCore.calculateEarthRadius(1.0) is not None) +# assert(simCore.clampEcefPointToGeodeticSurface(v1) is not None) +assert(simCore.calculateHorizonDist(v1) is not None) +assert(simCore.calculateHorizonDist(v1, simCore.GEOMETRIC_HORIZON) is not None) +assert(simCore.calculateHorizonDist(v1, simCore.GEOMETRIC_HORIZON, 1.06) is not None) +assert(simCore.calculateHorizonDist(v1, simCore.GEOMETRIC_HORIZON, 1.06, 1.333) is not None) +assert(simCore.positionInGate(v1, v2, 1.0, 0.0, 1.0, 1.0, 1000.0, 3000.0, simCore.WGS_84, coordConverter) is not None) +assert(simCore.laserInGate(v1, v2, 1.0, 0.0, 1.0, 1.0, 1000.0, 3000.0, 1.5, 1.5, 2000.0, simCore.WGS_84, coordConverter) is not None) +assert(simCore.laserInGate(v1, v2, 1.0, 0.0, 1.0, 1.0, 1000.0, 3000.0, 1.5, 1.5, 2000.0, simCore.WGS_84, coordConverter, 100) is not None) + +############################# +# Random.h +nv = simCore.NormalVariable() +assert(nv is not None) +assert(nv() is not None) +assert(nv.setMean(1) is None and nv.setStdDev(2) is None) +assert(nv.mean() == 1 and nv.stdDev() == 2) +gVar = simCore.GaussianVariable() +assert(gVar is not None) +assert(gVar.stdDev() == 1) +pv = simCore.PoissonVariable() +assert(pv is not None) +assert(pv.seeds() is not None) +assert(pv.setSeeds(1) is None) + +############################# +# SquareMatrix.h +sm = simCore.SquareMatrix() +# TODO: Implement. +# assert(sm.row(0) is not None) +assert(sm is not None) +assert(sm.makeIdentity() is not None) +assert(sm.transpose() is not None) +assert(sm.set(0, 0, 0) is not None) +assert(sm.get(0, 0) == 0) +assert(sm.data() is not None) +assert(sm.add(simCore.SquareMatrix()) is not None) + +############################# +# Units.h +assert(simCore.Units.UNITLESS is not None) +assert(simCore.Units.MIL is not None) +assert(simCore.Units.DATA_MILES is not None) +assert(simCore.Units.DATA_MILES_PER_HOUR is not None) +assert(simCore.Units.NAUTICAL_MILES_PER_SECOND_SQUARED is not None) +assert(simCore.Units.RANKINE is not None) +assert(simCore.Units.REVOLUTIONS_PER_MINUTE is not None) +assert(simCore.Units.TEASPOON is not None) +assert(simCore.Units.PASCALS is not None) +assert(simCore.Units.VOLUME_FAMILY is not None) +assert(simCore.Units.FEET.name() is not None) +assert(simCore.Units.FEET.abbreviation() is not None) +assert(simCore.Units.FEET.family() is not None) +assert(simCore.Units.FEET.canConvert(simCore.Units.METERS)) +assert(simCore.Units.FEET.convertTo(simCore.Units.METERS, 1) is not None) +assert(simCore.Units.FEET == simCore.Units.FEET) +assert(simCore.Units.FEET != simCore.Units.METERS) +assert(simCore.Units.FEET.isValid()) +assert(simCore.Units.FEET.toBaseScalar() is not None) +assert(simCore.Units.offsetThenScaleUnit("Yard", "yd", 0, 1.0 / 3, simCore.Units.LENGTH_FAMILY).name() == "Yard") +ur = simCore.UnitsRegistry() +assert(ur is not None) +assert(ur.registerDefaultUnits() is None) +assert(ur.registerUnits(simCore.Units.FEET) is not None) +assert(ur.units(simCore.Units.LENGTH_FAMILY) is not None) +# TODO: Fix +# assert(ur.families() is not None) +assert(ur.unitsByName("feet") == simCore.Units.FEET) +assert(ur.unitsByAbbreviation("ft") == simCore.Units.FEET) + +############################# +# VerticalDatum.h +assert(simCore.VERTDATUM_WGS84 is not None) +assert(simCore.VERTDATUM_MSL is not None) +assert(simCore.VERTDATUM_USER is not None) + +# simCore/LUT + +############################# +# LUT1.h +dLut = simCore.doubleLUT1() +assert(dLut is not None) +assert(dLut.initialize(1, 2, 1, 1) is None) +iLut = simCore.intLUT1() +assert(iLut is not None) +assert(iLut.initialize(1, 2, 1, 1) is None) +assert(iLut.minX() is not None) +assert(iLut.numX() == 1) +assert(iLut(0) is not None) +assert(simCore.index(1, 1, 1) is not None) +assert(simCore.intIndex(iLut, 1) is not None) +assert(simCore.doubleIndex(dLut, 1) is not None) +assert(simCore.intLowValue(iLut, 1) is not None) +assert(simCore.doubleLowValue(dLut, 0) is not None) + +############################# +# LUT2.h +dLut2 = simCore.doubleLUT2() +assert(dLut2 is not None) +assert(dLut2.initialize(1, 2, 1, 1, 2, 1, 1) is None) +iLut2 = simCore.intLUT2() +assert(iLut2 is not None) +assert(iLut2.initialize(1, 2, 1, 1, 2, 1, 1) is None) +assert(iLut2.minX() is not None) +assert(iLut2.numX() is not None) +assert(iLut2(0, 0) is not None) + +############################# +# InterpTable.h +e = simCore.InterpTableException("InterpTableException test error message") +assert(e is not None) +assert(e.what() is not None) +e = simCore.intInterpTableLimitException("InterpTableLimitException test error message", 1, 2, 3) +assert(e is not None) +assert(e.what() is not None) +e = simCore.doubleInterpTableLimitException("InterpTableLimitException test error message", 1, 2, 3) +assert(e is not None) +iit = simCore.intInterpTable() +assert(iit is not None) +assert(iit.initialize(1, 2, 1, 1, 2, 1) is None) +assert(iit(0, 0) is not None) +dit = simCore.doubleInterpTable() +assert(dit is not None) +assert(dit.initialize(1, 2, 1, 1, 2, 1) is None) +assert(dit.lut() is not None) + +# simCore/String + +############################# +# Angle.h +assert(simCore.DEG_SYM_NONE is not None) +assert(simCore.getDegreeSymbol(simCore.DEG_SYM_NONE) is not None) +success, angle = simCore.getAngleFromDegreeString("20", True) +assert(success is not None and angle is not None) +assert(simCore.FMT_BAM is not None) +assert(simCore.getAngleString(1, simCore.FMT_BAM, True, 1, simCore.DEG_SYM_NONE, 0, 0) is not None) +assert(simCore.printLatitude(1, simCore.FMT_BAM, True, 1, simCore.DEG_SYM_NONE) is not None) +assert(simCore.printLongitude(1, simCore.FMT_BAM, True, 1, simCore.DEG_SYM_NONE) is not None) + +############################# +# Constants.h +assert(simCore.STR_WHITE_SPACE_CHARS is not None) +assert(simCore.STR_DEGREE_SYMBOL_ASCII is not None) +assert(simCore.STR_DEGREE_SYMBOL_UNICODE is not None) +assert(simCore.STR_DEGREE_SYMBOL_UTF8 is not None) + +############################# +# FilePatterns.h +assert(simCore.ALL_SIMDIS_FILE_PATTERNS is not None) +assert(simCore.SIMDIS_FILE_PATTERNS is not None) +assert(simCore.ALL_DATA_FILE_PATTERNS is not None) + +############################# +# Format.h +assert(simCore.caseCompare("a", "b") is not None) +assert(simCore.lowerCase("APPLE") == "apple") +assert(simCore.upperCase("apple") == "APPLE") +assert(simCore.stringCaseFind("Apple", "A") == 0) +assert(simCore.getExtension("hi.cpp") == ".cpp") +assert(simCore.hasExtension("hi.cpp", ".cpp")) +assert(simCore.buildString("hi", 1) is not None) + +############################# +# Tokenizer.h +word, endWordPos = simCore.extractWord("Lots of words", 5) +assert(word == "of" and endWordPos == 7) +word, endWordPos = simCore.extractWordWithQuotes('Lots "of" words', 5) +assert(word == '"of"' and endWordPos == 9) +assert(simCore.getTerminateForStringPos("", 1) is not None) +assert(simCore.getFirstCharPosAfterString("abc", 0, "b") == 2) +assert(simCore.removeQuotes("'Too many quotes'") == "Too many quotes") +err, tokenName, tokenValue = simCore.getNameAndValueFromToken("key=value") +assert(err is not None and tokenName == "key" and tokenValue == "value") + +############################# +# Utils.h +assert(simCore.StringUtils.before("Testing function", " function") == "Testing") +assert(simCore.StringUtils.before("Testing function", " ") == "Testing") +assert(simCore.StringUtils.after("Testing function", "Testing ") == "function") +assert(simCore.StringUtils.after("Testing function", " ") == "function") +assert(simCore.StringUtils.beforeLast("Testing function", "ti") == "Testing func") +assert(simCore.StringUtils.beforeLast("Testing function", "n") == "Testing functio") +assert(simCore.StringUtils.afterLast("Testing function", "ti") == "on") +assert(simCore.StringUtils.afterLast("Testing function", "t") == "ion") +assert(simCore.StringUtils.substitute("Testing function", "t", "m") is not None) +assert(simCore.StringUtils.addEscapeSlashes('"') == '\\\"') +assert(simCore.StringUtils.removeEscapeSlashes('\\"') == '"') +assert(simCore.StringUtils.trim(' test ') == 'test') +assert(simCore.toNativeSeparators("Users\person\Documents") is not None) +assert(simCore.sanitizeFilename("") == "Documents") +assert(simCore.hasEnv("var") is not None) + +# TODO: More testing here + +""" +Not implemented due to MSVC 2019 errors. +# simCore/EM + +############################# +# Constants.h +assert(simCore.LIGHT_SPEED_VACUUM is not None) +assert(simCore.RRE_CONSTANT is not None) +assert(simCore.DEFAULT_ANTENNA_GAIN is not None) +assert(simCore.POLARITY_RIGHTCIRC is not None) +assert(simCore.POLARITY_STRING_UNKNOWN is not None) +assert(simCore.POLARITY_STRING_LEFTCIRC is not None) +assert(simCore.polarityString(simCore.POLARITY_LEFTCIRC) is not None) +assert(simCore.polarityType(simCore.POLARITY_STRING_LEFTCIRC) is not None) +assert(simCore.RCS_XPATCH is not None) +assert(simCore.RCS_SYM_LUT_TYPE is not None) +assert(simCore.RCS_LOG_NORMAL_FUNC is not None) +assert(simCore.ANTENNA_LOBE_BACK is not None) +assert(simCore.ANTENNA_ALGORITHM_SINXX is not None) +assert(simCore.ANTENNA_FORMAT_EZNEC is not None) +assert(simCore.ANTENNA_PATTERN_MONOPULSE is not None) +assert(simCore.ANTENNA_STRING_ALGORITHM_SINXX is not None) +assert(simCore.ANTENNA_STRING_FORMAT_MONOPULSE is not None) +assert(simCore.ANTENNA_STRING_EXTENSION_MONOPULSE is not None) + +############################# +# AntennaPattern.h +assert(simCore.antennaPatternTypeString(simCore.ANTENNA_PATTERN_SINXX) is not None) +assert(simCore.antennaPatternType(simCore.ANTENNA_STRING_ALGORITHM_SINXX) is not None) +assert(simCore.loadPatternFile(simCore.ANTENNA_STRING_ALGORITHM_SINXX, 1) is not None) +assert(simCore.AntennaGainParameters() is not None) +apg = simCore.AntennaPatternGauss() +assert(apg is not None) +# TODO: Test +# assert(apg.type() is not None) +assert(apg.gain(simCore.AntennaGainParameters()) is not None) +small, large = apg.minMaxGain(simCore.AntennaGainParameters()) +assert(small is not None and large is not None) +# TODO: Implement +# result, lastLobe = simCore.calculateGain({}, {}, 1, 2, 3, 4, 5. True) +# assert(result is not None and lastLobe is not None) +apt = simCore.AntennaPatternTable() +assert(apt is not None) +assert(apt.readPat("") is not None) +assert(apt.setValid(False) is None) +assert(apt.setFilename("") is None) +assert(apt.setAzimData(100, 10) is None) +# TODO: Test after installing LUT/InterpTable.h +# success, symmetricAntennaPattern = simCore.readPattern("", "", 1) +# assert(success is not None and symmetricAntennaPattern is not None) +# success, symmetricGainAntPattern = simCore.readPattern("", "", 1) +# assert(success is not None and symmetricAntennaPattern is not None) +""" + +print("Success!") + diff --git a/swig/simCore/CMakeLists.txt b/swig/simCore/CMakeLists.txt new file mode 100644 index 000000000..56d629e33 --- /dev/null +++ b/swig/simCore/CMakeLists.txt @@ -0,0 +1,47 @@ +vsi_require_target(PYTHON3) + +set(SIMCORE_FILES + simCore.i + simCoreCalc.i + simCoreCommon.i + simCoreEM.i + simCore.i + simCoreLUT.i + simCoreString.i + simCoreTime.i +) + +set_source_files_properties(simCore.i PROPERTIES + CPLUSPLUS ON + DEPENDS "${SIMCORE_FILES}" + SWIG_MODULE_NAME "simCore" +) +# SWIG does not define WIN32, _WIN32, _MSC_VER, etc. Need to provide it manually +if(MSVC) + set_source_files_properties(simCore.i PROPERTIES SWIG_FLAGS "-DMSVC") +endif() +# Avoid incorrect import declaration with debug libraries +set_source_files_properties(simCore.i PROPERTIES COMPILE_OPTIONS "-interface;_simCore") + +# LEGACY / STANDARD +set(UseSWIG_TARGET_NAME_PREFERENCE STANDARD) + +swig_add_library(SwigSimCore LANGUAGE python SOURCES simCore.i) +target_link_libraries(SwigSimCore PRIVATE PYTHON3 simCore) +set_target_properties(SwigSimCore PROPERTIES + FOLDER "SIMDIS SDK" + PROJECT_LABEL "Python Wrapper - simCore" + OUTPUT_NAME "simCore" +) + +# On Linux do not want the _d suffix on the library +if(NOT WIN32) + set_target_properties(SwigSimCore PROPERTIES DEBUG_POSTFIX "") +endif() + +InstallPythonFiles(simCore) + +install(FILES ${SIMCORE_FILES} + DESTINATION doc/SIMDIS/LabelScripting + COMPONENT SIMDIS +) diff --git a/swig/simCore/simCore.i b/swig/simCore/simCore.i new file mode 100644 index 000000000..299a119c6 --- /dev/null +++ b/swig/simCore/simCore.i @@ -0,0 +1,42 @@ +%module(directors="1") simCore + +%feature("autodoc", "3"); + +%{ +#include "simCore.h" +%} + +#ifdef _WIN32 +%include "windows.i" +#endif + +// Include STL support +%include "std_map.i" +%include "std_string.i" +%include "std_vector.i" + +typedef unsigned char uint8_t; +typedef signed char int8_t; +typedef unsigned short uint16_t; +typedef short int16_t; +typedef unsigned int uint32_t; +typedef int int32_t; + +// Windows MSVC uses long long for 64 bit ints. Linux g++ uses long +#ifdef MSVC +typedef unsigned long long uint64_t; +typedef long long int64_t; +#else +typedef unsigned long uint64_t; +typedef long int64_t; +#endif + +#define SDKCORE_EXPORT + + +%include "simCoreCommon.i" +%include "simCoreCalc.i" +%include "simCoreEM.i" +%include "simCoreLUT.i" +%include "simCoreString.i" +%include "simCoreTime.i" diff --git a/swig/simCore/simCoreCalc.i b/swig/simCore/simCoreCalc.i new file mode 100644 index 000000000..ed6b3f4f9 --- /dev/null +++ b/swig/simCore/simCoreCalc.i @@ -0,0 +1,215 @@ +%ignore simCore::Vec3::operator=; +%ignore simCore::Vec3::operator[]; +%ignore simCore::Coordinate::operator=; +%ignore simCore::CoordinateConverter::operator=; +%ignore simCore::SquareMatrix::operator=; + +//////////////////////////////////////////////// +// simCore/Calc +%include "simCore/Calc/Vec3.h" +%include "simCore/Calc/Angle.h" + +// simCore::coordinateSystemFromString() +%apply int& OUTPUT { simCore::CoordinateSystem& outSystem }; + +%include "simCore/Calc/CoordinateSystem.h" +%include "simCore/Calc/Coordinate.h" + +// Handling output parameter for toScientific() from simCore/calc/Math.cpp +%apply int* OUTPUT { int* exp }; + +// v3Scale() needs a custom wrapper for proper return value +%rename("wrap_v3Scale") simCore::v3Scale; +%include "simCore/Calc/Math.h" + +%rename("wrap_convert") simCore::CoordinateConverter::convert; +%include "simCore/Calc/CoordinateConverter.h" +%pythoncode %{ +def CoordConvert_convert(self, inCoord, outSystem): + outCoord = Coordinate() + rv = self.wrap_convert(inCoord, outCoord, outSystem) + return rv, outCoord +CoordinateConverter.convert = CoordConvert_convert +%} + +// isValidGars() +%apply std::string* OUTPUT { std::string* err }; +%apply int* OUTPUT { int* lonBand }; +%apply int* OUTPUT { int* latPrimaryIdx }; +%apply int* OUTPUT { int* latSecondaryIdx }; +%apply int* OUTPUT { int* quad15 }; +%apply int* OUTPUT { int* key5 }; +// convertGarsToGeodetic() +%apply double& OUTPUT { double& latRad }; +%apply double& OUTPUT { double& lonRad }; +// convertGeodeticToGars() +%apply std::string* OUTPUT { std::string* garsOut }; +%extend simCore::Gars { + // Fixes ordering of parameters for SWIG + static int convertGeodeticToGars(double latRad, double lonRad, simCore::Gars::Level level, std::string* garsOut, std::string* err) { + return simCore::Gars::convertGeodeticToGars(latRad, lonRad, *garsOut, level, err); + } + static int convertGeodeticToGars(double latRad, double lonRad, std::string* garsOut, std::string* err) { + return simCore::Gars::convertGeodeticToGars(latRad, lonRad, *garsOut, simCore::Gars::GARS_5, err); + } +}; +%ignore simCore::Gars::convertGeodeticToGars; // Ignore the C++ header file version +%warnfilter(509) simCore::Gars; // Ignore the overload warnings since they're treated as output parameters +%include "simCore/Calc/Gars.h" + +%include "simCore/Calc/Geometry.h" +%include "simCore/Calc/Interpolation.h" + +// simCore::WorldMagneticModel::calculateMagneticVariance() +%apply double& OUTPUT { double& varianceRad }; +%include "simCore/Calc/MagneticVariance.h" + +%warnfilter(509) simCore::Mgrs; // Ignore the overload warnings since they're treated as output parameters +// convertMgrsToGeodetic() +%apply double& OUTPUT { double& lat }; +%apply double& OUTPUT { double& lon }; +// breakMgrsString() +%apply int& OUTPUT { int& zone }; +%apply std::string& OUTPUT { std::string& gzdLetters }; +%apply double& OUTPUT { double& easting }; +%apply double& OUTPUT { double& northing }; +// TODO: These two methods do not wrap cleanly due to boolean reference +%ignore simCore::Mgrs::convertMgrsToUtm; +%ignore simCore::Mgrs::convertMgrsToUps; +%include "simCore/Calc/Mgrs.h" + +%include "simCore/Calc/MultiFrameCoordinate.h" + +// simCore::BisectionSearch::searchX() +%apply double& INOUT { double& x}; +%apply double& INOUT { double& xlo, double& xhi }; +// Required for double[3] in newtonInterp() +%include "carrays.i" +%array_class(double, DoubleArray); +// newtonInterp() +%apply double& OUTPUT { double& funcAtT0 }; +// invLinearInterp() +%apply double& OUTPUT { double& t0 }; +%include "simCore/Calc/NumericalAnalysis.h" + +// simCore::calculateRelAzEl() +%apply double* OUTPUT { double* azim, double* elev, double* cmp }; +// simCore::calculateGeodesicDRCR() +%apply double* OUTPUT { double* downRng, double* crossRng }; +// simCore::calculateRelAng() and simCore::calculateRelAngToTrueAzEl() +%apply double* OUTPUT { double* azim, double* elev, double* cmp }; + +%rename("wrap_tangentPlane2Sphere") simCore::tangentPlane2Sphere; +%rename("wrap_geodeticToSpherical") simCore::geodeticToSpherical; +%rename("wrap_calculateBodyUnitX") simCore::calculateBodyUnitX; +%rename("wrap_calculateBodyUnitY") simCore::calculateBodyUnitY; +%rename("wrap_calculateBodyUnitZ") simCore::calculateBodyUnitZ; +%rename("wrap_calculateVelFromGeodeticPos") simCore::calculateVelFromGeodeticPos; +%rename("wrap_calculateVelOriFromPos") simCore::calculateVelOriFromPos; +%rename("wrap_calculateGeodeticOriFromRelOri") simCore::calculateGeodeticOriFromRelOri; +%rename("wrap_calculateGeodeticOffsetPos") simCore::calculateGeodeticOffsetPos; +%rename("wrap_calculateGeodeticEndPoint") simCore::calculateGeodeticEndPoint; +%rename("wrap_calculateFlightPathAngles") simCore::calculateFlightPathAngles; +%rename("wrap_calculateVelocity") simCore::calculateVelocity; +%rename("wrap_getClosestPoint") simCore::getClosestPoint; + +// simCore::calculateAoaSideslipTotalAoa() +%apply double* OUTPUT { double* aoa, double* ss, double* totalAoA }; +// simCore::calculateYawPitchFromBodyUnitX() +%apply double& OUTPUT { double& yawOut, double& pitchOut }; +// simCore::sodanoDirect() +%warnfilter(509) simCore::sodanoDirect; +%apply double* OUTPUT { double *latOut, double *lonOut, double *azbck }; +// simCore::sodanoInverse() +%warnfilter(509) simCore::sodanoInverse; +%apply double* OUTPUT { double *azfwd, double *azbck }; + +%include "simCore/Calc/Calculations.h" + +%include "simCore/Calc/Random.h" +%include "simCore/Calc/SquareMatrix.h" + +%include "simCore/Calc/Units.h" +%include "simCore/Calc/VerticalDatum.h" + +// TODO: Implement after installing simCore/Time +/* +%include "simCore/Calc/DatumConvert.h" +%include "simCore/Calc/UnitContext.h" +*/ + +%template(intSdkMax) simCore::sdkMax; +%template(intSdkMin) simCore::sdkMin; +%template(intSquare) simCore::square; +%template(intSign) simCore::sign; +%template(doubleSdkMax) simCore::sdkMax; +%template(doubleSdkMin) simCore::sdkMin; +%template(doubleSquare) simCore::square; +%template(doubleSign) simCore::sign; + +%template(Vec3LinearInterpolate) simCore::linearInterpolate; +%template(DoubleLinearInterpolate) simCore::linearInterpolate; +%template(intBilinearInterpolate) simCore::bilinearInterpolate; +%template(doubleBilinearInterpolate) simCore::bilinearInterpolate; + +// Various Python overrides +%pythoncode %{ +def v3Scale(scalar, inVec): + outVec = Vec3() + wrap_v3Scale(scalar, inVec, outVec) + return outVec +def tangentPlane2Sphere(llaVec, tpVec): + sphereVec = Vec3() + sphereTpOrigin = Vec3() + wrap_tangentPlane2Sphere(llaVec, tpVec, sphereVec, sphereTpOrigin) + return sphereVec, sphereTpOrigin +def geodeticToSpherical(lat, lon, alt): + point = Vec3() + wrap_geodeticToSpherical(lat, lon, alt, point) + return point +def calculateBodyUnitX(yaw, pitch): + vecX = Vec3() + wrap_calculateBodyUnitX(yaw, pitch, vecX) + return vecX +def calculateBodyUnitY(yaw, pitch, roll): + vecY = Vec3() + wrap_calculateBodyUnitY(yaw, pitch, roll, vecY) + return vecY +def calculateBodyUnitZ(yaw, pitch, roll): + vecZ = Vec3() + wrap_calculateBodyUnitZ(yaw, pitch, roll, vecZ) + return vecZ +def calculateVelFromGeodeticPos(currPos, prevPos, deltaTime): + velVec = Vec3() + wrap_calculateVelFromGeodeticPos(currPos, prevPos, deltaTime, velVec) + return velVec +def calculateVelOriFromPos(currPos, prevPos, deltaTime, sysIn, refLLA, sysOut=COORD_SYS_XEAST): + velOut = Vec3() + oriOut = Vec3() + success = wrap_calculateVelOriFromPos(currPos, prevPos, deltaTime, sysIn, velOut, oriOut, refLLA, sysOut) + return success, velOut, oriOut +def calculateGeodeticOriFromRelOri(hostYpr, relYpr): + ypr = Vec3() + wrap_calculateGeodeticOriFromRelOri(hostYpr, relYpr, ypr) + return ypr +def calculateGeodeticOffsetPos(llaBgnPos, bodyOriOffset, bodyPosOffset): + offsetLla = Vec3() + wrap_calculateGeodeticOffsetPos(llaBgnPos, bodyOriOffset, bodyPosOffset, offsetLla) + return offsetLla +def calculateGeodeticEndPoint(llaBgnPos, az, el, rng): + llaEndPos = Vec3() + wrap_calculateGeodeticEndPoint(llaBgnPos, az, el, rng, llaEndPos) + return llaEndPos +def calculateFlightPathAngles(velVec): + fpa = Vec3() + wrap_calculateFlightPathAngles(velVec, fpa) + return fpa +def calculateVelocity(speed, heading, pitch): + velVec = Vec3() + wrap_calculateVelocity(speed, heading, pitch, velVec) + return velVec +def getClosestPoint(startLla, endLla, toLla): + closestLla = Vec3() + dist = wrap_getClosestPoint(startLla, endLla, toLla, closestLla) + return dist, closestLla +%} diff --git a/swig/simCore/simCoreCommon.i b/swig/simCore/simCoreCommon.i new file mode 100644 index 000000000..9c502dcc9 --- /dev/null +++ b/swig/simCore/simCoreCommon.i @@ -0,0 +1,5 @@ +//////////////////////////////////////////////// +// simCore/Common +%include "simCore/Common/Exception.h" +%include "simCore/Common/Time.h" + diff --git a/swig/simCore/simCoreEM.i b/swig/simCore/simCoreEM.i new file mode 100644 index 000000000..a08003d56 --- /dev/null +++ b/swig/simCore/simCoreEM.i @@ -0,0 +1,16 @@ +//////////////////////////////////////////////// +// simCore/EM +// TODO: Implement +/* +%include "simCore/EM/Constants.h" + +// AntennaPattern::minMaxGain() +%apply float* OUTPUT { float* min, float* max }; +%include "simCore/EM/AntennaPattern.h" + + +%include "simCore/EM/Decibel.h" +%include "simCore/EM/ElectroMagRange.h" +%include "simCore/EM/Propagation.h" +%include "simCore/EM/RadarCrossSection.h" +*/ diff --git a/swig/simCore/simCoreLUT.i b/swig/simCore/simCoreLUT.i new file mode 100644 index 000000000..5ab73b6f7 --- /dev/null +++ b/swig/simCore/simCoreLUT.i @@ -0,0 +1,29 @@ +//////////////////////////////////////////////// +// simCore/LUT +%include "simCore/LUT/LUT1.h" +%include "simCore/LUT/LUT2.h" +%include "simCore/LUT/InterpTable.h" + +%template(intLUT1) simCore::LUT::LUT1; +%template(doubleLUT1) simCore::LUT::LUT1; +%template(intIndex) simCore::LUT::index; +%template(doubleIndex) simCore::LUT::index; +%template(intLowValue) simCore::LUT::lowValue; +%template(doubleLowValue) simCore::LUT::lowValue; +// TODO: interpolate() + +%template(intLUT2) simCore::LUT::LUT2; +%template(doubleLUT2) simCore::LUT::LUT2; +// TODO: index() +// TODO: nearValue() +// TODO: interpolate() + +// TODO: BilinearInterpolate class +%template(intInterpTableLimitException) simCore::InterpTableLimitException; +%template(doubleInterpTableLimitException) simCore::InterpTableLimitException; +%template(intInterpTable) simCore::InterpTable; +%template(doubleInterpTable) simCore::InterpTable; +// TODO: NearestLookup() +// TODO: BilinearLookup() +// TODO: BilinearLookupNoException() + diff --git a/swig/simCore/simCoreString.i b/swig/simCore/simCoreString.i new file mode 100644 index 000000000..4bde173b8 --- /dev/null +++ b/swig/simCore/simCoreString.i @@ -0,0 +1,36 @@ +//////////////////////////////////////////////// +// simCore/String +%apply double& OUTPUT { double& ang }; +%include "simCore/String/Angle.h" + +%include "simCore/String/Constants.h" +%include "simCore/String/FilePatterns.h" +%include "simCore/String/Format.h" + +%apply size_t& OUTPUT { size_t& endWordPos }; +%apply std::string& OUTPUT { std::string& tokenName, std::string& tokenValue }; +%include "simCore/String/Tokenizer.h" + +%include "simCore/String/Utils.h" + +// Format.h +// TODO: join() +// TODO: getStrippedLine() + +// Tokenizer.h +// TODO: stringTokenizer() +// TODO: getTokens() +// TODO: removeQuotes() +// TODO: tokenizeWithQuotes() +// TODO: quoteTokenizer() +// TODO: removeCommentTokens() +// TODO: quoteCommentTokenizer() +// TODO: escapeTokenize() + +// TODO: Add these and test them as you add them +/* +%include "simCore/String/TextFormatter.h" +%include "simCore/String/TextReplacer.h" +%include "simCore/String/UnitContextFormatter.h" +%include "simCore/String/ValidNumber.h" +*/ diff --git a/swig/simCore/simCoreTime.i b/swig/simCore/simCoreTime.i new file mode 100644 index 000000000..a135612cb --- /dev/null +++ b/swig/simCore/simCoreTime.i @@ -0,0 +1,21 @@ +//////////////////////////////////////////////// +// simCore/Time + +// TODO: Add these and test them as you add them +/* +%include "simCore/String/TextFormatter.h" +%include "simCore/String/TextReplacer.h" +%include "simCore/String/UnitContextFormatter.h" +%include "simCore/String/ValidNumber.h" +%include "simCore/Time/Clock.h" +%include "simCore/Time/ClockImpl.h" +%include "simCore/Time/Constants.h" +%include "simCore/Time/CountDown.h" +%include "simCore/Time/DeprecatedStrings.h" +%include "simCore/Time/Exception.h" +%include "simCore/Time/Julian.h" +%include "simCore/Time/String.h" +%include "simCore/Time/TimeClass.h" +%include "simCore/Time/TimeClock.h" +%include "simCore/Time/Utils.h" +*/