diff --git a/.gitignore b/.gitignore index 1ba3b2e3..c75db36d 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ install_manifest.txt build* Build CMakeLists.txt.user +3rdparty/expat_2.2.6/ diff --git a/3rdparty/expat/CMakeLists.txt b/3rdparty/expat/CMakeLists.txt index 550a52c9..badfd61c 100644 --- a/3rdparty/expat/CMakeLists.txt +++ b/3rdparty/expat/CMakeLists.txt @@ -1,7 +1,7 @@ configure_file ( "${PROJECT_SOURCE_DIR}/3rdparty/expat/expat_config_cmake.in" - "${PROJECT_BINARY_DIR}/3rdparty/expat/expat_config.h" + "${PROJECT_BINARY_DIR}/3rdparty/expat/simgear_expat_config.h" ) set(expat_sources diff --git a/3rdparty/expat/xmlparse.c b/3rdparty/expat/xmlparse.c index e4fb313f..83ab782d 100644 --- a/3rdparty/expat/xmlparse.c +++ b/3rdparty/expat/xmlparse.c @@ -18,6 +18,8 @@ #include "amigaconfig.h" #elif defined(__WATCOMC__) #include "watcomconfig.h" +#elif defined(HAVE_SIMGEAR_EXPAT_CONFIG_H) +#include "simgear_expat_config.h" #elif defined(HAVE_EXPAT_CONFIG_H) #include "expat_config.h" #endif /* ndef COMPILED_FROM_DSP */ diff --git a/3rdparty/expat/xmlrole.c b/3rdparty/expat/xmlrole.c index 0bb159f5..84ce13dd 100644 --- a/3rdparty/expat/xmlrole.c +++ b/3rdparty/expat/xmlrole.c @@ -12,6 +12,8 @@ #include "amigaconfig.h" #elif defined(__WATCOMC__) #include "watcomconfig.h" +#elif defined(HAVE_SIMGEAR_EXPAT_CONFIG_H) +#include "simgear_expat_config.h" #else #ifdef HAVE_EXPAT_CONFIG_H #include "expat_config.h" diff --git a/3rdparty/expat/xmltok.c b/3rdparty/expat/xmltok.c index 5a8bd132..674d8cc5 100644 --- a/3rdparty/expat/xmltok.c +++ b/3rdparty/expat/xmltok.c @@ -12,6 +12,8 @@ #include "amigaconfig.h" #elif defined(__WATCOMC__) #include "watcomconfig.h" +#elif defined(HAVE_SIMGEAR_EXPAT_CONFIG_H) +#include "simgear_expat_config.h" #else #ifdef HAVE_EXPAT_CONFIG_H #include "expat_config.h" diff --git a/CMakeLists.txt b/CMakeLists.txt index 17ea0b6b..eef5e2cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,9 @@ if(COMMAND cmake_policy) if(POLICY CMP0067) cmake_policy(SET CMP0067 NEW) endif() + if(POLICY CMP0093) + cmake_policy(SET CMP0093 NEW) + endif() endif() @@ -173,15 +176,17 @@ if (MSVC AND MSVC_3RDPARTY_ROOT) set( OSG_MSVC "msvc" ) if (${MSVC_VERSION_MAJOR} EQUAL "19") - if (${MSVC_VERSION_MINOR} EQUAL "00") - set( OSG_MSVC ${OSG_MSVC}140 ) - else () + if (${MSVC_VERSION_MINOR} GREATER_EQUAL "20") + set( OSG_MSVC ${OSG_MSVC}142 ) + elseif (${MSVC_VERSION_MINOR} GREATER_EQUAL "10") set( OSG_MSVC ${OSG_MSVC}141 ) + else () + set( OSG_MSVC ${OSG_MSVC}140 ) endif () elseif (${MSVC_VERSION_MAJOR} EQUAL "18") set( OSG_MSVC ${OSG_MSVC}120 ) else () - message(FATAL_ERROR "Visual Studio 2013/15/17 is required") + message(FATAL_ERROR "Visual Studio 2013 or higher is required") endif () if (CMAKE_CL_64) @@ -271,9 +276,6 @@ if (SYSTEM_EXPAT) else() message(STATUS "Using built-in expat code") - # XML_STATIC is important to avoid sg_expat_external.h - # declaring symbols as declspec(import) - add_definitions(-DHAVE_EXPAT_CONFIG_H -DXML_STATIC) set(EXPAT_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/3rdparty/expat ${PROJECT_BINARY_DIR}/3rdparty/expat) @@ -521,6 +523,10 @@ include(CheckCXXFeatures) # ahead of system-installed libs include_directories(BEFORE ${PROJECT_BINARY_DIR}/simgear) +if(${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") + include_directories("/usr/X11R6/include") +endif() + add_definitions(-DHAVE_CONFIG_H) # configure a header file to pass some of the CMake settings diff --git a/CMakeModules/BoostTestTargets.cmake b/CMakeModules/BoostTestTargets.cmake index 580bf9d2..f1bd0f99 100644 --- a/CMakeModules/BoostTestTargets.cmake +++ b/CMakeModules/BoostTestTargets.cmake @@ -46,11 +46,17 @@ set(BOOST_TEST_TARGET_PREFIX "test") if(NOT Boost_FOUND) find_package(Boost 1.34.0 QUIET) endif() -if("${Boost_VERSION}0" LESS "1034000") + +if (NOT Boost_VERSION_MACRO) + # Compatibility with pre CMP0093 (CMake 3.15) + set(Boost_VERSION_MACRO ${Boost_VERSION}) +endif() + +if("${Boost_VERSION_MACRO}0" LESS "1034000") set(_shared_msg "NOTE: boost::test-based targets and tests cannot " "be added: boost >= 1.34.0 required but not found. " - "(found: '${Boost_VERSION}'; want >=103400) ") + "(found: '${Boost_VERSION_MACRO}'; want >=103400) ") if(ENABLE_TESTS) message(FATAL_ERROR ${_shared_msg} @@ -66,7 +72,7 @@ endif() include(GetForceIncludeDefinitions) include(CopyResourcesToBuildTree) -if(Boost_FOUND AND NOT "${Boost_VERSION}0" LESS "1034000") +if(Boost_FOUND AND NOT "${Boost_VERSION_MACRO}0" LESS "1034000") set(_boosttesttargets_libs) set(_boostConfig "BoostTestTargetsIncluded.h") if(NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY) @@ -129,7 +135,7 @@ function(add_boost_test _name) "Syntax error in use of add_boost_test: at least one source file required!") endif() - if(Boost_FOUND AND NOT "${Boost_VERSION}0" LESS "1034000") + if(Boost_FOUND AND NOT "${Boost_VERSION_MACRO}0" LESS "1034000") include_directories(${Boost_INCLUDE_DIRS}) @@ -221,7 +227,7 @@ function(add_boost_test _name) set(_test_command ${_target_name}) endif() - if(TESTS AND ( "${Boost_VERSION}" VERSION_GREATER "103799" )) + if(TESTS AND ( "${Boost_VERSION_MACRO}" VERSION_GREATER "103799" )) foreach(_test ${TESTS}) add_test( ${_name}-${_test} diff --git a/Doxyfile b/Doxyfile index ef222c3e..19616684 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.6 +# Doxyfile 1.8.13 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -38,29 +38,29 @@ PROJECT_NAME = SimGear # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 3.3.0 +PROJECT_NUMBER = 2019.2.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = +PROJECT_BRIEF = -# With the PROJECT_LOGO tag one can specify an logo or icon that is included in -# the documentation. The maximum height of the logo should not exceed 55 pixels -# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo -# to the output directory. +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. -PROJECT_LOGO = +PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = ../doxy +OUTPUT_DIRECTORY = ../docs/SimGear -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where @@ -70,6 +70,14 @@ OUTPUT_DIRECTORY = ../doxy CREATE_SUBDIRS = NO +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. @@ -85,14 +93,14 @@ CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = English -# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the @@ -110,7 +118,7 @@ REPEAT_BRIEF = YES # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. -ABBREVIATE_BRIEF = +ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief @@ -127,7 +135,7 @@ ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO -# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. @@ -144,7 +152,7 @@ FULL_PATH_NAMES = NO # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. -STRIP_FROM_PATH = +STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which @@ -153,7 +161,7 @@ STRIP_FROM_PATH = # specify the list of include paths that are normally passed to the compiler # using the -I flag. -STRIP_FROM_INC_PATH = +STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't @@ -197,9 +205,9 @@ MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a -# new page for each member. If set to NO, the documentation of a member will be -# part of the file/class/namespace that contains it. +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO @@ -220,13 +228,13 @@ TAB_SIZE = 8 # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. -ALIASES = +ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. -TCL_SUBST = +TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For @@ -261,16 +269,19 @@ OPTIMIZE_OUTPUT_VHDL = NO # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make -# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C -# (default is Fortran), use: inc=Fortran f=C. +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. # -# Note For files without extension you can use no_extension as a placeholder. +# Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. -EXTENSION_MAPPING = +EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable @@ -282,10 +293,19 @@ EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by by putting a % sign in front of the word -# or globally by setting AUTOLINK_SUPPORT to NO. +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES @@ -325,13 +345,20 @@ SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first +# tag is set to YES then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent @@ -390,7 +417,7 @@ LOOKUP_CACHE_SIZE = 0 # Build related configuration options #--------------------------------------------------------------------------- -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. @@ -398,37 +425,37 @@ LOOKUP_CACHE_SIZE = 0 # normally produced when WARNINGS is set to YES. # The default value is: NO. -EXTRACT_ALL = NO +EXTRACT_ALL = YES -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO -# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO -# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = YES -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES -# This flag is only useful for Objective-C code. When set to YES local methods, +# This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO only methods in the interface are +# included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. @@ -453,21 +480,21 @@ HIDE_UNDOC_MEMBERS = YES # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set -# to NO these classes will be included in the various overviews. This option has -# no effect if EXTRACT_ALL is enabled. +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = YES # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO these declarations will be +# (class|struct|union) declarations. If set to NO, these declarations will be # included in the documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO these +# documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. @@ -481,7 +508,7 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES upper-case letters are also +# names in lower-case letters. If set to YES, upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. @@ -490,12 +517,19 @@ INTERNAL_DOCS = NO CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES the +# their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. @@ -523,14 +557,14 @@ INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member -# name. If set to NO the members will appear in declaration order. +# name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member -# name. If set to NO the members will appear in declaration order. Note that +# name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. @@ -575,27 +609,25 @@ SORT_BY_SCOPE_NAME = NO STRICT_PROTO_MATCHING = NO -# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the -# todo list. This list is created by putting \todo commands in the -# documentation. +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES -# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the -# test list. This list is created by putting \test commands in the -# documentation. +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES -# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES -# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. @@ -606,7 +638,7 @@ GENERATE_DEPRECATEDLIST= YES # sections, marked by \if ... \endif and \cond # ... \endcond blocks. -ENABLED_SECTIONS = +ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the @@ -620,8 +652,8 @@ ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES the list -# will mention the files that were used to generate the documentation. +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES @@ -648,7 +680,7 @@ SHOW_NAMESPACES = YES # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. -FILE_VERSION_FILTER = +FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated @@ -661,7 +693,7 @@ FILE_VERSION_FILTER = # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. -LAYOUT_FILE = +LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib @@ -669,10 +701,9 @@ LAYOUT_FILE = # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. Do not use file names with spaces, bibtex cannot handle them. See -# also \cite for info how to create references. +# search path. See also \cite for info how to create references. -CITE_BIB_FILES = +CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages @@ -686,7 +717,7 @@ CITE_BIB_FILES = QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. @@ -694,7 +725,7 @@ QUIET = NO WARNINGS = YES -# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. @@ -711,12 +742,18 @@ WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return -# value. If set to NO doxygen will only warn about wrong or incomplete parameter -# documentation, but not about the absence of documentation. +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. # The default value is: NO. WARN_NO_PARAMDOC = NO +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated @@ -731,7 +768,7 @@ WARN_FORMAT = "$file:$line: $text" # messages should be written. If left blank the output is written to standard # error (stderr). -WARN_LOGFILE = +WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files @@ -740,7 +777,7 @@ WARN_LOGFILE = # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with -# spaces. +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = DoxygenMain.cxx \ @@ -757,12 +794,17 @@ INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank the -# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, -# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, -# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, -# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, -# *.qsf, *.as and *.js. +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. FILE_PATTERNS = *.h \ *.hxx \ @@ -799,7 +841,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -810,20 +852,20 @@ EXCLUDE_PATTERNS = # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = +EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. -EXAMPLE_PATTERNS = +EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands @@ -836,7 +878,7 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = +IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program @@ -852,8 +894,12 @@ IMAGE_PATH = # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. -INPUT_FILTER = +INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the @@ -861,11 +907,15 @@ INPUT_FILTER = # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. -FILTER_PATTERNS = +FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER ) will also be used to filter the input files that are used for +# INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. @@ -877,14 +927,14 @@ FILTER_SOURCE_FILES = NO # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. -FILTER_SOURCE_PATTERNS = +FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. -USE_MDFILE_AS_MAINPAGE = +USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing @@ -925,7 +975,7 @@ REFERENCED_BY_RELATION = NO REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. @@ -972,6 +1022,25 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse-libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -996,13 +1065,13 @@ COLS_IN_ALPHA_INDEX = 5 # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. -IGNORE_PREFIX = +IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES @@ -1040,7 +1109,7 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = +HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1050,7 +1119,7 @@ HTML_HEADER = # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_FOOTER = +HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of @@ -1062,18 +1131,20 @@ HTML_FOOTER = # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_STYLESHEET = +HTML_STYLESHEET = -# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- -# defined cascading style sheet that is included after the standard style sheets +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefor more robust against future updates. -# Doxygen will copy the style sheet file to the output directory. For an example -# see the documentation. +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1083,10 +1154,10 @@ HTML_EXTRA_STYLESHEET = # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_FILES = +HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the stylesheet and background images according to +# will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # http://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 @@ -1117,8 +1188,9 @@ HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: YES. +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = YES @@ -1211,31 +1283,32 @@ GENERATE_HTMLHELP = NO # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_FILE = +CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# including file name) of the HTML help compiler (hhc.exe). If non-empty, # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -HHC_LOCATION = +HHC_LOCATION = -# The GENERATE_CHI flag controls if a separate .chi index file is generated ( -# YES) or that it should be included in the master .chm file ( NO). +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_INDEX_ENCODING = +CHM_INDEX_ENCODING = -# The BINARY_TOC flag controls whether a binary table of contents is generated ( -# YES) or a normal table of contents ( NO) in the .chm file. +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1262,7 +1335,7 @@ GENERATE_QHP = NO # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. -QCH_FILE = +QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace @@ -1287,7 +1360,7 @@ QHP_VIRTUAL_FOLDER = doc # filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom @@ -1295,21 +1368,21 @@ QHP_CUST_FILTER_NAME = # filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_ATTRS = +QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_SECT_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. -QHG_LOCATION = +QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To @@ -1348,7 +1421,7 @@ DISABLE_INDEX = NO # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has @@ -1376,7 +1449,7 @@ ENUM_VALUES_PER_LINE = 4 TREEVIEW_WIDTH = 250 -# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1405,7 +1478,7 @@ FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering -# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. @@ -1442,7 +1515,7 @@ MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_EXTENSIONS = +MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site @@ -1450,7 +1523,7 @@ MATHJAX_EXTENSIONS = # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_CODEFILE = +MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and @@ -1475,11 +1548,11 @@ SEARCHENGINE = NO # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a web server instead of a web client using Javascript. There -# are two flavours of web server based searching depending on the -# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for -# searching and an index file used by the script. When EXTERNAL_SEARCH is -# enabled the indexing and searching needs to be provided by external tools. See -# the section "External Indexing and Searching" for details. +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. # The default value is: NO. # This tag requires that the tag SEARCHENGINE is set to YES. @@ -1491,7 +1564,7 @@ SERVER_BASED_SEARCH = NO # external search engine pointed to by the SEARCHENGINE_URL option to obtain the # search results. # -# Doxygen ships with an example indexer ( doxyindexer) and search engine +# Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library # Xapian (see: http://xapian.org/). # @@ -1504,13 +1577,13 @@ EXTERNAL_SEARCH = NO # The SEARCHENGINE_URL should point to a search engine hosted by a web server # which will return the search results when EXTERNAL_SEARCH is enabled. # -# Doxygen ships with an example indexer ( doxyindexer) and search engine +# Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library # Xapian (see: http://xapian.org/). See the section "External Indexing and # Searching" for details. # This tag requires that the tag SEARCHENGINE is set to YES. -SEARCHENGINE_URL = +SEARCHENGINE_URL = # When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed # search data is written to a file for indexing by an external tool. With the @@ -1526,7 +1599,7 @@ SEARCHDATA_FILE = searchdata.xml # projects and redirect the results back to the right project. # This tag requires that the tag SEARCHENGINE is set to YES. -EXTERNAL_SEARCH_ID = +EXTERNAL_SEARCH_ID = # The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen # projects other than the one defined by this configuration file, but that are @@ -1536,13 +1609,13 @@ EXTERNAL_SEARCH_ID = # EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... # This tag requires that the tag SEARCHENGINE is set to YES. -EXTRA_SEARCH_MAPPINGS = +EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- # Configuration options related to the LaTeX output #--------------------------------------------------------------------------- -# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output. +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. # The default value is: YES. GENERATE_LATEX = NO @@ -1573,7 +1646,7 @@ LATEX_CMD_NAME = latex MAKEINDEX_CMD_NAME = makeindex -# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX # documents. This may be useful for small projects and may help to save some # trees in general. # The default value is: NO. @@ -1591,13 +1664,16 @@ COMPACT_LATEX = NO PAPER_TYPE = letter # The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names -# that should be included in the LaTeX output. To get the times font for -# instance you can specify -# EXTRA_PACKAGES=times +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} # If left blank no extra packages will be included. # This tag requires that the tag GENERATE_LATEX is set to YES. -EXTRA_PACKAGES = +EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for the # generated LaTeX document. The header should contain everything until the first @@ -1607,22 +1683,35 @@ EXTRA_PACKAGES = # # Note: Only use a user-defined header if you know what you are doing! The # following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will -# replace them by respectively the title of the page, the current date and time, -# only the current date, the version number of doxygen, the project name (see -# PROJECT_NAME), or the project number (see PROJECT_NUMBER). +# $datetime, $date, $doxygenversion, $projectname, $projectnumber, +# $projectbrief, $projectlogo. Doxygen will replace $title with the empty +# string, for the replacement values of the other commands the user is referred +# to HTML_HEADER. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_HEADER = +LATEX_HEADER = # The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the # generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. +# chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. # # Note: Only use a user-defined footer if you know what you are doing! # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_FOOTER = +LATEX_FOOTER = + +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = # The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the LATEX_OUTPUT output @@ -1630,7 +1719,7 @@ LATEX_FOOTER = # markers available. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_EXTRA_FILES = +LATEX_EXTRA_FILES = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is # prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will @@ -1641,8 +1730,8 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = NO -# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES to get a +# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate +# the PDF file directly from the LaTeX files. Set this option to YES, to get a # higher quality PDF documentation. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1683,11 +1772,19 @@ LATEX_SOURCE_CODE = NO LATEX_BIB_STYLE = plain +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + #--------------------------------------------------------------------------- # Configuration options related to the RTF output #--------------------------------------------------------------------------- -# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The # RTF output is optimized for Word 97 and may not look too pretty with other RTF # readers/editors. # The default value is: NO. @@ -1702,7 +1799,7 @@ GENERATE_RTF = YES RTF_OUTPUT = rtf -# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF # documents. This may be useful for small projects and may help to save some # trees in general. # The default value is: NO. @@ -1730,20 +1827,30 @@ RTF_HYPERLINKS = NO # default style sheet that doxygen normally uses. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_STYLESHEET_FILE = +RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is # similar to doxygen's config file. A template extensions file can be generated # using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_EXTENSIONS_FILE = +RTF_EXTENSIONS_FILE = + +# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code +# with syntax highlighting in the RTF output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_SOURCE_CODE = NO #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- -# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for # classes and files. # The default value is: NO. @@ -1767,6 +1874,13 @@ MAN_OUTPUT = man MAN_EXTENSION = .3 +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + # If the MAN_LINKS tag is set to YES and doxygen generates man output, then it # will generate one additional man file for each entity documented in the real # man page(s). These additional files only source the real man page, but without @@ -1780,7 +1894,7 @@ MAN_LINKS = NO # Configuration options related to the XML output #--------------------------------------------------------------------------- -# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that # captures the structure of the code including all documentation. # The default value is: NO. @@ -1794,19 +1908,7 @@ GENERATE_XML = NO XML_OUTPUT = xml -# The XML_SCHEMA tag can be used to specify a XML schema, which can be used by a -# validating XML parser to check the syntax of the XML files. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify a XML DTD, which can be used by a -# validating XML parser to check the syntax of the XML files. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_DTD = - -# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program # listings (including syntax highlighting and cross-referencing information) to # the XML output. Note that enabling this will significantly increase the size # of the XML output. @@ -1819,7 +1921,7 @@ XML_PROGRAMLISTING = YES # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- -# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files # that can be used to generate PDF. # The default value is: NO. @@ -1833,14 +1935,23 @@ GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook +# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the +# program listings (including syntax highlighting and cross-referencing +# information) to the DOCBOOK output. Note that enabling this will significantly +# increase the size of the DOCBOOK output. +# The default value is: NO. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_PROGRAMLISTING = NO + #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- -# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen -# Definitions (see http://autogen.sf.net) file that captures the structure of -# the code including all documentation. Note that this feature is still -# experimental and incomplete at the moment. +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see http://autogen.sf.net) file that captures the +# structure of the code including all documentation. Note that this feature is +# still experimental and incomplete at the moment. # The default value is: NO. GENERATE_AUTOGEN_DEF = NO @@ -1849,7 +1960,7 @@ GENERATE_AUTOGEN_DEF = NO # Configuration options related to the Perl module output #--------------------------------------------------------------------------- -# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module # file that captures the structure of the code including all documentation. # # Note that this feature is still experimental and incomplete at the moment. @@ -1857,7 +1968,7 @@ GENERATE_AUTOGEN_DEF = NO GENERATE_PERLMOD = NO -# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary # Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI # output from the Perl module output. # The default value is: NO. @@ -1865,9 +1976,9 @@ GENERATE_PERLMOD = NO PERLMOD_LATEX = NO -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely # formatted so it can be parsed by a human reader. This is useful if you want to -# understand what is going on. On the other hand, if this tag is set to NO the +# understand what is going on. On the other hand, if this tag is set to NO, the # size of the Perl module output will be much smaller and Perl will parse it # just the same. # The default value is: YES. @@ -1881,20 +1992,20 @@ PERLMOD_PRETTY = YES # overwrite each other's variables. # This tag requires that the tag GENERATE_PERLMOD is set to YES. -PERLMOD_MAKEVAR_PREFIX = +PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- -# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all # C-preprocessor directives found in the sources and include files. # The default value is: YES. ENABLE_PREPROCESSING = YES -# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names -# in the source code. If set to NO only conditional compilation will be +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be # performed. Macro expansion can be done in a controlled way by setting # EXPAND_ONLY_PREDEF to YES. # The default value is: NO. @@ -1910,7 +2021,7 @@ MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = NO -# If the SEARCH_INCLUDES tag is set to YES the includes files in the +# If the SEARCH_INCLUDES tag is set to YES, the include files in the # INCLUDE_PATH will be searched if a #include is found. # The default value is: YES. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. @@ -1922,7 +2033,7 @@ SEARCH_INCLUDES = YES # preprocessor. # This tag requires that the tag SEARCH_INCLUDES is set to YES. -INCLUDE_PATH = +INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the @@ -1930,7 +2041,7 @@ INCLUDE_PATH = # used. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -INCLUDE_FILE_PATTERNS = +INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that are # defined before the preprocessor is started (similar to the -D option of e.g. @@ -1940,7 +2051,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -1949,12 +2060,12 @@ PREDEFINED = # definition found in the source code. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_AS_DEFINED = +EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will -# remove all refrences to function-like macros that are alone on a line, have an -# all uppercase name, and do not end with a semicolon. Such function macros are -# typically used for boiler-plate code, and will confuse the parser if not +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not # removed. # The default value is: YES. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. @@ -1974,32 +2085,33 @@ SKIP_FUNCTION_MACROS = YES # where loc1 and loc2 can be relative or absolute paths or URLs. See the # section "Linking to external documentation" for more information about the use # of tag files. -# Note: Each tag file must have an unique name (where the name does NOT include +# Note: Each tag file must have a unique name (where the name does NOT include # the path). If a tag file is not located in the directory in which doxygen is # run, you must also specify the path to the tagfile here. -TAGFILES = +TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create a # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. -GENERATE_TAGFILE = +GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES all external class will be listed in the -# class index. If set to NO only the inherited external classes will be listed. +# If the ALLEXTERNALS tag is set to YES, all external class will be listed in +# the class index. If set to NO, only the inherited external classes will be +# listed. # The default value is: NO. ALLEXTERNALS = NO -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in -# the modules index. If set to NO, only the current project's groups will be +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will be # listed. # The default value is: YES. EXTERNAL_GROUPS = YES -# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in # the related pages index. If set to NO, only the current project's pages will # be listed. # The default value is: YES. @@ -2016,14 +2128,14 @@ PERL_PATH = /usr/bin/perl # Configuration options related to the dot tool #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram +# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram # (in HTML and LaTeX) for classes with base or super classes. Setting the tag to # NO turns the diagrams off. Note that this option also works with HAVE_DOT # disabled, but it is recommended to install and use dot, since it yields more # powerful graphs. # The default value is: YES. -CLASS_DIAGRAMS = YES +CLASS_DIAGRAMS = NO # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see: @@ -2032,16 +2144,16 @@ CLASS_DIAGRAMS = YES # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. -MSCGEN_PATH = +MSCGEN_PATH = # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. # If left empty dia is assumed to be found in the default search path. -DIA_PATH = +DIA_PATH = -# If set to YES, the inheritance and collaboration graphs will hide inheritance +# If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. # The default value is: YES. @@ -2052,9 +2164,9 @@ HIDE_UNDOC_RELATIONS = YES # http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO -# The default value is: NO. +# The default value is: YES. -HAVE_DOT = NO +HAVE_DOT = YES # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed # to run in parallel. When set to 0 doxygen will base this on the number of @@ -2066,7 +2178,7 @@ HAVE_DOT = NO DOT_NUM_THREADS = 0 -# When you want a differently looking font n the dot files that doxygen +# When you want a differently looking font in the dot files that doxygen # generates you can specify the font name using DOT_FONTNAME. You need to make # sure dot is able to find the font, which can be done by putting it in a # standard location or by setting the DOTFONTPATH environment variable or by @@ -2088,7 +2200,7 @@ DOT_FONTSIZE = 10 # the path where dot can find it using this tag. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTPATH = +DOT_FONTPATH = # If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for # each documented class showing the direct and indirect inheritance relations. @@ -2114,7 +2226,7 @@ COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. # The default value is: NO. @@ -2166,22 +2278,24 @@ INCLUDED_BY_GRAPH = YES # # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected -# functions only using the \callgraph command. +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. -CALL_GRAPH = NO +CALL_GRAPH = YES # If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller # dependency graph for every global function or class method. # # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable caller graphs for selected -# functions only using the \callergraph command. +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. # The default value is: NO. # This tag requires that the tag HAVE_DOT is set to YES. -CALLER_GRAPH = NO +CALLER_GRAPH = YES # If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical # hierarchy of all classes instead of a textual one. @@ -2200,11 +2314,17 @@ GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# http://www.graphviz.org/)). # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). -# Possible values are: png, jpg, gif and svg. +# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd, +# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo, +# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. # The default value is: png. # This tag requires that the tag HAVE_DOT is set to YES. @@ -2226,26 +2346,44 @@ INTERACTIVE_SVG = NO # found. If left blank, it is assumed the dot tool can be found in the path. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_PATH = +DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the \dotfile # command). # This tag requires that the tag HAVE_DOT is set to YES. -DOTFILE_DIRS = +DOTFILE_DIRS = # The MSCFILE_DIRS tag can be used to specify one or more directories that # contain msc files that are included in the documentation (see the \mscfile # command). -MSCFILE_DIRS = +MSCFILE_DIRS = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile # command). -DIAFILE_DIRS = +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file. If left blank, it is assumed +# PlantUML is not used or called during a preprocessing step. Doxygen will +# generate a warning when it encounters a \startuml command in this case and +# will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes @@ -2283,7 +2421,7 @@ MAX_DOT_GRAPH_DEPTH = 0 DOT_TRANSPARENT = NO -# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support # this, this feature is disabled by default. @@ -2300,7 +2438,7 @@ DOT_MULTI_TARGETS = YES GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot # files that are used to generate the various graphs. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. diff --git a/simgear/CMakeLists.txt b/simgear/CMakeLists.txt index ca26c9d2..7646ac09 100644 --- a/simgear/CMakeLists.txt +++ b/simgear/CMakeLists.txt @@ -115,11 +115,22 @@ target_include_directories(SimGearCore BEFORE PUBLIC $ $) +# so simgear/simgear_config.h is found +target_include_directories(SimGearCore BEFORE PUBLIC + $ + $) + target_include_directories(SimGearCore PUBLIC ${Boost_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR}) target_include_directories(SimGearCore PRIVATE ${EXPAT_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS}) +if (NOT SYSTEM_EXPAT) + # XML_STATIC is important to avoid sg_expat_external.h + # declaring symbols as declspec(import) + target_compile_definitions(SimGearCore PRIVATE HAVE_SIMGEAR_EXPAT_CONFIG_H XML_STATIC) +endif() + install(TARGETS SimGearCore EXPORT SimGearTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -144,9 +155,10 @@ if (NOT SIMGEAR_HEADLESS) endif() endif() +# we expose ZLib in some of our headers +target_link_libraries(SimGearCore PUBLIC ${ZLIB_LIBRARY}) -target_link_libraries(SimGearCore - ${ZLIB_LIBRARY} +target_link_libraries(SimGearCore PRIVATE ${RT_LIBRARY} ${DL_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} @@ -155,29 +167,29 @@ target_link_libraries(SimGearCore ${WINSOCK_LIBRARY}) if(SYSTEM_EXPAT) - target_link_libraries(SimGearCore - ${EXPAT_LIBRARIES}) + target_link_libraries(SimGearCore PRIVATE ${EXPAT_LIBRARIES}) endif() if(ENABLE_DNS AND SYSTEM_UDNS) - target_link_libraries(SimGearCore - ${UDNS_LIBRARIES}) + target_link_libraries(SimGearCore PRIVATE ${UDNS_LIBRARIES}) endif() if(NOT SIMGEAR_HEADLESS) target_include_directories(SimGearScene PRIVATE ${PROJECT_SOURCE_DIR}/simgear/canvas/ShivaVG/include) - target_link_libraries(SimGearScene + target_link_libraries(SimGearScene PUBLIC SimGearCore - ${ZLIB_LIBRARY} ${OPENSCENEGRAPH_LIBRARIES} + ) + + target_link_libraries(SimGearScene PRIVATE + ${ZLIB_LIBRARY} ${OPENAL_LIBRARY} ${OPENGL_LIBRARY} ${JPEG_LIBRARY}) if(ENABLE_GDAL) - target_link_libraries(SimGearScene - ${GDAL_LIBRARIES}) + target_link_libraries(SimGearScene PRIVATE ${GDAL_LIBRARIES}) endif() # only actually needed by canvas/KeyboardEvent.cxx diff --git a/simgear/canvas/CanvasWindow.hxx b/simgear/canvas/CanvasWindow.hxx index db24bb9c..665a660a 100644 --- a/simgear/canvas/CanvasWindow.hxx +++ b/simgear/canvas/CanvasWindow.hxx @@ -68,8 +68,8 @@ namespace canvas Element* parent = 0 ); virtual ~Window(); - virtual void update(double delta_time_sec); - virtual void valueChanged(SGPropertyNode* node); + void update(double delta_time_sec) override; + void valueChanged(SGPropertyNode* node) override; const SGVec2 getPosition() const; const SGRect getScreenRegion() const; @@ -84,8 +84,8 @@ namespace canvas bool isResizable() const; bool isCapturingEvents() const; - virtual void setVisible(bool visible); - virtual bool isVisible() const; + void setVisible(bool visible) override; + bool isVisible() const override; /** * Moves window on top of all other windows with the same z-index. diff --git a/simgear/canvas/ShivaVG/src/shConfig.h b/simgear/canvas/ShivaVG/src/shConfig.h index 6ed402e7..77be46ac 100644 --- a/simgear/canvas/ShivaVG/src/shConfig.h +++ b/simgear/canvas/ShivaVG/src/shConfig.h @@ -31,6 +31,11 @@ // FreeBSD #define VG_API_FREEBSD +#elif defined(__OpenBSD__) + + // FreeBSD + #define VG_API_OPENBSD + #else // Unsupported system diff --git a/simgear/canvas/ShivaVG/src/shDefs.h b/simgear/canvas/ShivaVG/src/shDefs.h index ee1774b5..f4355b18 100644 --- a/simgear/canvas/ShivaVG/src/shDefs.h +++ b/simgear/canvas/ShivaVG/src/shDefs.h @@ -34,7 +34,7 @@ #include #include -#if !defined(VG_API_MACOSX) && !defined(__FreeBSD__) +#if !defined(VG_API_MACOSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) # include #endif @@ -161,7 +161,7 @@ SHfloat getMaxFloat(); /* OpenGL headers */ -#if defined(VG_API_LINUX) || defined(VG_API_FREEBSD) +#if defined(VG_API_LINUX) || defined(VG_API_FREEBSD) || defined(VG_API_OPENBSD) #include #include #elif defined(VG_API_MACOSX) diff --git a/simgear/canvas/elements/CanvasImage.cxx b/simgear/canvas/elements/CanvasImage.cxx index fbd2cf9f..3964653b 100644 --- a/simgear/canvas/elements/CanvasImage.cxx +++ b/simgear/canvas/elements/CanvasImage.cxx @@ -838,5 +838,98 @@ namespace canvas return false; } + void Image::fillRect(const SGRect& rect, const std::string& c) + { + osg::Vec4 color(1,1,1,1); + if(!c.empty() && !parseColor(c, color)) + return; + + fillRect(rect, color); + } + +void fillRow(GLubyte* row, GLuint pixel, GLuint width, GLuint pixelBytes) +{ + GLubyte* dst = row; + for (int x = 0; x < width; ++x) { + memcpy(dst, &pixel, pixelBytes); + dst += pixelBytes; + } +} + +SGRect intersectRect(const SGRect& a, const SGRect& b) +{ + SGVec2 m1 = max(a.getMin(), b.getMin()); + SGVec2 m2 = min(a.getMax(), b.getMax()); + return SGRect(m1, m2); +} + + void Image::fillRect(const SGRect& rect, const osg::Vec4& color) + { + osg::ref_ptr image = _texture->getImage(); + const auto format = image->getInternalTextureFormat(); + + auto clippedRect = intersectRect(rect, SGRect(0, 0, image->s(), image->t())); + if ((clippedRect.width() == 0) || (clippedRect.height() == 0)) { + return; + } + + GLubyte* rowData = nullptr; + size_t rowByteSize = 0; + GLuint pixelWidth = clippedRect.width(); + GLuint pixel = 0; + GLuint pixelBytes = 0; + + switch (format) { + case GL_RGBA8: + rowByteSize = pixelWidth * 4; + rowData = static_cast(alloca(rowByteSize)); + + // assume litte-endian, so read out backwards, hence when we memcpy + // the data, it ends up in RGBA order + pixel = color.asABGR(); + pixelBytes = 4; + fillRow(rowData, pixel, pixelWidth, pixelBytes); + break; + + case GL_RGB8: + rowByteSize = pixelWidth * 3; + rowData = static_cast(alloca(rowByteSize)); + pixel = color.asABGR(); + pixelBytes = 3; + fillRow(rowData, pixel, pixelWidth, pixelBytes); + break; + + default: + SG_LOG(SG_IO, SG_WARN, "Image::fillRect: unsupported internal image format:" << format); + return; + } + + for (int row=clippedRect.t(); row < clippedRect.b(); ++row) { + GLubyte* imageData = image->data(clippedRect.l(), row); + memcpy(imageData, rowData, rowByteSize); + } + + setImage(image); + } + + void Image::setPixel(int x, int y, const std::string& c) + { + osg::Vec4 color(1,1,1,1); + if(!c.empty() && !parseColor(c, color)) + return; + + setPixel(x, y, color); + } + + void Image::setPixel(int x, int y, const osg::Vec4& color) + { + osg::ref_ptr image = _texture->getImage(); + image->setColor(color, x, y); + + // is this needed, or does OSG track modifications to the data + // automatically? + setImage(image); + } + } // namespace canvas } // namespace simgear diff --git a/simgear/canvas/elements/CanvasImage.hxx b/simgear/canvas/elements/CanvasImage.hxx index 9641c489..dedcaf01 100644 --- a/simgear/canvas/elements/CanvasImage.hxx +++ b/simgear/canvas/elements/CanvasImage.hxx @@ -101,6 +101,22 @@ namespace canvas */ void setSourceRect(const SGRect& sourceRect); + /** + * fill the specified rectangle of the image, with an RGB value + */ + void fillRect(const SGRect& rect, const std::string& color); + + /** + * fill the specified rectangle of the image, with an RGB value + */ + void fillRect(const SGRect& rect, const osg::Vec4& color); + + void setPixel(int x, int y, const std::string& color); + + void setPixel(int x, int y, const osg::Vec4& color); + + + // void setRow(int row, int offset, ) protected: enum ImageAttributes { diff --git a/simgear/canvas/elements/CanvasText.cxx b/simgear/canvas/elements/CanvasText.cxx index 3051045f..9f7a54d7 100644 --- a/simgear/canvas/elements/CanvasText.cxx +++ b/simgear/canvas/elements/CanvasText.cxx @@ -53,18 +53,19 @@ namespace canvas TextLine lineAt(size_t i) const; /// Get nearest line to given y-coordinate +#if OSG_VERSION_LESS_THAN(3,6,5) TextLine nearestLine(float pos_y) const; - - SGVec2i sizeForWidth(int w) const; - - osg::BoundingBox -#if OSG_VERSION_LESS_THAN(3,3,2) - computeBound() #else - computeBoundingBox() + TextLine nearestLine(float pos_y); + SGVec2i sizeForWidth(int w); +#endif + +#if OSG_VERSION_LESS_THAN(3,3,2) + osg::BoundingBox computeBound() const override; +#else + osg::BoundingBox computeBoundingBox() const override; #endif - const override; protected: friend class TextLine; @@ -126,7 +127,6 @@ namespace canvas _quads = &text->_textureGlyphQuadMap.begin()->second; - #if OSG_VERSION_LESS_THAN(3,5,6) GlyphQuads::LineNumbers const& line_numbers = _quads->_lineNumbers; GlyphQuads::LineNumbers::const_iterator begin_it = @@ -140,8 +140,7 @@ namespace canvas _end = std::upper_bound(begin_it, line_numbers.end(), _line) - line_numbers.begin(); #else -//OSG:TODO: Need 3.5.6 version of this - + // TODO: Need 3.5.6 version of this #endif } @@ -171,11 +170,17 @@ namespace canvas if( empty() ) return pos; + +#if OSG_VERSION_GREATER_OR_EQUAL(3,5,6) + // TODO: need 3.5.6 version of this. +#else #if OSG_VERSION_LESS_THAN(3,3,5) GlyphQuads::Coords2 const& coords = _quads->_coords; -#elif OSG_VERSION_LESS_THAN(3,5,6) +#else GlyphQuads::Coords2 refCoords = _quads->_coords; GlyphQuads::Coords2::element_type &coords = *refCoords.get(); +#endif + size_t global_i = _begin + i; if (global_i == _begin) @@ -198,8 +203,6 @@ namespace canvas // position at center between characters pos.x() = 0.5 * (prev_r + cur_l); } -#else -//OSG:TODO: need 3.5.7 version of this. #endif return pos; @@ -208,16 +211,21 @@ namespace canvas //---------------------------------------------------------------------------- osg::Vec2 TextLine::nearestCursor(float x) const { - if( empty() ) + if (empty()) return cursorPos(0); - GlyphQuads::Glyphs const& glyphs = _quads->_glyphs; +#if OSG_VERSION_GREATER_OR_EQUAL(3,5,6) + // TODO: need 3.5.7 version of this. + return cursorPos(0); +#else #if OSG_VERSION_LESS_THAN(3,3,5) GlyphQuads::Coords2 const& coords = _quads->_coords; -#elif OSG_VERSION_LESS_THAN(3,5,6) - +#else GlyphQuads::Coords2 refCoords = _quads->_coords; GlyphQuads::Coords2::element_type &coords = *refCoords.get(); +#endif + + GlyphQuads::Glyphs const& glyphs = _quads->_glyphs; float const HIT_FRACTION = 0.6; float const character_width = _text->getCharacterHeight() @@ -237,9 +245,6 @@ namespace canvas } return cursorPos(i - _begin); -#else -//OSG:TODO: need 3.5.7 version of this. - return cursorPos(0); #endif } @@ -319,9 +324,16 @@ namespace canvas } //---------------------------------------------------------------------------- +#if OSG_VERSION_LESS_THAN(3,6,5) TextLine Text::TextOSG::nearestLine(float pos_y) const { osgText::Font const* font = getActiveFont(); +#else + TextLine Text::TextOSG::nearestLine(float pos_y) + { + auto font = getActiveFont(); +#endif + if( !font || lineCount() <= 0 ) return TextLine(0, this); @@ -343,12 +355,21 @@ namespace canvas // simplified version of osgText::Text::computeGlyphRepresentation() to // just calculate the size for a given weight. Glpyh calculations/creating // is not necessary for this... +#if OSG_VERSION_LESS_THAN(3,6,5) SGVec2i Text::TextOSG::sizeForWidth(int w) const +#else + SGVec2i Text::TextOSG::sizeForWidth(int w) +#endif { if( _text.empty() ) return SGVec2i(0, 0); +#if OSG_VERSION_LESS_THAN(3,6,5) osgText::Font* activefont = const_cast(getActiveFont()); +#else + auto activefont = getActiveFont(); +#endif + if( !activefont ) return SGVec2i(-1, -1); @@ -628,19 +649,16 @@ namespace canvas } //---------------------------------------------------------------------------- - osg::BoundingBox #if OSG_VERSION_LESS_THAN(3,3,2) - Text::TextOSG::computeBound() + osg::BoundingBox Text::TextOSG::computeBound() const #else - Text::TextOSG::computeBoundingBox() + osg::BoundingBox Text::TextOSG::computeBoundingBox() const #endif - const { - osg::BoundingBox bb = #if OSG_VERSION_LESS_THAN(3,3,2) - osgText::Text::computeBound(); + osg::BoundingBox bb = osgText::Text::computeBound(); #else - osgText::Text::computeBoundingBox(); + osg::BoundingBox bb = osgText::Text::computeBoundingBox(); #endif #if OSG_VERSION_LESS_THAN(3,1,0) @@ -727,9 +745,9 @@ namespace canvas } #else - void Text::TextOSG::computePositionsImplementation() + void Text::TextOSG::computePositionsImplementation() { - TextBase::computePositionsImplementation(); + TextBase::computePositionsImplementation(); } #endif //---------------------------------------------------------------------------- diff --git a/simgear/debug/logstream.cxx b/simgear/debug/logstream.cxx index d5851001..f2c61531 100644 --- a/simgear/debug/logstream.cxx +++ b/simgear/debug/logstream.cxx @@ -194,7 +194,13 @@ public: { if (!shouldLog(c, p)) return; //fprintf(stderr, "%s\n", aMessage.c_str()); - fprintf(stderr, "%8.2f [%.8s]:%-10s %s\n", logTimer.elapsedMSec()/1000.0, debugPriorityToString(p), debugClassToString(c), aMessage.c_str()); + + if (file && line != -1) { + fprintf(stderr, "%8.2f %s:%i: [%.8s]:%-10s %s\n", logTimer.elapsedMSec()/1000.0, file, line, debugPriorityToString(p), debugClassToString(c), aMessage.c_str()); + } + else { + fprintf(stderr, "%8.2f [%.8s]:%-10s %s\n", logTimer.elapsedMSec()/1000.0, debugPriorityToString(p), debugClassToString(c), aMessage.c_str()); + } // file, line, aMessage.c_str()); //fprintf(stderr, "%s:%d:%s:%d:%s\n", debugClassToString(c), p, // file, line, aMessage.c_str()); @@ -407,6 +413,7 @@ public: bool m_stdout_isRedirectedAlready = false; #endif bool m_developerMode = false; + bool m_fileLine = false; // test suite mode. bool m_testMode = false; @@ -438,7 +445,7 @@ public: LogEntry entry(m_entries.pop()); // special marker entry detected, terminate the thread since we are // making a configuration change or quitting the app - if ((entry.debugClass == SG_NONE) && !strcmp(entry.file, "done")) { + if ((entry.debugClass == SG_NONE) && entry.file && !strcmp(entry.file, "done")) { return; } { @@ -535,6 +542,10 @@ public: const char* fileName, int line, const std::string& msg) { p = translatePriority(p); + if (!m_fileLine) { + /* This prevents output of file:line. */ + line = -1; + } LogEntry entry(c, p, fileName, line, msg); m_entries.push(entry); } @@ -581,6 +592,10 @@ void logstream::setDeveloperMode(bool devMode) d->m_developerMode = devMode; } +void logstream::setFileLine(bool fileLine) +{ + d->m_fileLine = fileLine; +} void logstream::addCallback(simgear::LogCallback* cb) diff --git a/simgear/debug/logstream.hxx b/simgear/debug/logstream.hxx index 30c656c7..ec101d44 100644 --- a/simgear/debug/logstream.hxx +++ b/simgear/debug/logstream.hxx @@ -112,6 +112,12 @@ public: */ void setDeveloperMode(bool devMode); + /** + * set output of file:line mode on/off. If on, all log messages are + * prefixed by the file:line of the caller of SG_LOG(). + */ + void setFileLine(bool fileLine); + /** * the core logging method */ diff --git a/simgear/emesary/test_emesary.cxx b/simgear/emesary/test_emesary.cxx index 852fa55b..55acd622 100644 --- a/simgear/emesary/test_emesary.cxx +++ b/simgear/emesary/test_emesary.cxx @@ -32,20 +32,23 @@ class TestThreadRecipient : public simgear::Emesary::IReceiver public: TestThreadRecipient() : receiveCount(0) { - } std::atomic receiveCount; + virtual simgear::Emesary::ReceiptStatus Receive(simgear::Emesary::INotification &n) { if (n.GetType() == (const char*)this) { - TestThreadNotification *tn = dynamic_cast(&n); + // Unused: TestThreadNotification *tn = dynamic_cast(&n); receiveCount++; + TestThreadNotification onwardNotification("AL"); simgear::Emesary::GlobalTransmitter::instance()->NotifyAll(onwardNotification); + return simgear::Emesary::ReceiptStatusOK; } + return simgear::Emesary::ReceiptStatusOK; } }; diff --git a/simgear/environment/metar.cxx b/simgear/environment/metar.cxx index 5b497a05..9d9c944f 100644 --- a/simgear/environment/metar.cxx +++ b/simgear/environment/metar.cxx @@ -38,9 +38,12 @@ # include #endif +#include #include #include #include +#include +#include #include #include @@ -155,6 +158,320 @@ SGMetar::~SGMetar() } +static const char *azimuthName(double d) +{ + const char *dir[] = { + "N", "NNE", "NE", "ENE", + "E", "ESE", "SE", "SSE", + "S", "SSW", "SW", "WSW", + "W", "WNW", "NW", "NNW" + }; + d += 11.25; + while (d < 0) + d += 360; + while (d >= 360) + d -= 360; + return dir[int(d / 22.5)]; +} + + +// round double to 10^g +static double rnd(double r, int g = 0) +{ + double f = pow(10.0, g); + return f * floor(r / f + 0.5); +} + + +/* A manipulator that can use spaces to emulate tab characters. */ +struct Tab +{ + /* If is 0, we simply insert tab characters. Otherwise we insert + spaces to align with the next column at multiple of . */ + explicit Tab(int stops) + : + _stops(stops) + {} + int _stops; +}; + +std::ostream& operator << (std::ostream& out, const Tab& t) +{ + if (t._stops == 0) { + return out << '\t'; + } + + std::ostringstream& out2 = *(std::ostringstream*) &out; + std::string s = out2.str(); + + if (t._stops < 0) { + if (!s.size() || s[s.size()-1] != ' ') { + out << ' '; + } + return out; + } + + auto nl = s.rfind('\n'); + if (nl < 0) nl = 0; + int column = 0; + for (auto i = nl+1; i != s.size(); ++i) { + if (s[i] == '\t') + column = (column + t._stops) / t._stops * t._stops; + else + column += 1; + } + int column2 = (column + t._stops) / t._stops * t._stops; + for (int i=column; igetVisibility_m() != NaN) + out << tab << tab << tab << SGMetarVisibilityManip(*dirvis, tab) << "\n"; + + + // vertical visibility + SGMetarVisibility vertvis = getVertVisibility(); + if ((d = vertvis.getVisibility_ft()) != NaN) + out << "Vert. visibility:" << tab << SGMetarVisibilityManip(vertvis, tab) << "\n"; + else if (vertvis.getModifier() == SGMetarVisibility::NOGO) + out << "Vert. visibility:" << tab << "impossible to determine" << "\n"; + + + // wind + d = getWindSpeed_kmh(); + out << "Wind:" << tab << tab << tab; + if (d < .1) + out << "none" << "\n"; + else { + if ((i = getWindDir()) == -1) + out << "from variable directions"; + else + out << "from the " << azimuthName(i) << " (" << i << " deg)"; + out << " at " << rnd(d, -1) << " km/h"; + + out << tab << tab << rnd(getWindSpeed_kt(), -1) << " kt"; + out << " = " << rnd(getWindSpeed_mph(), -1) << " mph"; + out << " = " << rnd(getWindSpeed_mps(), -1) << " m/s"; + out << "\n"; + + d = getGustSpeed_kmh(); + if (d != NaN && d != 0) { + out << tab << tab << tab << "with gusts at " << rnd(d, -1) << " km/h"; + out << tab << tab << tab << rnd(getGustSpeed_kt(), -1) << " kt"; + out << " = " << rnd(getGustSpeed_mph(), -1) << " mph"; + out << " = " << rnd(getGustSpeed_mps(), -1) << " m/s"; + out << "\n"; + } + + int from = getWindRangeFrom(); + int to = getWindRangeTo(); + if (from != to) { + out << tab << tab << tab << "variable from " << azimuthName(from); + out << " to " << azimuthName(to); + out << " (" << from << "deg --" << to << " deg)" << "\n"; + } + } + + + // temperature/humidity/air pressure + if ((d = getTemperature_C()) != NaN) { + out << "Temperature:" << tab << tab << d << " C" << tab << tab << tab << tab << tab; + out << rnd(getTemperature_F(), -1) << " F" << "\n"; + + if ((d = getDewpoint_C()) != NaN) { + out << "Dewpoint:" << tab << tab << d << " C" << tab << tab << tab << tab << tab; + out << rnd(getDewpoint_F(), -1) << " F" << "\n"; + out << "Rel. Humidity: " << tab << tab << rnd(getRelHumidity()) << " %" << "\n"; + } + } + if ((d = getPressure_hPa()) != NaN) { + out << "Pressure:" << tab << tab << rnd(d) << " hPa" << tab << tab << tab << tab; + out << rnd(getPressure_inHg(), -2) << " in. Hg" << "\n"; + } + + + // weather phenomena + vector wv = getWeather(); + vector::iterator weather; + for (i = 0, weather = wv.begin(); weather != wv.end(); weather++, i++) { + out << (i ? ", " : "Weather:") << tab << tab << weather->c_str(); + } + if (i) + out << "\n"; + + + // cloud layers + const char *coverage_string[5] = { + "clear skies", "few clouds", "scattered clouds", "broken clouds", "sky overcast" + }; + vector cv = getClouds(); + vector::iterator cloud; + for (lineno = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, lineno++) { + if (lineno) out << tab << tab << tab; + else out << "Sky condition:" << tab << tab; + + if ((i = cloud->getCoverage()) != -1) + out << coverage_string[i]; + if ((d = cloud->getAltitude_ft()) != NaN) + out << " at " << rnd(d, 1) << " ft"; + if ((s = cloud->getTypeLongString())) + out << " (" << s << ')'; + if (d != NaN) + out << tab << tab << tab << rnd(cloud->getAltitude_m(), 1) << " m"; + out << "\n"; + } + + + // runways + map rm = getRunways(); + map::iterator runway; + for (runway = rm.begin(); runway != rm.end(); runway++) { + lineno = 0; + if (!strcmp(runway->first.c_str(), "ALL")) + out << "All runways:" << tab << tab; + else + out << "Runway " << runway->first << ":" << tab << tab; + SGMetarRunway rwy = runway->second; + + // assemble surface string + vector surface; + if ((s = rwy.getDepositString()) && strlen(s)) + surface.push_back(s); + if ((s = rwy.getExtentString()) && strlen(s)) + surface.push_back(s); + if ((d = rwy.getDepth()) != NaN) { + sprintf(buf, "%.1lf mm", d * 1000.0); + surface.push_back(buf); + } + if ((s = rwy.getFrictionString()) && strlen(s)) + surface.push_back(s); + if ((d = rwy.getFriction()) != NaN) { + sprintf(buf, "friction: %.2lf", d); + surface.push_back(buf); + } + + if (! surface.empty()) { + vector::iterator rwysurf = surface.begin(); + for (i = 0; rwysurf != surface.end(); rwysurf++, i++) { + if (i) + out << ", "; + out << *rwysurf; + } + lineno++; + } + + // assemble visibility string + SGMetarVisibility minvis = rwy.getMinVisibility(); + SGMetarVisibility maxvis = rwy.getMaxVisibility(); + if ((d = minvis.getVisibility_m()) != NaN) { + if (lineno++) + out << "\n" << tab << tab << tab; + out << SGMetarVisibilityManip(minvis, tab); + } + if (maxvis.getVisibility_m() != d) { + out << "\n" << tab << tab << tab << SGMetarVisibilityManip(maxvis, tab) << "\n"; + lineno++; + } + + if (rwy.getWindShear()) { + if (lineno++) + out << "\n" << tab << tab << tab; + out << "critical wind shear" << "\n"; + } + out << "\n"; + } + out << "\n"; + return out.str(); +} + void SGMetar::useCurrentDate() { struct tm now; diff --git a/simgear/environment/metar.hxx b/simgear/environment/metar.hxx index 2f2e5d02..a92bf1a5 100644 --- a/simgear/environment/metar.hxx +++ b/simgear/environment/metar.hxx @@ -235,6 +235,11 @@ public: inline const std::vector& getWeather() const { return _weather; } inline const std::vector getWeather2() const { return _weather2; } + /* Returns human-readable description. If tabtops is 0, we use tab + characters. If +ve we use spaces to pad to multiple of . If + -1 all sequences of tabs are represented by a single space. */ + std::string getDescription(int tabstops) const; + protected: std::string _url; int _grpcount; diff --git a/simgear/io/iostreams/zlibstream_test.cxx b/simgear/io/iostreams/zlibstream_test.cxx index b62ec160..20ae1c10 100644 --- a/simgear/io/iostreams/zlibstream_test.cxx +++ b/simgear/io/iostreams/zlibstream_test.cxx @@ -431,7 +431,7 @@ void test_ZlibDecompressorIStream_readPutbackEtc() try { // 'Z' is not the last character read from the stream decompressor.putback('Z'); - } catch (std::ios_base::failure) { + } catch (const std::ios_base::failure&) { gotException = true; } catch (const std::exception& e) { // gcc fails to catch std::ios_base::failure due to an inconsistent C++11 diff --git a/simgear/io/untar.cxx b/simgear/io/untar.cxx index 4d825efe..cc70c62f 100644 --- a/simgear/io/untar.cxx +++ b/simgear/io/untar.cxx @@ -48,6 +48,8 @@ namespace simgear assert(outer); } + virtual ~ArchiveExtractorPrivate() = default; + typedef enum { INVALID = 0, READING_HEADER, @@ -614,10 +616,7 @@ ArchiveExtractor::ArchiveExtractor(const SGPath& rootPath) : { } -ArchiveExtractor::~ArchiveExtractor() -{ - -} +ArchiveExtractor::~ArchiveExtractor() = default; void ArchiveExtractor::extractBytes(const uint8_t* bytes, size_t count) { diff --git a/simgear/io/untar.hxx b/simgear/io/untar.hxx index 1b3d9ccc..9760544c 100644 --- a/simgear/io/untar.hxx +++ b/simgear/io/untar.hxx @@ -34,7 +34,7 @@ class ArchiveExtractor { public: ArchiveExtractor(const SGPath& rootPath); - ~ArchiveExtractor(); + virtual ~ArchiveExtractor(); enum DetermineResult { diff --git a/simgear/math/SGVec2.hxx b/simgear/math/SGVec2.hxx index 93655cb1..d9e4439f 100644 --- a/simgear/math/SGVec2.hxx +++ b/simgear/math/SGVec2.hxx @@ -193,8 +193,8 @@ min(S s, SGVec2 v) template inline SGVec2 -max(const SGVec2& v1, const SGVec2& v2) -{ v1 = simd4::max(v1.simd2(), v2.simd2()); return v1; } +max(SGVec2 v1, const SGVec2& v2) +{ v1.simd2() = simd4::max(v1.simd2(), v2.simd2()); return v1; } template inline SGVec2 @@ -375,6 +375,29 @@ interpolate(T tau, const SGVec2& v1, const SGVec2& v2) return r; } +// Helper function for point_in_triangle +template +inline +T +pt_determine(const SGVec2& pt1, const SGVec2& pt2, const SGVec2& pt3) +{ + return (pt1.x()-pt3.x()) * (pt2.y()-pt3.y()) - (pt2.x() - pt3.x()) * (pt1.y() - pt3.y()); +} + +// Is testpt inside the triangle formed by the other three points? +template +inline +bool +point_in_triangle(const SGVec2& testpt, const SGVec2& pt1, const SGVec2& pt2, const SGVec2& pt3) +{ + T d1 = pt_determine(testpt,pt1,pt2); + T d2 = pt_determine(testpt,pt2,pt3); + T d3 = pt_determine(testpt,pt3,pt1); + bool has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0); + bool has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0); + return !(has_neg && has_pos); +} + #ifndef NDEBUG template inline diff --git a/simgear/misc/strutils.cxx b/simgear/misc/strutils.cxx index 6d2cbeaa..5062e242 100644 --- a/simgear/misc/strutils.cxx +++ b/simgear/misc/strutils.cxx @@ -1043,7 +1043,7 @@ std::string error_string(int errnum) retcode = strerror_s(buf, sizeof(buf), errnum); #elif defined(_GNU_SOURCE) return std::string(strerror_r(errnum, buf, sizeof(buf))); -#elif (_POSIX_C_SOURCE >= 200112L) || defined(SG_MAC) || defined(__FreeBSD__) +#elif (_POSIX_C_SOURCE >= 200112L) || defined(SG_MAC) || defined(__FreeBSD__) || defined(__OpenBSD__) int retcode; // POSIX.1-2001 and POSIX.1-2008 retcode = strerror_r(errnum, buf, sizeof(buf)); diff --git a/simgear/nasal/code.c b/simgear/nasal/code.c index 0783b38b..f724c1ba 100644 --- a/simgear/nasal/code.c +++ b/simgear/nasal/code.c @@ -235,6 +235,21 @@ void naFreeContext(naContext c) if(c->callChild) naFreeContext(c->callChild); if(c->callParent) c->callParent->callChild = 0; LOCK(); + + // 2019-09-21 + // James adding this to ensure stray stuff in freed contexts gets GCed + // this shows up when doing a reset / shutdown of all Nasal - we drop + // all our contexts and saved refs, and run a GC pass. We expect *everything* + // to be freed but actually the freed contexts often have a ref in their + // opStack. + // + // The underlying cause is likely some operation which leaves a value on + // the opstack accidently, but tracing that down requires more Nasal-fu + // than I have right now. So instead I'm clearing the stack tops here, so + // a freed context looks the same as a new one returned by initContext. + + c->fTop = c->opTop = c->markTop = 0; + c->nextFree = globals->freeContexts; globals->freeContexts = c; UNLOCK(); diff --git a/simgear/nasal/cppbind/NasalEmesaryInterface.hxx b/simgear/nasal/cppbind/NasalEmesaryInterface.hxx index b6d01ca1..64d24f18 100644 --- a/simgear/nasal/cppbind/NasalEmesaryInterface.hxx +++ b/simgear/nasal/cppbind/NasalEmesaryInterface.hxx @@ -67,7 +67,6 @@ namespace nasal public: NasalMainLoopRecipient() : receiveCount(0) { simgear::Emesary::GlobalTransmitter::instance()->Register(*this); - SG_LOG(SG_NASAL, SG_INFO, "NasalMainLoopRecipient created"); } virtual ~NasalMainLoopRecipient() { simgear::Emesary::GlobalTransmitter::instance()->DeRegister(*this); diff --git a/simgear/nasal/cppbind/detail/from_nasal_helper.hxx b/simgear/nasal/cppbind/detail/from_nasal_helper.hxx index a582bfe6..afda9f24 100644 --- a/simgear/nasal/cppbind/detail/from_nasal_helper.hxx +++ b/simgear/nasal/cppbind/detail/from_nasal_helper.hxx @@ -207,6 +207,15 @@ namespace nasal return Vec2(vec[0], vec[1]); } + template + std::enable_if_t::value, Vec4> + from_nasal_helper(naContext c, naRef ref, const Vec4*) + { + auto vec = from_nasal_helper(c, ref, static_cast*>(0)); + return Vec4(vec[0], vec[1], vec[2], vec[3]); + } + + /** * Convert a Nasal vector with 4 elements ([x, y, w, h]) to an SGRect */ diff --git a/simgear/nasal/cppbind/detail/nasal_traits.hxx b/simgear/nasal/cppbind/detail/nasal_traits.hxx index 17e18ecd..228b2556 100644 --- a/simgear/nasal/cppbind/detail/nasal_traits.hxx +++ b/simgear/nasal/cppbind/detail/nasal_traits.hxx @@ -28,6 +28,7 @@ class SGWeakReferenced; template class SGSharedPtr; template class SGWeakPtr; template class SGVec2; +template class SGVec4; namespace boost { @@ -47,6 +48,9 @@ namespace osg class Vec2d; class Vec2f; class Vec2s; + + class Vec4f; + class Vec4d; } // The actual traits... @@ -55,6 +59,10 @@ namespace nasal template struct is_vec2: public std::false_type {}; + template + struct is_vec4: public std::false_type {}; + + #define SG_MAKE_TRAIT(templ,type,attr)\ template templ\ struct attr< type >: public std::true_type {}; @@ -65,6 +73,10 @@ SG_MAKE_TRAIT(<>, osg::Vec2d, is_vec2) SG_MAKE_TRAIT(<>, osg::Vec2f, is_vec2) SG_MAKE_TRAIT(<>, osg::Vec2s, is_vec2) +SG_MAKE_TRAIT(, SGVec4, is_vec4) +SG_MAKE_TRAIT(<>, osg::Vec4d, is_vec4) +SG_MAKE_TRAIT(<>, osg::Vec4f, is_vec4) + #undef SG_MAKE_TRAIT template diff --git a/simgear/nasal/parse.c b/simgear/nasal/parse.c index e5b012fa..419bf5b3 100644 --- a/simgear/nasal/parse.c +++ b/simgear/nasal/parse.c @@ -420,10 +420,14 @@ naRef naParseCode(struct Context* c, naRef srcFile, int firstLine, // Catch parser errors here. p.errLine = *errLine = 1; - if(setjmp(p.jumpHandle)) { - strncpy(c->error, p.err, sizeof(c->error)); + if (setjmp(p.jumpHandle)) { + size_t end_ = sizeof(c->error) - 1; + strncpy(c->error, p.err, end_); + c->error[end_] = '\0'; + *errLine = p.errLine; naParseDestroy(&p); + return naNil(); } diff --git a/simgear/props/props.cxx b/simgear/props/props.cxx index 9d941559..c6a84df0 100644 --- a/simgear/props/props.cxx +++ b/simgear/props/props.cxx @@ -849,7 +849,7 @@ SGPropertyNode::trace_read () const * Last used attribute * Update as needed when enum Attribute is changed */ -const int SGPropertyNode::LAST_USED_ATTRIBUTE = PROTECTED; +const int SGPropertyNode::LAST_USED_ATTRIBUTE = LISTENER_SAFE; /** * Default constructor: always creates a root node. diff --git a/simgear/props/props.hxx b/simgear/props/props.hxx index 3afc8796..0d407c99 100644 --- a/simgear/props/props.hxx +++ b/simgear/props/props.hxx @@ -821,6 +821,7 @@ public: USERARCHIVE = 64, PRESERVE = 128, PROTECTED = 1 << 8, + LISTENER_SAFE = 1 << 9, /// it's safe to listen to this property, even if it's tied // beware: if you add another attribute here, // also update value of "LAST_USED_ATTRIBUTE". }; diff --git a/simgear/scene/material/Effect.cxx b/simgear/scene/material/Effect.cxx index c2240c05..89b33a75 100644 --- a/simgear/scene/material/Effect.cxx +++ b/simgear/scene/material/Effect.cxx @@ -918,8 +918,13 @@ void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass, resolvedKey.attributes = prgKey.attributes; BOOST_FOREACH(const ShaderKey& shaderKey, prgKey.shaders) { - const string& shaderName = shaderKey.first; + // FIXME orig: const string& shaderName = shaderKey.first; + string shaderName = shaderKey.first; Shader::Type stype = (Shader::Type)shaderKey.second; + if (getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false) && + shaderName.substr(0, shaderName.find("/")) == "Shaders") { + shaderName = "Compositor/" + shaderName; + } string fileName = SGModelLib::findDataFile(shaderName, options); if (fileName.empty()) { @@ -1474,7 +1479,8 @@ static SGMutex realizeTechniques_lock; bool Effect::realizeTechniques(const SGReaderWriterOptions* options) { SGGuard g(realizeTechniques_lock); - mergeSchemesFallbacks(this, options); + if (getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false)) + mergeSchemesFallbacks(this, options); if (_isRealized) return true; diff --git a/simgear/scene/material/makeEffect.cxx b/simgear/scene/material/makeEffect.cxx index 35591c51..7877378e 100644 --- a/simgear/scene/material/makeEffect.cxx +++ b/simgear/scene/material/makeEffect.cxx @@ -130,8 +130,12 @@ Effect* makeEffect(const string& name, string effectFileName; // Use getPropertyRoot() because the SGReaderWriterOptions might not have a // valid property tree - if (getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false)) + if (getPropertyRoot()->getBoolValue("/sim/version/compositor-support", false) && + name.substr(0, name.find("/")) == "Effects") { + // Temporarily append the Compositor/ directory to every effect that should + // be inside Effects/. effectFileName += "Compositor/"; + } effectFileName += name; effectFileName += ".eff"; string absFileName diff --git a/simgear/scene/material/mat.cxx b/simgear/scene/material/mat.cxx index 0eaa37a6..c8d856d7 100644 --- a/simgear/scene/material/mat.cxx +++ b/simgear/scene/material/mat.cxx @@ -108,7 +108,7 @@ SGMaterial::SGMaterial( const SGReaderWriterOptions* options, } SGMaterial::SGMaterial( const osgDB::Options* options, - const SGPropertyNode *props, + const SGPropertyNode *props, SGPropertyNode *prop_root, AreaList *a, SGSharedPtr c) @@ -210,7 +210,7 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options, _internal_state st( NULL, tpath.local8BitStr(), true, options ); _status.push_back( st ); } - + std::vector masks = props->getChildren("object-mask"); for (unsigned int i = 0; i < masks.size(); i++) { @@ -243,8 +243,8 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options, // the object mask, as DDS textures have an origin at the bottom // left rather than top left. Therefore we flip a copy of the image // (otherwise a second reference to the object mask would flip it - // back!). - SG_LOG(SG_GENERAL, SG_DEBUG, "Flipping object mask" << omname); + // back!). + SG_LOG(SG_GENERAL, SG_DEBUG, "Flipping object mask" << omname); image = (osg::Image* ) image->clone(osg::CopyOp::SHALLOW_COPY); image->flipVertical(); } @@ -271,30 +271,30 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options, wrapv = props->getBoolValue("wrapv", true); mipmap = props->getBoolValue("mipmap", true); light_coverage = props->getDoubleValue("light-coverage", 0.0); - + // Building properties building_coverage = props->getDoubleValue("building-coverage", 0.0); building_spacing = props->getDoubleValue("building-spacing-m", 5.0); - + std::string bt = props->getStringValue( "building-texture", "Textures/buildings.png" ); - building_texture = SGModelLib::findDataFile(bt, options); - + building_texture = SGModelLib::findDataFile(bt, options); + if (building_texture.empty()) { SG_LOG(SG_GENERAL, SG_ALERT, "Cannot find texture \"" << bt); } - + bt = props->getStringValue("building-lightmap", "Textures/buildings-lightmap.png"); - building_lightmap = SGModelLib::findDataFile(bt, options); - + building_lightmap = SGModelLib::findDataFile(bt, options); + if (building_lightmap.empty()) { SG_LOG(SG_GENERAL, SG_ALERT, "Cannot find texture \"" << bt); - } - + } + building_small_ratio = props->getDoubleValue("building-small-ratio", 0.8); building_medium_ratio = props->getDoubleValue("building-medium-ratio", 0.15); building_large_ratio = props->getDoubleValue("building-large-ratio", 0.05); - + building_small_pitch = props->getDoubleValue("building-small-pitch", 0.8); building_medium_pitch = props->getDoubleValue("building-medium-pitch", 0.2); building_large_pitch = props->getDoubleValue("building-large-pitch", 0.1); @@ -305,29 +305,43 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options, building_medium_max_floors = props->getIntValue("building-medium-max-floors", 8); building_large_min_floors = props->getIntValue("building-large-min-floors", 5); building_large_max_floors = props->getIntValue("building-large-max-floors", 20); - + building_small_min_width = props->getFloatValue("building-small-min-width-m", 15.0); building_small_max_width = props->getFloatValue("building-small-max-width-m", 60.0); building_small_min_depth = props->getFloatValue("building-small-min-depth-m", 10.0); building_small_max_depth = props->getFloatValue("building-small-max-depth-m", 20.0); - + building_medium_min_width = props->getFloatValue("building-medium-min-width-m", 25.0); building_medium_max_width = props->getFloatValue("building-medium-max-width-m", 50.0); building_medium_min_depth = props->getFloatValue("building-medium-min-depth-m", 20.0); building_medium_max_depth = props->getFloatValue("building-medium-max-depth-m", 50.0); - + building_large_min_width = props->getFloatValue("building-large-min-width-m", 50.0); building_large_max_width = props->getFloatValue("building-large-max-width-m", 75.0); building_large_min_depth = props->getFloatValue("building-large-min-depth-m", 50.0); building_large_max_depth = props->getFloatValue("building-large-max-depth-m", 75.0); building_range = props->getDoubleValue("building-range-m", default_object_range); - + + // There are some constraints on the maximum building size that we can sensibly render. + // Using values outside these ranges will result in the texture being stretched to fit, + // which may not be desireable. We will allow it, but display warnings. + // We do not display warnings for large buildings as we assume the textures are sufficiently + // generic to be stretched without problems. + if (building_small_max_floors > 3) SG_LOG(SG_GENERAL, SG_ALERT, "building-small-max-floors exceeds maximum (3). Texture will be stretched to fit."); + if (building_medium_max_floors > 8) SG_LOG(SG_GENERAL, SG_ALERT, "building-medium-max-floors exceeds maximum (8). Texture will be stretched to fit."); + if (building_large_max_floors > 22) SG_LOG(SG_GENERAL, SG_ALERT, "building-large-max-floors exceeds maximum (22). Texture will be stretched to fit."); + if (building_small_max_width > 192.0) SG_LOG(SG_GENERAL, SG_ALERT, "building-small-max-width-m exceeds maximum (192). Texture will be stretched to fit."); + if (building_small_max_depth > 192.0) SG_LOG(SG_GENERAL, SG_ALERT, "building-small-max-depth-m exceeds maximum (192). Texture will be stretched to fit."); + if (building_medium_max_width > 80.0) SG_LOG(SG_GENERAL, SG_ALERT, "building-medium-max-width-m exceeds maximum (80). Texture will be stretched to fit."); + if (building_medium_max_depth > 80.0) SG_LOG(SG_GENERAL, SG_ALERT, "building-medium-max-depth-m exceeds maximum (80). Texture will be stretched to fit."); + cos_object_max_density_slope_angle = cos(props->getFloatValue("object-max-density-angle-deg", 20.0) * osg::PI/180.0); cos_object_zero_density_slope_angle = cos(props->getFloatValue("object-zero-density-angle-deg", 30.0) * osg::PI/180.0); - + // Random vegetation properties wood_coverage = props->getDoubleValue("wood-coverage", 0.0); + is_plantation = props->getBoolValue("plantation",false); tree_effect = props->getStringValue("tree-effect", "Effects/tree"); tree_height = props->getDoubleValue("tree-height-m", 0.0); tree_width = props->getDoubleValue("tree-width-m", 0.0); @@ -337,7 +351,7 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options, cos_tree_zero_density_slope_angle = cos(props->getFloatValue("tree-zero-density-angle-deg", 45.0) * osg::PI/180.0); const SGPropertyNode* treeTexNode = props->getChild("tree-texture"); - + if (treeTexNode) { std::string treeTexPath = props->getStringValue("tree-texture"); @@ -398,7 +412,7 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options, if (name) glyphs[name] = new SGMaterialGlyph(glyph_nodes[i]); } - + // Read parameters entry, which is passed into the effect if (props->hasChild("parameters")) { parameters = props->getChild("parameters"); @@ -413,7 +427,7 @@ SGMaterial::read_properties(const SGReaderWriterOptions* options, // Private methods. //////////////////////////////////////////////////////////////////////// -void +void SGMaterial::init () { _status.clear(); @@ -437,7 +451,7 @@ SGMaterial::init () } Effect* SGMaterial::get_effect(int i) -{ +{ if(!_status[i].effect_realized) { if (!_status[i].effect.valid()) return 0; @@ -454,7 +468,7 @@ Effect* SGMaterial::get_one_effect(int texIndex) SG_LOG( SG_GENERAL, SG_WARN, "No effect available."); return 0; } - + int i = texIndex % _status.size(); return get_effect(i); } @@ -472,9 +486,9 @@ osg::Texture2D* SGMaterial::get_one_object_mask(int texIndex) SG_LOG( SG_GENERAL, SG_WARN, "No mask available."); return 0; } - + // Note that the object mask is closely linked to the texture/effect - // so we index based on the texture index, + // so we index based on the texture index, unsigned int i = texIndex % _status.size(); if (i < _masks.size()) { return _masks[i].get(); @@ -489,10 +503,10 @@ void SGMaterial::buildEffectProperties(const SGReaderWriterOptions* options) ref_ptr user = new SGMaterialUserData(this); SGPropertyNode_ptr propRoot = new SGPropertyNode(); makeChild(propRoot, "inherits-from")->setStringValue(effect); - + SGPropertyNode* paramProp = makeChild(propRoot, "parameters"); copyProperties(parameters, paramProp); - + SGPropertyNode* materialProp = makeChild(paramProp, "material"); makeChild(materialProp, "ambient")->setValue(SGVec4d(ambient)); makeChild(materialProp, "diffuse")->setValue(SGVec4d(diffuse)); diff --git a/simgear/scene/material/mat.hxx b/simgear/scene/material/mat.hxx index ca70bd65..8e4647d2 100644 --- a/simgear/scene/material/mat.hxx +++ b/simgear/scene/material/mat.hxx @@ -244,6 +244,13 @@ public: */ inline double get_wood_coverage () const { return wood_coverage; } + /** + * Get whether or not vegetation is regularly spaced + * + * @return flag: if true, vegetation is regularly spaced. + */ + inline bool get_is_plantation () const { return is_plantation; } + /** * Get the tree height. * @@ -455,6 +462,9 @@ private: // coverage of woods double wood_coverage; + // are trees regularly planted? + bool is_plantation; + // Range at which trees become visible double tree_range; diff --git a/simgear/scene/model/BVHPageNodeOSG.hxx b/simgear/scene/model/BVHPageNodeOSG.hxx index 1a3a5a73..e6a83a4d 100644 --- a/simgear/scene/model/BVHPageNodeOSG.hxx +++ b/simgear/scene/model/BVHPageNodeOSG.hxx @@ -18,6 +18,8 @@ #ifndef BVHPageNodeOSG_hxx #define BVHPageNodeOSG_hxx +#include + #include "../../bvh/BVHPageNode.hxx" #include diff --git a/simgear/scene/model/SGLight.cxx b/simgear/scene/model/SGLight.cxx index ab332750..03b659fa 100644 --- a/simgear/scene/model/SGLight.cxx +++ b/simgear/scene/model/SGLight.cxx @@ -14,6 +14,8 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +#include + #include "SGLight.hxx" #include @@ -73,34 +75,41 @@ SGLight::appendLight(const SGPropertyNode *configNode, light->setSpotExponent(configNode->getFloatValue("spot-exponent")); light->setSpotCutoff(configNode->getFloatValue("spot-cutoff")); - osg::Group *group = 0; - if ((p = configNode->getNode("offsets")) == NULL) { - group = new osg::Group; - } else { - // Set up the alignment node ("stolen" from animation.cxx) - // XXX Order of rotations is probably not correct. - osg::MatrixTransform *align = new osg::MatrixTransform; - osg::Matrix res_matrix; - res_matrix.makeRotate( - p->getFloatValue("pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS, - osg::Vec3(0, 1, 0), - p->getFloatValue("roll-deg", 0.0)*SG_DEGREES_TO_RADIANS, - osg::Vec3(1, 0, 0), - p->getFloatValue("heading-deg", 0.0)*SG_DEGREES_TO_RADIANS, - osg::Vec3(0, 0, 1)); + osg::MatrixTransform *align = new osg::MatrixTransform; + align->addChild(light); - osg::Matrix tmat; - tmat.makeTranslate(configNode->getFloatValue("offsets/x-m", 0.0), - configNode->getFloatValue("offsets/y-m", 0.0), - configNode->getFloatValue("offsets/z-m", 0.0)); + osg::Matrix t; + osg::Vec3 pos(configNode->getFloatValue("position/x-m"), + configNode->getFloatValue("position/y-m"), + configNode->getFloatValue("position/z-m")); + t.makeTranslate(pos); - align->setMatrix(res_matrix * tmat); - group = align; + osg::Matrix r; + if (const SGPropertyNode *dirNode = configNode->getNode("direction")) { + if (dirNode->hasValue("pitch-deg")) { + r.makeRotate( + dirNode->getFloatValue("pitch-deg")*SG_DEGREES_TO_RADIANS, + osg::Vec3(0, 1, 0), + dirNode->getFloatValue("roll-deg")*SG_DEGREES_TO_RADIANS, + osg::Vec3(1, 0, 0), + dirNode->getFloatValue("heading-deg")*SG_DEGREES_TO_RADIANS, + osg::Vec3(0, 0, 1)); + } else if (dirNode->hasValue("lookat-x-m")) { + osg::Vec3 lookAt(dirNode->getFloatValue("lookat-x-m"), + dirNode->getFloatValue("lookat-y-m"), + dirNode->getFloatValue("lookat-z-m")); + osg::Vec3 dir = lookAt - pos; + r.makeRotate(osg::Vec3(0, 0, -1), dir); + } else { + r.makeRotate(osg::Vec3(0, 0, -1), + osg::Vec3(dirNode->getFloatValue("x"), + dirNode->getFloatValue("y"), + dirNode->getFloatValue("z"))); + } } + align->setMatrix(r * t); - group->addChild(light); - - osg::Shape *debug_shape; + osg::Shape *debug_shape = nullptr; if (light->getType() == SGLight::Type::POINT) { debug_shape = new osg::Sphere(osg::Vec3(0, 0, 0), light->getRange()); } else if (light->getType() == SGLight::Type::SPOT) { @@ -109,7 +118,10 @@ SGLight::appendLight(const SGPropertyNode *configNode, osg::Vec3(0, 0, -0.75 * light->getRange()), tan(light->getSpotCutoff() * SG_DEGREES_TO_RADIANS) * light->getRange(), light->getRange()); + } else { + throw std::domain_error("Unsupported SGLight::Type"); } + osg::ShapeDrawable *debug_drawable = new osg::ShapeDrawable(debug_shape); debug_drawable->setColor(osg::Vec4(1.0, 0.0, 0.0, 1.0)); osg::Geode *debug_geode = new osg::Geode; @@ -125,14 +137,14 @@ SGLight::appendLight(const SGPropertyNode *configNode, debug_switch->addChild(debug_geode); simgear::getPropertyRoot()->getNode("/sim/debug/show-light-volumes", true)-> addChangeListener(new SGLightDebugListener(debug_switch), true); - group->addChild(debug_switch); + align->addChild(debug_switch); if ((p = configNode->getNode("name")) != NULL) - group->setName(p->getStringValue()); + align->setName(p->getStringValue()); else - group->setName("light"); + align->setName("light"); - return group; + return align; } SGLight::SGLight() : diff --git a/simgear/scene/model/SGPickAnimation.cxx b/simgear/scene/model/SGPickAnimation.cxx index 9b9f5249..9f2a9eac 100644 --- a/simgear/scene/model/SGPickAnimation.cxx +++ b/simgear/scene/model/SGPickAnimation.cxx @@ -615,9 +615,8 @@ public: return false; } - virtual void buttonReleased( int keyModState, - const osgGA::GUIEventAdapter&, - const Info* ) + void buttonReleased( int keyModState, const osgGA::GUIEventAdapter&, + const Info* ) override { if (!_condition || _condition->test()) { @@ -641,8 +640,7 @@ public: return _dragDirection; } - virtual void mouseMoved( const osgGA::GUIEventAdapter& ea, - const Info* ) + void mouseMoved( const osgGA::GUIEventAdapter& ea, const Info* ) override { if (!_condition || _condition->test()) { _mousePos = eventToWindowCoords(ea); @@ -675,7 +673,7 @@ public: } } - virtual void update(double dt, int keyModState) + void update(double dt, int keyModState) override { if (_hasDragged) { return; @@ -688,8 +686,7 @@ public: } // of repeat iteration } - virtual bool hover( const osg::Vec2d& windowPos, - const Info& ) + bool hover( const osg::Vec2d& windowPos, const Info& ) override { if (!_condition || _condition->test()) { @@ -711,7 +708,7 @@ public: _cursorName = aName; } - virtual std::string getCursor() const + std::string getCursor() const override { return _cursorName; } private: diff --git a/simgear/scene/sky/newcloud.cxx b/simgear/scene/sky/newcloud.cxx index 6791b370..8e9eb714 100644 --- a/simgear/scene/sky/newcloud.cxx +++ b/simgear/scene/sky/newcloud.cxx @@ -256,7 +256,7 @@ osg::ref_ptr SGNewCloud::genCloud() { geode->addDrawable(sg); geode->setName("3D cloud"); geode->setEffect(effect.get()); - geode->setNodeMask( ~simgear::MODELLIGHT_BIT ); + geode->setNodeMask( ~(simgear::CASTSHADOW_BIT | simgear::MODELLIGHT_BIT) ); return geode; } diff --git a/simgear/scene/tgdb/ReaderWriterSTG.cxx b/simgear/scene/tgdb/ReaderWriterSTG.cxx index a417df45..73fc9da0 100644 --- a/simgear/scene/tgdb/ReaderWriterSTG.cxx +++ b/simgear/scene/tgdb/ReaderWriterSTG.cxx @@ -95,6 +95,13 @@ static SGBucket bucketIndexFromFileName(const std::string& fileName) return SGBucket(index); } +/** + * callback per STG token, with access synced by a lock. + */ +using TokenCallbackMap = std::map; +static TokenCallbackMap globalStgObjectCallbacks = {}; +static OpenThreads::Mutex globalStgObjectCallbackLock; + struct ReaderWriterSTG::_ModelBin { struct _Object { SGPath _errorLocation; @@ -536,8 +543,26 @@ struct ReaderWriterSTG::_ModelBin { _buildingListList.push_back(buildinglist); //SG_LOG(SG_TERRAIN, SG_ALERT, "Building list: " << buildinglist._filename << " " << buildinglist._material_name << " " << buildinglist._lon << " " << buildinglist._lat); } else { - SG_LOG( SG_TERRAIN, SG_ALERT, absoluteFileName - << ": Unknown token '" << token << "'" ); + // Check registered callback for token. Keep lock until callback completed to make sure it will not be + // executed after a thread successfully executed removeSTGObjectHandler() + { + OpenThreads::ScopedLock lock(globalStgObjectCallbackLock); + STGObjectCallback callback = globalStgObjectCallbacks[token]; + + if (callback != nullptr) { + _ObjectStatic obj; + // pitch and roll are not common, so passed in "restofline" only + in >> obj._lon >> obj._lat >> obj._elev >> obj._hdg; + string_list restofline; + std::string buf; + while (in >> buf) { + restofline.push_back(buf); + } + callback(token,name, SGGeod::fromDegM(obj._lon, obj._lat, obj._elev), obj._hdg,restofline); + } else { + SG_LOG( SG_TERRAIN, SG_ALERT, absoluteFileName << ": Unknown token '" << token << "'" ); + } + } } } } @@ -711,4 +736,16 @@ ReaderWriterSTG::readNode(const std::string& fileName, const osgDB::Options* opt return modelBin.load(bucket, options); } + +void ReaderWriterSTG::setSTGObjectHandler(const std::string &token, STGObjectCallback callback) +{ + OpenThreads::ScopedLock lock(globalStgObjectCallbackLock); + globalStgObjectCallbacks[token] = callback; +} + +void ReaderWriterSTG::removeSTGObjectHandler(const std::string &token, STGObjectCallback callback) +{ + OpenThreads::ScopedLock lock(globalStgObjectCallbackLock); + globalStgObjectCallbacks.erase(token); +} } diff --git a/simgear/scene/tgdb/ReaderWriterSTG.hxx b/simgear/scene/tgdb/ReaderWriterSTG.hxx index 8e19578e..74b6c77b 100644 --- a/simgear/scene/tgdb/ReaderWriterSTG.hxx +++ b/simgear/scene/tgdb/ReaderWriterSTG.hxx @@ -22,8 +22,12 @@ #ifndef _READERWRITERSTG_HXX #define _READERWRITERSTG_HXX -#include +#include +#include +#include + +class SGGeod; class SGBucket; namespace simgear { @@ -38,6 +42,11 @@ public: virtual ReadResult readNode(const std::string&, const osgDB::Options*) const; + //pitch and roll are not common, so passed in "restofline" only + using STGObjectCallback = std::function; + //add/remove a callback that is invoked for unknown STG token + static void setSTGObjectHandler(const std::string &token, STGObjectCallback callback); + static void removeSTGObjectHandler(const std::string &token, STGObjectCallback callback); private: struct _ModelBin; }; diff --git a/simgear/scene/tgdb/SGBuildingBin.cxx b/simgear/scene/tgdb/SGBuildingBin.cxx index 10158a5d..2a56f309 100644 --- a/simgear/scene/tgdb/SGBuildingBin.cxx +++ b/simgear/scene/tgdb/SGBuildingBin.cxx @@ -55,7 +55,6 @@ #include "ShaderGeometry.hxx" #include "SGBuildingBin.hxx" - using namespace osg; namespace simgear @@ -67,44 +66,339 @@ static BuildingStateSetMap statesetmap; typedef std::map > EffectMap; static EffectMap buildingEffectMap; -// Building instance scheme: -// vertex - local position of vertices, with 0,0,0 being the center front. -// fog coord - rotation -// color - xyz of tree quad origin, replicated 4 times. - -struct BuildingBoundingBoxCallback : public Drawable::ComputeBoundingBoxCallback +// Helper classes for creating the quad tree +struct MakeBuildingLeaf { - BuildingBoundingBoxCallback() {} - BuildingBoundingBoxCallback(const BuildingBoundingBoxCallback&, const CopyOp&) {} - META_Object(simgear, BuildingBoundingBoxCallback); - virtual BoundingBox computeBound(const Drawable&) const; + MakeBuildingLeaf(float range, Effect* effect, bool fade) : + _range(range), _effect(effect), _fade_out(fade) {} + + MakeBuildingLeaf(const MakeBuildingLeaf& rhs) : + _range(rhs._range), _effect(rhs._effect), _fade_out(rhs._fade_out) + {} + + LOD* operator() () const + { + osg::Vec3Array* v = new osg::Vec3Array; + osg::Vec2Array* t = new osg::Vec2Array; + osg::Vec3Array* n = new osg::Vec3Array; + osg::Vec4Array* c = new osg::Vec4Array; + // Color array is used to identify the different building faces by the + // vertex shader for texture mapping: + // (front, roof, roof top vertex, side) + + v->reserve(52); + t->reserve(52); + n->reserve(52); + c->reserve(52); + + // Now create an OSG Geometry based on the Building + // 0,0,0 is the bottom center of the front + // face, e.g. where the front door would be + + // BASEMENT + // This extends 10m below the main section + // Front face + v->push_back( osg::Vec3( 0.0, -0.5, -1.0) ); // bottom right + v->push_back( osg::Vec3( 0.0, 0.5, -1.0) ); // bottom left + v->push_back( osg::Vec3( 0.0, 0.5, 0.0) ); // top left + v->push_back( osg::Vec3( 0.0, -0.5, 0.0) ); // top right + + for (int i=0; i<4; ++i) { + n->push_back( osg::Vec3(1, 0, 0) ); // normal + c->push_back( osg::Vec4(1, 0, 0, 0) ); // color - used to differentiate wall from roof + t->push_back( osg::Vec2( 0.0, 0.0) ); + } + + // Left face + v->push_back( osg::Vec3( -1.0, -0.5, -1.0) ); // bottom right + v->push_back( osg::Vec3( 0.0, -0.5, -1.0) ); // bottom left + v->push_back( osg::Vec3( 0.0, -0.5, 0.0) ); // top left + v->push_back( osg::Vec3( -1.0, -0.5, 0.0) ); // top right + + for (int i=0; i<4; ++i) { + n->push_back( osg::Vec3(0, -1, 0) ); // normal + c->push_back( osg::Vec4(1, 0, 0, 0) ); // color - used to differentiate wall from roof + t->push_back( osg::Vec2( 0.0, 0.0) ); + } + + // Back face + v->push_back( osg::Vec3( -1.0, 0.5, -1.0) ); // bottom right + v->push_back( osg::Vec3( -1.0, -0.5, -1.0) ); // bottom left + v->push_back( osg::Vec3( -1.0, -0.5, 0.0) ); // top left + v->push_back( osg::Vec3( -1.0, 0.5, 0.0) ); // top right + + for (int i=0; i<4; ++i) { + n->push_back( osg::Vec3(-1, 0, 0) ); // normal + c->push_back( osg::Vec4(1, 0, 0, 0) ); // color - used to differentiate wall from roof + t->push_back( osg::Vec2( 0.0, 0.0) ); + } + + // Right face + v->push_back( osg::Vec3( 0.0, 0.5, -1.0) ); // bottom right + v->push_back( osg::Vec3( -1.0, 0.5, -1.0) ); // bottom left + v->push_back( osg::Vec3( -1.0, 0.5, 0.0) ); // top left + v->push_back( osg::Vec3( 0.0, 0.5, 0.0) ); // top right + + for (int i=0; i<4; ++i) { + n->push_back( osg::Vec3(0, 1, 0) ); // normal + c->push_back( osg::Vec4(1, 0, 0, 0) ); // color - used to differentiate wall from roof + t->push_back( osg::Vec2( 0.0, 0.0) ); + } + + // MAIN BODY + // Front face + v->push_back( osg::Vec3( 0.0, -0.5, 0.0) ); // bottom right + v->push_back( osg::Vec3( 0.0, 0.5, 0.0) ); // bottom left + v->push_back( osg::Vec3( 0.0, 0.5, 1.0) ); // top left + v->push_back( osg::Vec3( 0.0, -0.5, 1.0) ); // top right + + t->push_back( osg::Vec2( 1.0, 0.0) ); // bottom right + t->push_back( osg::Vec2( 0.0, 0.0) ); // bottom left + t->push_back( osg::Vec2( 0.0, 1.0) ); // top left + t->push_back( osg::Vec2( 1.0, 1.0) ); // top right + + for (int i=0; i<4; ++i) { + n->push_back( osg::Vec3(1, 0, 0) ); // normal + c->push_back( osg::Vec4(1, 0, 0, 0) ); // color - used to differentiate wall from roof + } + + // Left face + v->push_back( osg::Vec3( -1.0, -0.5, 0.0) ); // bottom right + v->push_back( osg::Vec3( 0.0, -0.5, 0.0) ); // bottom left + v->push_back( osg::Vec3( 0.0, -0.5, 1.0) ); // top left + v->push_back( osg::Vec3( -1.0, -0.5, 1.0) ); // top right + + t->push_back( osg::Vec2( 0.0, 0.0) ); // bottom right + t->push_back( osg::Vec2( 1.0, 0.0) ); // bottom left + t->push_back( osg::Vec2( 1.0, 1.0) ); // top left + t->push_back( osg::Vec2( 0.0, 1.0) ); // top right + + for (int i=0; i<4; ++i) { + n->push_back( osg::Vec3(0, -1, 0) ); // normal + c->push_back( osg::Vec4(0, 0, 0, 1) ); // color - used to differentiate wall from roof + } + + // Back face + v->push_back( osg::Vec3( -1.0, 0.5, 0.0) ); // bottom right + v->push_back( osg::Vec3( -1.0, -0.5, 0.0) ); // bottom left + v->push_back( osg::Vec3( -1.0, -0.5, 1.0) ); // top left + v->push_back( osg::Vec3( -1.0, 0.5, 1.0) ); // top right + + t->push_back( osg::Vec2( 1.0, 0.0) ); // bottom right + t->push_back( osg::Vec2( 0.0, 0.0) ); // bottom left + t->push_back( osg::Vec2( 0.0, 1.0 ) ); // top left + t->push_back( osg::Vec2( 1.0, 1.0 ) ); // top right + + for (int i=0; i<4; ++i) { + n->push_back( osg::Vec3(-1, 0, 0) ); // normal + c->push_back( osg::Vec4(1, 0, 0, 0) ); // color - used to differentiate wall from roof + } + + // Right face + v->push_back( osg::Vec3( 0.0, 0.5, 0.0) ); // bottom right + v->push_back( osg::Vec3( -1.0, 0.5, 0.0) ); // bottom left + v->push_back( osg::Vec3( -1.0, 0.5, 1.0) ); // top left + v->push_back( osg::Vec3( 0.0, 0.5, 1.0) ); // top right + + t->push_back( osg::Vec2( 0.0, 0.0) ); // bottom right + t->push_back( osg::Vec2( 1.0, 0.0) ); // bottom left + t->push_back( osg::Vec2( 1.0, 1.0 ) ); // top left + t->push_back( osg::Vec2( 0.0, 1.0 ) ); // top right + + for (int i=0; i<4; ++i) { + n->push_back( osg::Vec3(0, 1, 0) ); // normal + c->push_back( osg::Vec4(0, 0, 0, 1) ); // color - used to differentiate wall from roof + } + + // ROOF 1 - built as a block. The shader will deform it to the correct shape. + // Front face + v->push_back( osg::Vec3( 0.0, -0.5, 1.0) ); // bottom right + v->push_back( osg::Vec3( 0.0, 0.5, 1.0) ); // bottom left + v->push_back( osg::Vec3( 0.0, 0.5, 1.0) ); // top left + v->push_back( osg::Vec3( 0.0, -0.5, 1.0) ); // top right + + t->push_back( osg::Vec2( -1.0, 0.0) ); // bottom right + t->push_back( osg::Vec2( 0.0, 0.0) ); // bottom left + t->push_back( osg::Vec2( 0.0, 1.0) ); // top left + t->push_back( osg::Vec2( -1.0, 1.0) ); // top right + + c->push_back( osg::Vec4(0, 1, 0, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 0, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 1, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 1, 0) ); // color - used to differentiate wall from roof + + for (int i=0; i<4; ++i) { + n->push_back( osg::Vec3(0.707, 0, 0.707) ); // normal + } + + // Left face + v->push_back( osg::Vec3( -1.0, -0.5, 1.0) ); // bottom right + v->push_back( osg::Vec3( 0.0, -0.5, 1.0) ); // bottom left + v->push_back( osg::Vec3( 0.0, -0.5, 1.0) ); // top left + v->push_back( osg::Vec3( -1.0, -0.5, 1.0) ); // top right + + t->push_back( osg::Vec2( 0.0, 0.0) ); // bottom right + t->push_back( osg::Vec2( -1.0, 0.0) ); // bottom left + t->push_back( osg::Vec2( -1.0, 1.0) ); // top left + t->push_back( osg::Vec2( 0.0, 1.0) ); // top right + + c->push_back( osg::Vec4(0, 1, 0, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 0, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 1, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 1, 0) ); // color - used to differentiate wall from roof + + for (int i=0; i<4; ++i) { + n->push_back( osg::Vec3(0, -0.707, 0.707) ); // normal + } + + // Back face + v->push_back( osg::Vec3( -1.0, 0.5, 1.0) ); // bottom right + v->push_back( osg::Vec3( -1.0, -0.5, 1.0) ); // bottom left + v->push_back( osg::Vec3( -1.0, -0.5, 1.0) ); // top left + v->push_back( osg::Vec3( -1.0, 0.5, 1.0) ); // top right + + t->push_back( osg::Vec2( -1.0, 0.0) ); // bottom right + t->push_back( osg::Vec2( 0.0, 0.0) ); // bottom left + t->push_back( osg::Vec2( 0.0, 1.0 ) ); // top left + t->push_back( osg::Vec2( -1.0, 1.0 ) ); // top right + + c->push_back( osg::Vec4(0, 1, 0, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 0, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 1, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 1, 0) ); // color - used to differentiate wall from roof + + for (int i=0; i<4; ++i) { + n->push_back( osg::Vec3(-0.707, 0, 0.707) ); // normal + } + + // Right face + v->push_back( osg::Vec3( 0.0, 0.5, 1.0) ); // bottom right + v->push_back( osg::Vec3( -1.0, 0.5, 1.0) ); // bottom left + v->push_back( osg::Vec3( -1.0, 0.5, 1.0) ); // top left + v->push_back( osg::Vec3( 0.0, 0.5, 1.0) ); // top right + + t->push_back( osg::Vec2( 0.0, 0.0) ); // bottom right + t->push_back( osg::Vec2( -1.0, 0.0) ); // bottom left + t->push_back( osg::Vec2( -1.0, 1.0 ) ); // top left + t->push_back( osg::Vec2( 0.0, 1.0 ) ); // top right + + c->push_back( osg::Vec4(0, 1, 0, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 0, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 1, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 1, 0) ); // color - used to differentiate wall from roof + + for (int i=0; i<4; ++i) { + n->push_back( osg::Vec3(0, 0.707, 0.707) ); // normal + } + + // Top face + v->push_back( osg::Vec3( 0.0, -0.5, 1.0) ); // bottom right + v->push_back( osg::Vec3( 0.0, 0.5, 1.0) ); // bottom left + v->push_back( osg::Vec3( -1.0, 0.5, 1.0) ); // top left + v->push_back( osg::Vec3( -1.0, -0.5, 1.0) ); // top right + + t->push_back( osg::Vec2( -1.0, 0.0) ); // bottom right + t->push_back( osg::Vec2( 0.0, 0.0) ); // bottom left + t->push_back( osg::Vec2( 0.0, 1.0) ); // top left + t->push_back( osg::Vec2( -1.0, 1.0) ); // top right + + c->push_back( osg::Vec4(0, 1, 1, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 1, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 1, 0) ); // color - used to differentiate wall from roof + c->push_back( osg::Vec4(0, 1, 1, 0) ); // color - used to differentiate wall from roof + + for (int i=0; i<4; ++i) { + n->push_back( osg::Vec3(0, 0, -1.0) ); // normal + } + + assert(v->size() == 52); + assert(t->size() == 52); + assert(c->size() == 52); + assert(n->size() == 52); + + Geometry* geom = new Geometry; + static int buildingCounter = 0; + geom->setName("BuildingGeometry_" + std::to_string(buildingCounter++)); + geom->setVertexArray(v); + geom->setTexCoordArray(0, t, Array::BIND_PER_VERTEX); + geom->setNormalArray(n, Array::BIND_PER_VERTEX); + geom->setColorArray(c, Array::BIND_PER_VERTEX); + geom->setUseDisplayList( false ); + geom->setUseVertexBufferObjects( true ); + geom->setComputeBoundingBoxCallback(new BuildingBoundingBoxCallback); + + geom->setVertexAttribArray(BUILDING_POSITION_ATTR, new osg::Vec3Array, Array::BIND_PER_VERTEX); + geom->setVertexAttribArray(BUILDING_SCALE_ATTR, new osg::Vec3Array, Array::BIND_PER_VERTEX); + geom->setVertexAttribArray(BUILDING_ROT_PITCH_TEX0X_ATTR, new osg::Vec3Array, Array::BIND_PER_VERTEX); + geom->setVertexAttribArray(BUILDING_TEX0Y_TEX1X_TEX1Y_ATTR, new osg::Vec3Array, Array::BIND_PER_VERTEX); + geom->setVertexAttribArray(BUILDING_RTEX0X_RTEX0Y_ATTR, new osg::Vec3Array, Array::BIND_PER_VERTEX); + geom->setVertexAttribArray(BUILDING_ROFFTOPSCALE_ATTR, new osg::Vec3Array, Array::BIND_PER_VERTEX); + + //geom->addPrimitiveSet( new osg::DrawArrays( GL_QUADS, 0, 48, 0) ); + geom->addPrimitiveSet( new osg::DrawArrays( GL_QUADS, 0, 52, 0) ); + + EffectGeode* geode = new EffectGeode; + geode->addDrawable(geom); + geode->setEffect(_effect.get()); + + StateSet* ss = geode->getOrCreateStateSet(); + ss->setAttributeAndModes(new osg::VertexAttribDivisor(BUILDING_POSITION_ATTR, 1)); + ss->setAttributeAndModes(new osg::VertexAttribDivisor(BUILDING_SCALE_ATTR, 1)); + ss->setAttributeAndModes(new osg::VertexAttribDivisor(BUILDING_ROT_PITCH_TEX0X_ATTR, 1)); + ss->setAttributeAndModes(new osg::VertexAttribDivisor(BUILDING_TEX0Y_TEX1X_TEX1Y_ATTR, 1)); + ss->setAttributeAndModes(new osg::VertexAttribDivisor(BUILDING_RTEX0X_RTEX0Y_ATTR, 1)); + ss->setAttributeAndModes(new osg::VertexAttribDivisor(BUILDING_ROFFTOPSCALE_ATTR, 1)); + + LOD* result = new LOD; + result->addChild(geode, 0, _range); + return result; + } + + float _range; + ref_ptr _effect; + bool _fade_out; }; -BoundingBox -BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const +struct AddBuildingLeafObject { - BoundingBox bb; - const Geometry* geom = static_cast(&drawable); - const Vec3Array* v = static_cast(geom->getVertexArray()); - const Vec4Array* pos = static_cast(geom->getColorArray()); + void operator() (LOD* lod, const SGBuildingBin::BuildingInstance building) const + { + //Geode* geode = static_cast(lod->getChild(int(building.position.x() * 10.0f) % lod->getNumChildren())); + Geode* geode = static_cast(lod->getChild(0)); - Geometry::PrimitiveSetList primSets = geom->getPrimitiveSetList(); - for (Geometry::PrimitiveSetList::const_iterator psitr = primSets.begin(), psend = primSets.end(); - psitr != psend; - ++psitr) { - DrawArrays* da = static_cast(psitr->get()); - GLint psFirst = da->getFirst(); - GLint psEndVert = psFirst + da->getCount(); - for (GLint i = psFirst;i < psEndVert; ++i) { - Vec3 pt = (*v)[i]; - Matrixd trnsfrm = Matrixd::rotate(- M_PI * 2 * (*pos)[i].a(), Vec3(0.0f, 0.0f, 1.0f)); - pt = pt * trnsfrm; - pt += Vec3((*pos)[i].x(), (*pos)[i].y(), (*pos)[i].z()); - bb.expandBy(pt); - } + Geometry* geom = static_cast(geode->getDrawable(0)); + + osg::Vec3Array* positions = static_cast (geom->getVertexAttribArray(BUILDING_POSITION_ATTR)); // (x,y,z) + osg::Vec3Array* scale = static_cast (geom->getVertexAttribArray(BUILDING_SCALE_ATTR)); // (width, depth, height) + osg::Vec3Array* rot = static_cast (geom->getVertexAttribArray(BUILDING_ROT_PITCH_TEX0X_ATTR)); // (rotation, pitch height, wall texture x0) + osg::Vec3Array* tex = static_cast (geom->getVertexAttribArray(BUILDING_TEX0Y_TEX1X_TEX1Y_ATTR)); // (wall texture y0, front/roof texture x1, front/side/roof texture y1) + osg::Vec3Array* rtex = static_cast (geom->getVertexAttribArray(BUILDING_RTEX0X_RTEX0Y_ATTR)); // (roof texture x0, roof texture y0, side texture x1) + osg::Vec3Array* rooftops = static_cast (geom->getVertexAttribArray(BUILDING_ROFFTOPSCALE_ATTR)); // (rooftop scale x, rooftop scale y, unused) + + positions->push_back(building.position); + // Depth is the x-axis, width is the y-axis + scale->push_back(osg::Vec3f(building.depth, building.width, building.height)); + rot->push_back(osg::Vec3f(building.rotation, building.pitch_height, building.walltex0.x())); + tex->push_back(osg::Vec3f(building.walltex0.y(), building.tex1.x(), building.tex1.y())); + rtex->push_back(osg::Vec3f(building.rooftex0.x(), building.rooftex0.y(), building.tex1.z())); + rooftops->push_back(osg::Vec3f(building.rooftop_scale.x(), building.rooftop_scale.y(), 0.0f)); + + DrawArrays* primSet = static_cast(geom->getPrimitiveSet(0)); + primSet->setNumInstances(positions->size()); } - return bb; -} +}; + +struct GetBuildingCoord +{ + Vec3 operator() (const SGBuildingBin::BuildingInstance& building) const + { + return building.position; + } +}; + +typedef QuadTreeBuilder BuildingGeometryQuadtree; + // Set up a BuildingBin from a file containing a list of individual building // positions. @@ -118,12 +412,12 @@ BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const sg_gzifstream stream(absoluteFileName); if (!stream.is_open()) { - SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to open " << absoluteFileName << " does not exist."); + SG_LOG(SG_TERRAIN, SG_ALERT, "Unable to open " << absoluteFileName); return; } while (!stream.eof()) { - // read a line. Each line defines a single builing position, and may have + // read a line. Each line defines a single building position, and may have // a comment, starting with # std::string line; std::getline(stream, line); @@ -136,24 +430,45 @@ BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const // and process further std::stringstream in(line); - // Line format is X Y Z R T + // Line format is X Y Z R B W D H P S O F WT RT // where: - // X,Y,Z are the cartesian coordinates of the bottom SW corner of the building. +X is East, +Y is North - // R is the building rotation in degrees centered on the SW corner - // T is the building type [0, 1, 2] for SMALL, MEDIUM, LARGE - float x, y, z, r; - int t; - in >> x >> y >> z >> r >> t; + // X,Y,Z are the cartesian coordinates of the center of the front face. +X is East, +Y is North + // R is the building rotation in degrees centered on the middle of the front face. + // B is the building type [0, 1, 2] for SMALL, MEDIUM, LARGE + // W is the building width in meters + // D is the building depth in meters + // H is the building height in meters, excluding any pitched roof + // P is the pitch height in meters. 0 for a flat roof + // S is the roof shape (Currently the following values are valid: 0, 2, 4, 5) : + // 0=flat 1=skillion 2=gabled 3=half-hipped 4=hipped 5=pyramidal 6=gambrel + // 7=mansard 8=dome 9=onion 10=round 11=saltbox + // O is the roof ridge orientation : + // 0 = parallel to the front face of the building + // 1 = orthogonal to the front face of the building + // F is the number of floors (integer) + // WT is the texture index to use (integer) for walls. Buildings with the same WT value will have the same wall texture assigned. There are 6 small, 6 medium and 4 large textures. + // RT is the texture index to use (integer) for roofs. Buildings with the same RT value will have the same roof texture assigned. There are 6 small, 6 medium and 4 large textures. + float x, y, z, r, w, d, h, p; + int b, s, o, f, wt, rt; + in >> x >> y >> z >> r >> b >> w >> d >> h >> p >> s >> o >> f >> wt >> rt; - //SG_LOG(SG_TERRAIN, SG_ALERT, "Building entry " << x << " " << y << " " << z << " " << t ); - SGVec3f p = SGVec3f(x,y,z); + //SG_LOG(SG_TERRAIN, SG_ALERT, "Building entry " << x << " " << y << " " << z << " " << b ); + SGVec3f loc = SGVec3f(x,y,z); BuildingType type = BuildingType::SMALL; - if (t == 1) type = BuildingType::MEDIUM; - if (t == 2) type = BuildingType::LARGE; + if (b == 1) type = BuildingType::MEDIUM; + if (b == 2) type = BuildingType::LARGE; // Rotation is in the file as degrees, but in the datastructure normalized // to 0.0 - 1.0 - insert(p, (float) (r / 360.0f), type); + float rot = (float) (r / 360.0f); + + if (w == 0.0f) { + // If width is not defined, then we assume we don't have a full set of + // data for the buildings so just use the random building definitions + insert(loc, rot, type); + } else { + insert(loc, rot, type, w, d, h, p, f, s, o, wt, rt); + } } stream.close(); @@ -161,594 +476,240 @@ BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const // Set up the building set based on the material definitions SGBuildingBin::SGBuildingBin(const SGMaterial *mat, bool useVBOs) { - material_name = new std::string(mat->get_names()[0]); SG_LOG(SG_TERRAIN, SG_DEBUG, "Building material " << material_name); + material = mat; texture = new std::string(mat->get_building_texture()); lightMap = new std::string(mat->get_building_lightmap()); + buildingRange = mat->get_building_range(); SG_LOG(SG_TERRAIN, SG_DEBUG, "Building texture " << texture); + } + + SGBuildingBin::~SGBuildingBin() { + buildingLocations.clear(); + } + + // Generate a building specifying the exact position, dimensions and texture index. + void SGBuildingBin::insert(SGVec3f p, float r, BuildingType buildingtype, float width, float depth, float height, float pitch_height, int floors, int roof_shape, int roof_orientation, int wall_tex_index, int roof_tex_index) { + + // The 2048x2048 texture is split into 64x32 blocks. So there are 64 on + // the x-axis and 128 on the y-axis. + // The leftmost 32 are used for the sides of the building, and the rightmost + // 32 for the roof. + const float BUILDING_TEXTURE_BLOCK_HEIGHT = 32.0f / 2048.0f; // The height of a single block within the random building texture + const float BUILDING_TEXTURE_BLOCK_WIDTH = 64.0f / 2048.0f; // The width of a single block within the random building texture + Vec2f wall_tex0, roof_tex0; + Vec3f tex1; + + if (buildingtype == SGBuildingBin::SMALL) { + // SMALL BUILDINGS + // Maximum texture height is 3 stories. + // Small buildings are represented on the bottom 18 rows + // Each block is 5m wide and 3m high. + int wall_row = wall_tex_index % 6; + int roof_row = roof_tex_index % 6; + float wall_x0 = 0.0f; + float wall_y0 = (float) wall_row * 3.0f * BUILDING_TEXTURE_BLOCK_HEIGHT; + float roof_x0 = 0.0f; + float roof_y0 = (float) roof_row * 3.0f * BUILDING_TEXTURE_BLOCK_HEIGHT; + float wall_roof_x1 = min(0.5f, std::round(width / 5.0f) * BUILDING_TEXTURE_BLOCK_WIDTH); + float side_x1 = min(0.5f, std::round(depth / 5.0f) * BUILDING_TEXTURE_BLOCK_WIDTH); + float y1 = (float) (min(3, floors)) * BUILDING_TEXTURE_BLOCK_HEIGHT; + + // Checks + if ((wall_x0 + wall_roof_x1 > 0.5f) || + (wall_y0 + y1 > (6.0f * 3.0f * BUILDING_TEXTURE_BLOCK_HEIGHT))) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Small building texture coordinates out of bounds offset (" << wall_x0 << ", " << wall_y0 << ") gain (" << wall_roof_x1 << ", " << y1 << ")"); + } + + + wall_tex0 = Vec2f(wall_x0, wall_y0); + roof_tex0 = Vec2f(roof_x0, roof_y0); + tex1 = Vec3f(wall_roof_x1, y1, side_x1); + } else if (buildingtype == SGBuildingBin::MEDIUM) { + // MEDIUM BUILDING + // Maximum texture height is 8 stories. + // Medium buildings are arranged on the texture in a 2x3 pattern. + // For a medium building, each block is 10m wide and 3m high. + int wall_column = wall_tex_index % 2; + int wall_row = wall_tex_index % 3; + int roof_column = roof_tex_index % 2; + int roof_row = roof_tex_index % 3; + + float wall_x0 = wall_column * 0.25f; + // Counting from the bottom, we have 6 rows of small buildings, each 3 blocks high + float wall_y0 = (6.0f * 3.0f + wall_row * 8.0f) * BUILDING_TEXTURE_BLOCK_HEIGHT; + + float roof_x0 = roof_column * 0.25f; + // Counting from the bottom, we have 6 rows of small buildings, each 3 blocks high + float roof_y0 = (6.0f * 3.0f + roof_row * 8.0f) * BUILDING_TEXTURE_BLOCK_HEIGHT; + + float wall_roof_x1 = min(0.25f, std::ceil(width / 10.0f) * BUILDING_TEXTURE_BLOCK_WIDTH); + float side_x1 = min(0.5f, std::round(depth / 10.0f) * BUILDING_TEXTURE_BLOCK_WIDTH); + float y1 = (float) (min(8, floors)) * BUILDING_TEXTURE_BLOCK_HEIGHT; + + if ((wall_x0 + wall_roof_x1 > 0.5f) || + (wall_y0 + y1 < (6.0f * 3.0f * BUILDING_TEXTURE_BLOCK_HEIGHT)) || + (wall_y0 + y1 > ((6.0f * 3.0f + 3.0f * 8.0f) * BUILDING_TEXTURE_BLOCK_HEIGHT))) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Medium building texture coordinates out of bounds offset (" << wall_x0 << ", " << wall_y0 << ") gain (" << wall_roof_x1 << ", " << y1 << ")"); + } + + + wall_tex0 = Vec2f(wall_x0, wall_y0); + roof_tex0 = Vec2f(roof_x0, roof_y0); + tex1 = Vec3f(wall_roof_x1, y1, side_x1); + } else { + // LARGE BUILDING + // Maximum texture height is 22 stories. + // Large buildings are arranged in a 4x1 pattern + // Each block is 20m wide and 3m high. + int wall_column = wall_tex_index % 4; + int roof_column = roof_tex_index % 4; + // Counting from the bottom we have 6 rows of small buildings (3 blocks high), + // then 3 rows of medium buildings (8 blocks high). Then the large building texture + float wall_x0 = wall_column * 0.125f; + float wall_y0 = (6.0f * 3.0f + 3.0f * 8.0f) * BUILDING_TEXTURE_BLOCK_HEIGHT; + float roof_x0 = roof_column * 0.125f; + float roof_y0 = (6.0f * 3.0f + 3.0f * 8.0f) * BUILDING_TEXTURE_BLOCK_HEIGHT; + float wall_roof_x1 = min(0.125f, std::ceil(width / 20.0f) * BUILDING_TEXTURE_BLOCK_WIDTH); + float side_x1 = min(0.5f, std::round(depth / 20.0f) * BUILDING_TEXTURE_BLOCK_WIDTH); + float y1 = (float) min(22, floors) * BUILDING_TEXTURE_BLOCK_HEIGHT; + + if ((wall_x0 + wall_roof_x1 > 0.5f) || + (wall_y0 + y1 < ((6.0f * 3.0f + 3.0f * 8.0f) * BUILDING_TEXTURE_BLOCK_HEIGHT)) || + (wall_y0 + y1 > 1.0) ) { + SG_LOG(SG_TERRAIN, SG_ALERT, "Large building texture coordinates out of bounds offset (" << wall_x0 << ", " << wall_y0 << ") gain (" << wall_roof_x1 << ", " << y1 << ")"); + } + + wall_tex0 = Vec2f(wall_x0, wall_y0); + roof_tex0 = Vec2f(roof_x0, roof_y0); + tex1 = Vec3f(wall_roof_x1, y1, side_x1); + } + + // Build a scaling factor in the x,y axes for the top of the roof. This allows us to create gabled, hipped, pyramidal roofs. + Vec2f rooftop_scale = Vec2f(1.0f, 1.0f); // Default of a flat roof. + + if (pitch_height > 0.0f) + { + // Roof with some pitch height + + if ((roof_shape == 2) || (roof_shape == 6) || (roof_shape == 10) || (roof_shape == 11)) { + // Gabled, gambrel, round, saltbox + if (roof_orientation == 0) rooftop_scale = Vec2f(0.0f, 1.0f); + if (roof_orientation == 1) rooftop_scale = Vec2f(1.0f, 0.0f); + } + + if ((roof_shape == 3) || (roof_shape == 4) || (roof_shape == 7)) { + // Hipped, half-hipped, mansard + // The pitch height expressed as a fraction of the building width/depth such that the hipped + // roof has a pitch of around 45 degrees. A minimum of 0.5 so that they have at least some ridge. + if (roof_orientation == 0) rooftop_scale = Vec2f(0.0f, max(0.5f,(depth - 2*pitch_height) / width)); + if (roof_orientation == 1) rooftop_scale = Vec2f(max(0.5f,(width - 2*pitch_height) / width), 0.0f); + } + + // Pyramidal, dome, onion + if ((roof_shape == 5) || (roof_shape == 8) || (roof_shape == 9)) rooftop_scale = Vec2f(0.0f, 0.0f); + } + + buildingLocations.push_back(BuildingInstance(toOsg(p), width, depth, height, pitch_height, r, wall_tex0, roof_tex0, tex1, rooftop_scale)); + } + + + // Generate a building of a given type at a specified position, using the random building material definition to determine the dimensions and texture index. + void SGBuildingBin::insert(SGVec3f p, float r, BuildingType buildingtype) { + + float width, depth, height, pitch_height; + int floors; + int roof_shape; + int roof_orientation; // Generate a random seed for the building generation. mt seed; - mt_init(&seed, unsigned(123)); + mt_init(&seed, unsigned(p.x() + p.y() + p.z())); - smallSharedGeometry = new osg::Geometry(); - mediumSharedGeometry = new osg::Geometry(); - largeSharedGeometry = new osg::Geometry(); + if (buildingtype == SGBuildingBin::SMALL) { + // Small building + // Maximum number of floors is 3, and maximum width/depth is 192m. + width = material->get_building_small_min_width() + mt_rand(&seed) * mt_rand(&seed) * (material->get_building_small_max_width() - material->get_building_small_min_width()); + depth = material->get_building_small_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (material->get_building_small_max_depth() - material->get_building_small_min_depth()); + floors = SGMisc::round(material->get_building_small_min_floors() + mt_rand(&seed) * (material->get_building_small_max_floors() - material->get_building_small_min_floors())); + height = floors * (2.8 + mt_rand(&seed)); - smallBuildingMaxRadius = std::max(mat->get_building_small_max_depth() * 0.5, mat->get_building_small_max_width() * 0.5); - mediumBuildingMaxRadius = std::max(mat->get_building_medium_max_depth() * 0.5, mat->get_building_medium_max_width() * 0.5); - largeBuildingMaxRadius = std::max(mat->get_building_large_max_depth() * 0.5, mat->get_building_large_max_width() * 0.5); + // Small buildings are never deeper than they are wide. + if (depth > width) { depth = width; } - smallBuildingMaxDepth = mat->get_building_small_max_depth(); - mediumBuildingMaxDepth = mat->get_building_medium_max_depth(); - largeBuildingMaxDepth = mat->get_building_large_max_depth(); + pitch_height = (mt_rand(&seed) < material->get_building_small_pitch()) ? 3.0 : 0.0; - smallBuildingFraction = mat->get_building_small_fraction(); - mediumBuildingFraction = mat->get_building_medium_fraction(); + if (pitch_height == 0.0) { + roof_shape = 0; + roof_orientation = 0; + } else { + roof_shape = (int) (mt_rand(&seed) * 10.0); + roof_orientation = (int) std::round((float) mt_rand(&seed)); + } + } else if (buildingtype == SGBuildingBin::MEDIUM) { + // MEDIUM BUILDING + width = material->get_building_medium_min_width() + mt_rand(&seed) * mt_rand(&seed) * (material->get_building_medium_max_width() - material->get_building_medium_min_width()); + depth = material->get_building_medium_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (material->get_building_medium_max_depth() - material->get_building_medium_min_depth()); + floors = SGMisc::round(material->get_building_medium_min_floors() + mt_rand(&seed) * (material->get_building_medium_max_floors() - material->get_building_medium_min_floors())); + height = floors * (2.8 + mt_rand(&seed)); - buildingRange = mat->get_building_range(); - - SG_LOG(SG_TERRAIN, SG_DEBUG, "Building fractions " << smallBuildingFraction << " " << mediumBuildingFraction); - - - // TODO: Reverse this - otherwise we never get any large buildings! - BuildingType types[] = { SGBuildingBin::SMALL, SGBuildingBin::MEDIUM, SGBuildingBin::LARGE }; - BuildingList lists[] = { SGBuildingBin::smallBuildings, SGBuildingBin::mediumBuildings, SGBuildingBin::largeBuildings }; - ref_ptr geometries[] = { smallSharedGeometry, mediumSharedGeometry, largeSharedGeometry }; - - for (int bt=0; bt < 3; bt++) { - SGBuildingBin::BuildingType buildingtype = types[bt]; - ref_ptr sharedGeometry = geometries[bt]; - BuildingList buildings = lists[bt]; - - osg::ref_ptr v = new osg::Vec3Array; - osg::ref_ptr t = new osg::Vec2Array; - osg::ref_ptr n = new osg::Vec3Array; - - v->reserve(BUILDING_SET_SIZE * VERTICES_PER_BUILDING); - t->reserve(BUILDING_SET_SIZE * VERTICES_PER_BUILDING); - n->reserve(BUILDING_SET_SIZE * VERTICES_PER_BUILDING); - - sharedGeometry->setFogCoordBinding(osg::Geometry::BIND_PER_VERTEX); - sharedGeometry->setComputeBoundingBoxCallback(new BuildingBoundingBoxCallback); - sharedGeometry->setUseDisplayList(false); - sharedGeometry->setDataVariance(osg::Object::STATIC); - if (useVBOs) { - sharedGeometry->setUseVertexBufferObjects(true); + while ((height > width) && (floors > material->get_building_medium_min_floors())) { + // Ensure that medium buildings aren't taller than they are wide + floors--; + height = floors * (2.8 + mt_rand(&seed)); } - for (unsigned int j = 0; j < BUILDING_SET_SIZE; j++) { - float width; - float depth; - int floors; - float height; - bool pitched; - - if (buildingtype == SGBuildingBin::SMALL) { - // Small building - width = mat->get_building_small_min_width() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_small_max_width() - mat->get_building_small_min_width()); - depth = mat->get_building_small_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_small_max_depth() - mat->get_building_small_min_depth()); - floors = SGMisc::round(mat->get_building_small_min_floors() + mt_rand(&seed) * (mat->get_building_small_max_floors() - mat->get_building_small_min_floors())); - height = floors * (2.8 + mt_rand(&seed)); - - // Small buildings are never deeper than they are wide. - if (depth > width) { depth = width; } - - pitched = (mt_rand(&seed) < mat->get_building_small_pitch()); - } else if (buildingtype == SGBuildingBin::MEDIUM) { - width = mat->get_building_medium_min_width() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_medium_max_width() - mat->get_building_medium_min_width()); - depth = mat->get_building_medium_min_depth() + mt_rand(&seed) * mt_rand(&seed) * (mat->get_building_medium_max_depth() - mat->get_building_medium_min_depth()); - floors = SGMisc::round(mat->get_building_medium_min_floors() + mt_rand(&seed) * (mat->get_building_medium_max_floors() - mat->get_building_medium_min_floors())); - height = floors * (2.8 + mt_rand(&seed)); - - while ((height > width) && (floors > mat->get_building_medium_min_floors())) { - // Ensure that medium buildings aren't taller than they are wide - floors--; - height = floors * (2.8 + mt_rand(&seed)); - } - - pitched = (mt_rand(&seed) < mat->get_building_medium_pitch()); - } else { - width = mat->get_building_large_min_width() + mt_rand(&seed) * (mat->get_building_large_max_width() - mat->get_building_large_min_width()); - depth = mat->get_building_large_min_depth() + mt_rand(&seed) * (mat->get_building_large_max_depth() - mat->get_building_large_min_depth()); - floors = SGMisc::round(mat->get_building_large_min_floors() + mt_rand(&seed) * (mat->get_building_large_max_floors() - mat->get_building_large_min_floors())); - height = floors * (2.8 + mt_rand(&seed)); - pitched = (mt_rand(&seed) < mat->get_building_large_pitch()); - } - - Building building = Building(buildingtype, - width, - depth, - height, - floors, - pitched); - - buildings.push_back(building); - - // Now create an OSG Geometry based on the Building - float cw = 0.5f * building.width; - float cd = building.depth; - float ch = building.height; - - // 0,0,0 is the bottom center of the front - // face, e.g. where the front door would be - - // BASEMENT - // This exteds 10m below the main section - // Front face - v->push_back( osg::Vec3( 0, -cw, -10) ); // bottom right - v->push_back( osg::Vec3( 0, cw, -10) ); // bottom left - v->push_back( osg::Vec3( 0, cw, 0) ); // top left - v->push_back( osg::Vec3( 0, -cw, 0) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(1, 0, 0) ); // normal - - // Left face - v->push_back( osg::Vec3( -cd, -cw, -10) ); // bottom right - v->push_back( osg::Vec3( 0, -cw, -10) ); // bottom left - v->push_back( osg::Vec3( 0, -cw, 0) ); // top left - v->push_back( osg::Vec3( -cd, -cw, 0) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, -1, 0) ); // normal - - // Back face - v->push_back( osg::Vec3( -cd, cw, -10) ); // bottom right - v->push_back( osg::Vec3( -cd, -cw, -10) ); // bottom left - v->push_back( osg::Vec3( -cd, -cw, 0) ); // top left - v->push_back( osg::Vec3( -cd, cw, 0) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(-1, 0, 0) ); // normal - - // Right face - v->push_back( osg::Vec3( 0, cw, -10) ); // bottom right - v->push_back( osg::Vec3( -cd, cw, -10) ); // bottom left - v->push_back( osg::Vec3( -cd, cw, 0) ); // top left - v->push_back( osg::Vec3( 0, cw, 0) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, 1, 0) ); // normal - - // MAIN BODY - // Front face - v->push_back( osg::Vec3( 0, -cw, 0) ); // bottom right - v->push_back( osg::Vec3( 0, cw, 0) ); // bottom left - v->push_back( osg::Vec3( 0, cw, ch) ); // top left - v->push_back( osg::Vec3( 0, -cw, ch) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(1, 0, 0) ); // normal - - // Left face - v->push_back( osg::Vec3( -cd, -cw, 0) ); // bottom right - v->push_back( osg::Vec3( 0, -cw, 0) ); // bottom left - v->push_back( osg::Vec3( 0, -cw, ch) ); // top left - v->push_back( osg::Vec3( -cd, -cw, ch) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, -1, 0) ); // normal - - // Back face - v->push_back( osg::Vec3( -cd, cw, 0) ); // bottom right - v->push_back( osg::Vec3( -cd, -cw, 0) ); // bottom left - v->push_back( osg::Vec3( -cd, -cw, ch) ); // top left - v->push_back( osg::Vec3( -cd, cw, ch) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(-1, 0, 0) ); // normal - - // Right face - v->push_back( osg::Vec3( 0, cw, 0) ); // bottom right - v->push_back( osg::Vec3( -cd, cw, 0) ); // bottom left - v->push_back( osg::Vec3( -cd, cw, ch) ); // top left - v->push_back( osg::Vec3( 0, cw, ch) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, 1, 0) ); // normal - - // ROOF - if (building.pitched) { - - // Front pitched roof - v->push_back( osg::Vec3( 0, -cw, ch) ); // bottom right - v->push_back( osg::Vec3( 0, cw, ch) ); // bottom left - v->push_back( osg::Vec3(-0.5*cd, cw, ch+3) ); // top left - v->push_back( osg::Vec3(-0.5*cd, -cw, ch+3) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0.707, 0, 0.707) ); // normal - - // Left pitched roof - v->push_back( osg::Vec3( -cd, -cw, ch) ); // bottom right - v->push_back( osg::Vec3( 0, -cw, ch) ); // bottom left - v->push_back( osg::Vec3(-0.5*cd, -cw, ch+3) ); // top left - v->push_back( osg::Vec3(-0.5*cd, -cw, ch+3) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, -1, 0) ); // normal - - // Back pitched roof - v->push_back( osg::Vec3( -cd, cw, ch) ); // bottom right - v->push_back( osg::Vec3( -cd, -cw, ch) ); // bottom left - v->push_back( osg::Vec3(-0.5*cd, -cw, ch+3) ); // top left - v->push_back( osg::Vec3(-0.5*cd, cw, ch+3) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(-0.707, 0, 0.707) ); // normal - - // Right pitched roof - v->push_back( osg::Vec3( 0, cw, ch) ); // bottom right - v->push_back( osg::Vec3( -cd, cw, ch) ); // bottom left - v->push_back( osg::Vec3(-0.5*cd, cw, ch+3) ); // top left - v->push_back( osg::Vec3(-0.5*cd, cw, ch+3) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, 1, 0) ); // normal - } else { - // If the roof isn't pitched, we still generate the - // vertices for simplicity later. - - // Top of the roof - v->push_back( osg::Vec3( 0, -cw, ch) ); // bottom right - v->push_back( osg::Vec3( 0, cw, ch) ); // bottom left - v->push_back( osg::Vec3(-cd, cw, ch) ); // top left - v->push_back( osg::Vec3(-cd, -cw, ch) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, 0, 1) ); // normal - - // Left non-pitched roof - v->push_back( osg::Vec3( -cd, -cw, ch) ); // bottom right - v->push_back( osg::Vec3( 0, -cw, ch) ); // bottom left - v->push_back( osg::Vec3( 0, -cw, ch) ); // top left - v->push_back( osg::Vec3( -cd, -cw, ch) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, -1, 0) ); // normal - - // Back pitched roof - v->push_back( osg::Vec3(-cd, cw, ch) ); // bottom right - v->push_back( osg::Vec3(-cd, -cw, ch) ); // bottom left - v->push_back( osg::Vec3(-cd, -cw, ch) ); // top left - v->push_back( osg::Vec3(-cd, cw, ch) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(1, 0, 0) ); // normal - - // Right pitched roof - v->push_back( osg::Vec3( 0, cw, ch) ); // bottom right - v->push_back( osg::Vec3(-cd, cw, ch) ); // bottom left - v->push_back( osg::Vec3(-cd, cw, ch) ); // top left - v->push_back( osg::Vec3( 0, cw, ch) ); // top right - - for (int i=0; i<4; ++i) - n->push_back( osg::Vec3(0, 1, 0) ); // normal - } - - // The 1024x1024 texture is split into 32x16 blocks. - // For a small building, each block is 6m wide and 3m high. - // For a medium building, each block is 10m wide and 3m high. - // For a large building, each block is 20m wide and 3m high - - if (building.type == SGBuildingBin::SMALL) { - // Small buildings are represented on the bottom 5 rows of 3 floors - int row = ((int) (mt_rand(&seed) * 1000)) % 5; - float base_y = (float) row * 16.0 * 3.0 / 1024.0; - float top_y = base_y + 16.0 * (float) building.floors / 1024.0; - float left_x = 32.0 / 1024.0 * SGMisc::round((float)building.width / 6.0f); - float right_x = 0.0f; - float front_x = 384.0/1024.0; - float back_x = 384.0 / 1024.0 + 32.0 / 1024.0 * SGMisc::round((float)building.depth / 6.0f); - - // BASEMENT - uses the baseline texture - for (unsigned int i = 0; i < 16; i++) { - t->push_back( osg::Vec2( left_x, base_y) ); - } - // MAIN BODY - // Front - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Left - t->push_back( osg::Vec2( front_x, base_y) ); // bottom right - t->push_back( osg::Vec2( back_x, base_y) ); // bottom left - t->push_back( osg::Vec2( back_x, top_y ) ); // top left - t->push_back( osg::Vec2( front_x, top_y ) ); // top right - - // Back (same as front for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Right (same as left for the moment) - t->push_back( osg::Vec2( front_x, base_y) ); // bottom right - t->push_back( osg::Vec2( back_x, base_y) ); // bottom left - t->push_back( osg::Vec2( back_x, top_y ) ); // top left - t->push_back( osg::Vec2( front_x, top_y ) ); // top right - - // ROOF - if (building.pitched) { - // Use the entire height of the roof texture - top_y = base_y + 16.0 * 3.0 / 1024.0; - left_x = 512 / 1024.0 + 32.0 / 1024.0 * SGMisc::round(building.width / 6.0f); - right_x = 512/1024.0; - front_x = 480.0/1024.0; - back_x = 512.0/1024.0; - - // Front - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Left - t->push_back( osg::Vec2( front_x, base_y) ); // bottom right - t->push_back( osg::Vec2( back_x, base_y) ); // bottom left - t->push_back( osg::Vec2( back_x, top_y ) ); // top left - t->push_back( osg::Vec2( front_x, top_y ) ); // top right - - // Back (same as front for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Right (same as left for the moment) - t->push_back( osg::Vec2( front_x, base_y) ); // bottom right - t->push_back( osg::Vec2( back_x, base_y) ); // bottom left - t->push_back( osg::Vec2( back_x, top_y ) ); // top left - t->push_back( osg::Vec2( front_x, top_y ) ); // top right - } else { - // Flat roof - left_x = 640.0/1024.0; - right_x = 512.0/1024.0; - // Use the entire height of the roof texture - top_y = base_y + 16.0 * 3.0 / 1024.0; - - // Flat roofs still have 4 surfaces, so we need to set the textures - for (int i=0; i<4; ++i) { - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - } - } - - } - - if (building.type == SGBuildingBin::MEDIUM) - { - int column = ((int) (mt_rand(&seed) * 1000)) % 5; - float base_y = 288 / 1024.0; - float top_y = base_y + 16.0 * (float) building.floors / 1024.0; - float left_x = column * 192.0 / 1024.0 + 32.0 / 1024.0 * SGMisc::round((float)building.width / 10.0f); - float right_x = column * 192.0 /1024.0; - - // BASEMENT - uses the baseline texture - for (unsigned int i = 0; i < 16; i++) { - t->push_back( osg::Vec2( left_x, base_y) ); - } - - // MAIN BODY - // Front - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Left - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Back (same as front for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Right (same as left for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // ROOF - if (building.pitched) { - base_y = 288.0/1024.0; - top_y = 576.0/1024.0; - left_x = 960.0/1024.0; - right_x = 1.0; - - // Front - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Left - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Back (same as front for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Right (same as left for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - } else { - // Flat roof - base_y = 416/1024.0; - top_y = 576.0/1024.0; - left_x = column * 192.0 /1024.0; - right_x = (column + 1)* 192.0 /1024.0; - - // Flat roofs still have 4 surfaces - for (int i=0; i<4; ++i) { - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - } - } - } - - if (building.type == SGBuildingBin::LARGE) - { - int column = ((int) (mt_rand(&seed) * 1000)) % 8; - float base_y = 576 / 1024.0; - float top_y = base_y + 16.0 * (float) building.floors / 1024.0; - float left_x = column * 128.0 / 1024.0 + 32.0 / 1024.0 * SGMisc::round((float)building.width / 20.0f); - float right_x = column * 128.0 /1024.0; - - // BASEMENT - uses the baseline texture - for (unsigned int i = 0; i < 16; i++) { - t->push_back( osg::Vec2( left_x, base_y) ); - } - - // MAIN BODY - // Front - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Left - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Back (same as front for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Right (same as left for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // ROOF - if (building.pitched) { - base_y = 896/1024.0; - top_y = 1.0; - // Front - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Left - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Back (same as front for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - - // Right (same as left for the moment) - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - } else { - // Flat roof - base_y = 896/1024.0; - top_y = 1.0; - - // Flat roofs still have 4 surfaces - for (int i=0; i<4; ++i) { - t->push_back( osg::Vec2( right_x, base_y) ); // bottom right - t->push_back( osg::Vec2( left_x, base_y) ); // bottom left - t->push_back( osg::Vec2( left_x, top_y ) ); // top left - t->push_back( osg::Vec2( right_x, top_y ) ); // top right - } - } - } + pitch_height = (mt_rand(&seed) < material->get_building_medium_pitch()) ? 3.0 : 0.0; + + if (pitch_height == 0.0) { + roof_shape = 0; + roof_orientation = 0; + } else { + roof_shape = (int) (mt_rand(&seed) * 10.0); + roof_orientation = (int) std::round((float) mt_rand(&seed)); } + } else { + // LARGE BUILDING + width = material->get_building_large_min_width() + mt_rand(&seed) * (material->get_building_large_max_width() - material->get_building_large_min_width()); + depth = material->get_building_large_min_depth() + mt_rand(&seed) * (material->get_building_large_max_depth() - material->get_building_large_min_depth()); + floors = SGMisc::round(material->get_building_large_min_floors() + mt_rand(&seed) * (material->get_building_large_max_floors() - material->get_building_large_min_floors())); + height = floors * (2.8 + mt_rand(&seed)); + pitch_height = (mt_rand(&seed) < material->get_building_large_pitch()) ? 3.0 : 0.0; - // Set the vertex, texture and normals. Colors will be set per-instance - // later. - sharedGeometry->setVertexArray(v); - sharedGeometry->setTexCoordArray(0, t, Array::BIND_PER_VERTEX); - sharedGeometry->setNormalArray(n, Array::BIND_PER_VERTEX); - } - } - - void SGBuildingBin::insert(SGVec3f p, float r, BuildingType type) { - - if (type == SGBuildingBin::SMALL) { - smallBuildingLocations.push_back(BuildingInstance(p, r, &smallBuildings, smallSharedGeometry)); + if (pitch_height == 0.0) { + roof_shape = 0; + roof_orientation = 0; + } else { + roof_shape = (int) (mt_rand(&seed) * 10.0); + roof_orientation = (int) std::round((float) mt_rand(&seed)); + } } - if (type == SGBuildingBin::MEDIUM) { - mediumBuildingLocations.push_back(BuildingInstance(p, r, &mediumBuildings, mediumSharedGeometry)); - } - - if (type == SGBuildingBin::LARGE) { - largeBuildingLocations.push_back(BuildingInstance(p, r, &largeBuildings, largeSharedGeometry)); - } + insert(p, r, buildingtype, width, depth, height, pitch_height, floors, roof_shape, roof_orientation, (int) (mt_rand(&seed) * 1000.0), (int) (mt_rand(&seed) * 1000.0)); } int SGBuildingBin::getNumBuildings() { - return smallBuildingLocations.size() + mediumBuildingLocations.size() + largeBuildingLocations.size(); + return buildingLocations.size(); } bool SGBuildingBin::checkMinDist (SGVec3f p, float radius) { BuildingInstanceList::iterator iter; - - float r = (radius + smallBuildingMaxRadius) * (radius + smallBuildingMaxRadius); - for (iter = smallBuildingLocations.begin(); iter != smallBuildingLocations.end(); ++iter) { - if (iter->getDistSqr(p) < r) { + for (iter = buildingLocations.begin(); iter != buildingLocations.end(); ++iter) { + if (iter->getDistSqr(toOsg(p)) < radius) { return false; } } - - r = (radius + mediumBuildingMaxRadius) * (radius + mediumBuildingMaxRadius); - for (iter = mediumBuildingLocations.begin(); iter != mediumBuildingLocations.end(); ++iter) { - if (iter->getDistSqr(p) < r) { - return false; - } - } - - r = (radius + largeBuildingMaxRadius) * (radius + largeBuildingMaxRadius); - for (iter = largeBuildingLocations.begin(); iter != largeBuildingLocations.end(); ++iter) { - if (iter->getDistSqr(p) < r) { - return false; - } - } - return true; } SGBuildingBin::BuildingType SGBuildingBin::getBuildingType(float roll) { - if (roll < smallBuildingFraction) { + if (roll < material->get_building_small_fraction()) { return SGBuildingBin::SMALL; } - if (roll < (smallBuildingFraction + mediumBuildingFraction)) { + if (roll < (material->get_building_small_fraction() + material->get_building_medium_fraction())) { return SGBuildingBin::MEDIUM; } @@ -756,20 +717,16 @@ BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const } float SGBuildingBin::getBuildingMaxRadius(BuildingType type) { - - if (type == SGBuildingBin::SMALL) return smallBuildingMaxRadius; - if (type == SGBuildingBin::MEDIUM) return mediumBuildingMaxRadius; - if (type == SGBuildingBin::LARGE) return largeBuildingMaxRadius; - + if (type == SGBuildingBin::SMALL) return material->get_building_small_max_width(); + if (type == SGBuildingBin::MEDIUM) return material->get_building_medium_max_width(); + if (type == SGBuildingBin::LARGE) return material->get_building_large_max_width(); return 0; } float SGBuildingBin::getBuildingMaxDepth(BuildingType type) { - - if (type == SGBuildingBin::SMALL) return smallBuildingMaxDepth; - if (type == SGBuildingBin::MEDIUM) return mediumBuildingMaxDepth; - if (type == SGBuildingBin::LARGE) return largeBuildingMaxDepth; - + if (type == SGBuildingBin::SMALL) return material->get_building_small_max_depth(); + if (type == SGBuildingBin::MEDIUM) return material->get_building_medium_max_depth(); + if (type == SGBuildingBin::LARGE) return material->get_building_large_max_depth(); return 0; } @@ -799,35 +756,33 @@ BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const iter->second = effect; // update existing, but empty observer } - ref_ptr group = new osg::Group(); + // Transform building positions from the "geocentric" positions we + // get from the scenery polys into the local Z-up coordinate + // system. + std::vector rotatedBuildings; + rotatedBuildings.reserve(buildingLocations.size()); + for (const auto &b : buildingLocations) { + rotatedBuildings.emplace_back(BuildingInstance( + b.position * transInv, + b + )); + } // Now, create a quadbuilding for the buildings. + BuildingGeometryQuadtree + quadbuilding(GetBuildingCoord(), AddBuildingLeafObject(), + SG_BUILDING_QUAD_TREE_DEPTH, + MakeBuildingLeaf(buildingRange, effect, false)); - BuildingInstanceList locs[] = { smallBuildingLocations, - SGBuildingBin::mediumBuildingLocations, - SGBuildingBin::largeBuildingLocations }; + quadbuilding.buildQuadTree(rotatedBuildings.begin(), rotatedBuildings.end()); - for (int i = 0; i < 3; i++) - { - // Create a quad tree. Only small and medium buildings are faded out. - BuildingGeometryQuadtree - quadbuilding(GetBuildingCoord(), AddBuildingLeafObject(), - SG_BUILDING_QUAD_TREE_DEPTH, - MakeBuildingLeaf(buildingRange, effect, (i != 2))); + ref_ptr group = new osg::Group(); - // Transform building positions from the "geocentric" positions we - // get from the scenery polys into the local Z-up coordinate - // system. - std::vector rotatedBuildings; - rotatedBuildings.reserve(locs[i].size()); - std::transform(locs[i].begin(), locs[i].end(), - std::back_inserter(rotatedBuildings), - BuildingInstanceTransformer(transInv)); - quadbuilding.buildQuadTree(rotatedBuildings.begin(), rotatedBuildings.end()); + static int buildingGroupCounter = 0; + group->setName("BuildingsGroup_" + std::to_string(buildingGroupCounter++)); - for (size_t j = 0; j < quadbuilding.getRoot()->getNumChildren(); ++j) - group->addChild(quadbuilding.getRoot()->getChild(j)); - } + for (size_t j = 0; j < quadbuilding.getRoot()->getNumChildren(); ++j) + group->addChild(quadbuilding.getRoot()->getChild(j)); return group; } @@ -847,10 +802,6 @@ BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const if (!geode) continue; bool geodeEmpty = true; - if (geode->getNumDrawables() > 1) { - SG_LOG(SG_TERRAIN, SG_DEBUG, "Building LOD Drawables: " << geode->getNumDrawables()); - } - for (unsigned j = 0; j < geode->getNumDrawables(); ++j) { const Geometry* geom = dynamic_cast(geode->getDrawable(j)); if (!geom) { @@ -878,7 +829,6 @@ BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const const SGReaderWriterOptions* options) { Matrix transInv = Matrix::inverse(transform); - static Matrix ident; // Set up some shared structures. MatrixTransform* mt = new MatrixTransform(transform); SGBuildingBinList::iterator i; @@ -890,12 +840,8 @@ BuildingBoundingBoxCallback::computeBound(const Drawable& drawable) const for (size_t j = 0; j < group->getNumChildren(); ++j) { mt->addChild(group->getChild(j)); } - - delete bin; } - buildings.clear(); - QuadTreeCleaner cleaner; mt->accept(cleaner); return mt; diff --git a/simgear/scene/tgdb/SGBuildingBin.hxx b/simgear/scene/tgdb/SGBuildingBin.hxx index d6774ca0..6e99d45b 100644 --- a/simgear/scene/tgdb/SGBuildingBin.hxx +++ b/simgear/scene/tgdb/SGBuildingBin.hxx @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -47,141 +48,140 @@ #include #include -#define SG_BUILDING_QUAD_TREE_DEPTH 4 +#define SG_BUILDING_QUAD_TREE_DEPTH 2 #define SG_BUILDING_FADE_OUT_LEVELS 4 +// these correspond to building.eff +const int BUILDING_POSITION_ATTR = 10; // (x,y,z) +const int BUILDING_SCALE_ATTR = 11; // (width, depth, height) +const int BUILDING_ROT_PITCH_TEX0X_ATTR = 12; // (rotation, pitch height, wall texture x offset) +const int BUILDING_TEX0Y_TEX1X_TEX1Y_ATTR = 13; // (wall texture y offset, texture x gain, texture y gain) +const int BUILDING_RTEX0X_RTEX0Y_ATTR = 14; // (roof texture x offset, roof texture y offset, unused) +const int BUILDING_ROFFTOPSCALE_ATTR = 15; // (roof texture x offset, roof texture y offset, unused) + using namespace osg; namespace simgear { + +struct BuildingBoundingBoxCallback : public Drawable::ComputeBoundingBoxCallback +{ + BuildingBoundingBoxCallback() {} + BuildingBoundingBoxCallback(const BuildingBoundingBoxCallback&, const CopyOp&) {} + META_Object(simgear, BuildingBoundingBoxCallback); + virtual BoundingBox computeBound(const Drawable& drawable) const + { + BoundingBox bb; + const Geometry* geom = static_cast(&drawable); + const Vec3Array* pos = static_cast(geom->getVertexAttribArray(BUILDING_POSITION_ATTR)); + + for (unsigned int v=0; vsize(); ++v) { + Vec3 pt = (*pos)[v]; + bb.expandBy(pt); + } + return bb; + } +}; + class SGBuildingBin { public: - // Number of buildings to auto-generate. Individual - // building instances are taken from this set. - static const unsigned int BUILDING_SET_SIZE = 200; - - static const unsigned int QUADS_PER_BUILDING = 12; - static const unsigned int VERTICES_PER_BUILDING = 4 * QUADS_PER_BUILDING; - static const unsigned int VERTICES_PER_BUILDING_SET = BUILDING_SET_SIZE * VERTICES_PER_BUILDING; - enum BuildingType { SMALL = 0, MEDIUM, LARGE }; + struct BuildingInstance { + BuildingInstance(Vec3f p, float w, float d, float h, float ph, float r, Vec2f wt0, Vec2f rt0, Vec3f t1, Vec2f rs) : + position(p), + width(w), + depth(d), + height(h), + pitch_height(ph), + rotation(r), + walltex0(wt0), + rooftex0(rt0), + tex1(t1), + rooftop_scale(rs) + { } + + BuildingInstance(Vec3f p, BuildingInstance b) : + position(p), + width(b.width), + depth(b.depth), + height(b.height), + pitch_height(b.pitch_height), + rotation(b.rotation), + walltex0(b.walltex0), + rooftex0(b.rooftex0), + tex1(b.tex1), + rooftop_scale(b.rooftop_scale) + { } + + + Vec3f position; + float width; + float depth; + float height; + float pitch_height; + float rotation; + + Vec2f walltex0; + Vec2f rooftex0; + Vec3f tex1; // Texture gains for the front, roof and sides + + Vec2f rooftop_scale; + + // References to allow the QuadTreeBuilder to work + //const BuildingList* buildingList; + //ref_ptr sharedGeometry; + + Vec3f getPosition() { return position; } + float getRotation() { return rotation; } + + float getDistSqr(Vec3f p) { + return (p - position) * (p - position); + } + }; + private: - struct Building { - Building(BuildingType t, float w, float d, float h, int f, bool pitch) : - type(t), - width(w), - depth(d), - height(h), - floors(f), - pitched(pitch), - radius(std::max(d, 0.5f*w)) - { } - - BuildingType type; - float width; - float depth; - float height; - int floors; - bool pitched; - float radius; - - float getFootprint() { - return radius; - } - }; - - // The set of buildings that are instantiated - typedef std::vector BuildingList; - BuildingList smallBuildings; - BuildingList mediumBuildings; - BuildingList largeBuildings; + const SGMaterial *material; std::string* material_name; std::string* texture; std::string* lightMap; - // Fraction of buildings of this type - float smallBuildingFraction; - float mediumBuildingFraction; - - // The maximum radius of each building type - float smallBuildingMaxRadius; - float mediumBuildingMaxRadius; - float largeBuildingMaxRadius; - - // The maximum depth of each building type - float smallBuildingMaxDepth; - float mediumBuildingMaxDepth; - float largeBuildingMaxDepth; - // Visibility range for buildings float buildingRange; - // Shared geometries of the building set - ref_ptr smallSharedGeometry; - ref_ptr mediumSharedGeometry; - ref_ptr largeSharedGeometry; - - struct BuildingInstance { - BuildingInstance(SGVec3f p, float r, const BuildingList* bl, ref_ptr sg) : - position(p), - rotation(r), - buildingList(bl), - sharedGeometry(sg) - { } - - BuildingInstance(SGVec3f p, BuildingInstance b) : - position(p), - rotation(b.rotation), - buildingList(b.buildingList), - sharedGeometry(b.sharedGeometry) - { } - - SGVec3f position; - float rotation; - - // References to allow the QuadTreeBuilder to work - const BuildingList* buildingList; - ref_ptr sharedGeometry; - - SGVec3f getPosition() { return position; } - float getRotation() { return rotation; } - - float getDistSqr(SGVec3f p) { - return distSqr(p, position); - } - - const osg::Vec4f getColorValue() { - return osg::Vec4f(toOsg(position), rotation); - } - }; // Information for an instance of a building - position and orientation typedef std::vector BuildingInstanceList; - BuildingInstanceList smallBuildingLocations; - BuildingInstanceList mediumBuildingLocations; - BuildingInstanceList largeBuildingLocations; + BuildingInstanceList buildingLocations; public: SGBuildingBin(const SGMaterial *mat, bool useVBOs); SGBuildingBin(const SGPath& absoluteFileName, const SGMaterial *mat, bool useVBOs); - ~SGBuildingBin() { - smallBuildings.clear(); - mediumBuildings.clear(); - largeBuildings.clear(); - smallBuildingLocations.clear(); - mediumBuildingLocations.clear(); - largeBuildingLocations.clear(); - } + ~SGBuildingBin(); + // Generate a building specifying the exact position, dimensions and texture index. + void insert(SGVec3f p, + float r, + BuildingType buildingtype, + float width, + float depth, + float height, + float pitch_height, + int floors, + int roof_shape, + int roof_orientation, + int wall_tex_index, + int roof_tex_index); + + // Generate a building of a given type at a specified position, using the random building material definition to determine the dimensions and texture index. void insert(SGVec3f p, float r, BuildingType type); int getNumBuildings(); @@ -190,123 +190,9 @@ public: std::string* getMaterialName() { return material_name; } BuildingType getBuildingType(float roll); - float getBuildingMaxRadius(BuildingType); float getBuildingMaxDepth(BuildingType); - // Helper classes for creating the quad tree - struct MakeBuildingLeaf - { - MakeBuildingLeaf(float range, Effect* effect, bool fade) : - _range(range), _effect(effect), _fade_out(fade) {} - - MakeBuildingLeaf(const MakeBuildingLeaf& rhs) : - _range(rhs._range), _effect(rhs._effect), _fade_out(rhs._fade_out) - {} - - LOD* operator() () const - { - LOD* result = new LOD; - - if (_fade_out) { - // Create a series of LOD nodes so building cover decreases - // gradually with distance from _range to 2*_range - for (float i = 0.0; i < SG_BUILDING_FADE_OUT_LEVELS; i++) - { - EffectGeode* geode = new EffectGeode; - geode->setEffect(_effect.get()); - result->addChild(geode, 0, _range * (1.0 + i / (SG_BUILDING_FADE_OUT_LEVELS - 1.0))); - } - } else { - // No fade-out, so all are visible for 2X range - EffectGeode* geode = new EffectGeode; - geode->setEffect(_effect.get()); - result->addChild(geode, 0, 2.0 * _range); - } - return result; - } - - float _range; - ref_ptr _effect; - bool _fade_out; - }; - - struct AddBuildingLeafObject - { - Geometry* createNewBuildingGeometryInstance(const BuildingInstance& building) const - { - Geometry* geom = simgear::clone(building.sharedGeometry.get(), CopyOp::SHALLOW_COPY); - geom->setColorArray(new Vec4Array, Array::BIND_PER_VERTEX); - geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS)); - return geom; - } - - void operator() (LOD* lod, const BuildingInstance& building) const - { - Geode* geode = static_cast(lod->getChild(int(building.position.x() * 10.0f) % lod->getNumChildren())); - unsigned int numDrawables = geode->getNumDrawables(); - - // Get the last geometry of to be added and check if there is space for - // another building instance within it. This is done by checking - // if the number of Color values matches the number of vertices. - // The color array is used to store the position of a particular - // instance. - Geometry* geom; - - if (numDrawables == 0) { - // Create a new copy of the shared geometry to instantiate - geom = createNewBuildingGeometryInstance(building); - geode->addDrawable(geom); - } else { - geom = static_cast(geode->getDrawable(numDrawables - 1)); - } - - // Check if this building is too close to any other others. - DrawArrays* primSet = static_cast(geom->getPrimitiveSet(0)); - Vec4Array* posArray = static_cast(geom->getColorArray()); - - // Now check if this geometry is full. - if (posArray->size() >= static_cast(geom->getVertexArray())->size()) { - // This particular geometry is full, so we generate another - // by taking a shallow copy of the shared Geomety. - geom = createNewBuildingGeometryInstance(building); - geode->addDrawable(geom); - posArray = static_cast(geom->getColorArray()); - SG_LOG(SG_TERRAIN, SG_DEBUG, "Added new geometry to building geod: " << geode->getNumDrawables()); - } - - // We now have a geometry with space for this new building. - // Set the position and rotation - osg::Vec4f c = osg::Vec4f(toOsg(building.position), building.rotation); - posArray->insert(posArray->end(), VERTICES_PER_BUILDING, c); - size_t numVerts = posArray->size(); - primSet = static_cast(geom->getPrimitiveSet(0)); - primSet->setCount(numVerts); - } - }; - - struct GetBuildingCoord - { - Vec3 operator() (const BuildingInstance& building) const - { - return toOsg(building.position); - } - }; - - typedef QuadTreeBuilder BuildingGeometryQuadtree; - - struct BuildingInstanceTransformer - { - BuildingInstanceTransformer(Matrix& mat_) : mat(mat_) {} - BuildingInstance operator()(const BuildingInstance& buildingInstance) const - { - Vec3 pos = toOsg(buildingInstance.position) * mat; - return BuildingInstance(toSG(pos), buildingInstance); - } - Matrix mat; - }; - ref_ptr createBuildingsGroup(Matrix transInv, const SGReaderWriterOptions* options); }; diff --git a/simgear/scene/tgdb/SGNodeTriangles.hxx b/simgear/scene/tgdb/SGNodeTriangles.hxx index 1d3f72d2..128b242d 100644 --- a/simgear/scene/tgdb/SGNodeTriangles.hxx +++ b/simgear/scene/tgdb/SGNodeTriangles.hxx @@ -285,100 +285,154 @@ public: } } } - + void addRandomTreePoints(float wood_coverage, osg::Texture2D* object_mask, float vegetation_density, float cos_max_density_angle, float cos_zero_density_angle, + bool is_plantation, std::vector& points, std::vector& normals) { if ( !geometries.empty() ) { const osg::Vec3Array* vertices = dynamic_cast(geometries[0]->getVertexArray()); const osg::Vec2Array* texcoords = dynamic_cast(geometries[0]->getTexCoordArray(0)); - + int numPrimitiveSets = geometries[0]->getNumPrimitiveSets(); if ( numPrimitiveSets > 0 ) { const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0); unsigned int numIndices = ps->getNumIndices(); - + for ( unsigned int i=2; ioperator[](ps->index(i-2))); SGVec3f v1 = toSG(vertices->operator[](ps->index(i-1))); SGVec3f v2 = toSG(vertices->operator[](ps->index(i-0))); - + SGVec2f t0 = toSG(texcoords->operator[](ps->index(i-2))); SGVec2f t1 = toSG(texcoords->operator[](ps->index(i-1))); SGVec2f t2 = toSG(texcoords->operator[](ps->index(i-0))); - + SGVec3f normal = cross(v1 - v0, v2 - v0); - + // Ensure the slope isn't too steep by checking the // cos of the angle between the slope normal and the // vertical (conveniently the z-component of the normalized // normal) and values passed in. float alpha = normalize(normal).z(); float slope_density = 1.0; - + if (alpha < cos_zero_density_angle) continue; // Too steep for any vegetation - + if (alpha < cos_max_density_angle) { slope_density = (alpha - cos_zero_density_angle) / (cos_max_density_angle - cos_zero_density_angle); } - + // Compute the area float area = 0.5f*length(normal); if (area <= SGLimitsf::min()) continue; - - // Determine the number of trees, taking into account vegetation - // density (which is linear) and the slope density factor. - // Use a zombie door method to create the proper random chance - // of a tree being created for partial values. - int woodcount = (int) (vegetation_density * vegetation_density * - slope_density * - area / wood_coverage + mt_rand(&seed)); - - for (int j = 0; j < woodcount; j++) { - float a = mt_rand(&seed); - float b = mt_rand(&seed); - - if ( a + b > 1.0f ) { - a = 1.0f - a; - b = 1.0f - b; - } - - float c = 1.0f - a - b; - - SGVec3f randomPoint = a*v0 + b*v1 + c*v2; - - if (object_mask != NULL) { - SGVec2f texCoord = a*t0 + b*t1 + c*t2; - - // Check this random point against the object mask - // green (for trees) channel. - osg::Image* img = object_mask->getImage(); - unsigned int x = (int) (img->s() * texCoord.x()) % img->s(); - unsigned int y = (int) (img->t() * texCoord.y()) % img->t(); - - if (mt_rand(&seed) < img->getColor(x, y).g()) { - // The red channel contains the rotation for this object - points.push_back(randomPoint); - normals.push_back(normalize(normal)); + + if (is_plantation) { // regularly-spaced vegetation + // separate vegetation in integral 1m units + int separation = (int) ceil(sqrt(wood_coverage)); + float max_x = ceil(max(max(v1.x(),v2.x()),v0.x())); + float min_x = floor(min(min(v1.x(),v2.x()),v0.x())); + float max_y = ceil(max(max(v1.y(),v2.y()),v0.y())); + float min_y = floor(min(min(v1.y(),v2.y()),v0.y())); + + /* equation of the plane ax+by+cz+d=0, need d */ + + float d = -1*(normal.x()*v0.x() + normal.y()*v0.y()+normal.z()*v0.z()); + /* Now loop over a grid, skipping points not in the triangle */ + int x_steps = (int) (max_x - min_x)/separation; + int y_steps = (int) (max_y - min_y)/separation; + SGVec2f v02d = SGVec2f(v0.x(),v0.y()); + SGVec2f v12d = SGVec2f(v1.x(),v1.y()); + SGVec2f v22d = SGVec2f(v2.x(),v2.y()); + + for (int jx = 0; jx < x_steps; jx++) { + float ptx = min_x + jx * separation; + + for (int jy = 0; jy < y_steps; jy++) { + float pty = min_y + jy * separation; + SGVec2f newpt = SGVec2f(ptx,pty); + if (!point_in_triangle(newpt,v02d,v12d,v22d)) + continue; + + // z = (-ax-by-d)/c; c is not zero as + // that would be alpha of 1.0 + + float ptz = (-normal.x()*ptx - normal.y()*pty-d)/normal.z(); + SGVec3f randomPoint = SGVec3f(ptx,pty,ptz); + + if (object_mask != NULL) { + // Check this point against the object mask + // green (for trees) channel. + osg::Image* img = object_mask->getImage(); + unsigned int x = (int) (img->s() * newpt.x()) % img->s(); + unsigned int y = (int) (img->t() * newpt.y()) % img->t(); + + if (mt_rand(&seed) < img->getColor(x, y).g()) { + // The red channel contains the rotation for this object + points.push_back(randomPoint); + normals.push_back(normalize(normal)); + } + } else { + points.push_back(randomPoint); + normals.push_back(normalize(normal)); + } } - } else { - points.push_back(randomPoint); - normals.push_back(normalize(normal)); - } + } + } else { + // Determine the number of trees, taking into account vegetation + // density (which is linear) and the slope density factor. + // Use a zombie door method to create the proper random chance + // of a tree being created for partial values. + int woodcount = (int) (vegetation_density * vegetation_density * + slope_density * + area / wood_coverage + mt_rand(&seed)); + for (int j = 0; j < woodcount; j++) { + // Use barycentric coordinates + float a = mt_rand(&seed); + float b = mt_rand(&seed); + + if ( a + b > 1.0f ) { + a = 1.0f - a; + b = 1.0f - b; + } + + float c = 1.0f - a - b; + + SGVec3f randomPoint = a*v0 + b*v1 + c*v2; + if (object_mask != NULL) { + SGVec2f texCoord = a*t0 + b*t1 + c*t2; + + // Check this random point against the object mask + // green (for trees) channel. + osg::Image* img = object_mask->getImage(); + unsigned int x = (int) (img->s() * texCoord.x()) % img->s(); + unsigned int y = (int) (img->t() * texCoord.y()) % img->t(); + + if (mt_rand(&seed) < img->getColor(x, y).g()) { + // The red channel contains the rotation for this object + points.push_back(randomPoint); + normals.push_back(normalize(normal)); + } + } else { + points.push_back(randomPoint); + normals.push_back(normalize(normal)); + + } + } } } } } } - + #if 0 // debug : this will save the tile as a shapefile that can be viewed in QGIS. // NOTE: this is really slow.... diff --git a/simgear/scene/tgdb/SGOceanTile.cxx b/simgear/scene/tgdb/SGOceanTile.cxx index f4636159..29839b29 100644 --- a/simgear/scene/tgdb/SGOceanTile.cxx +++ b/simgear/scene/tgdb/SGOceanTile.cxx @@ -338,7 +338,7 @@ osg::Node* SGOceanTile(const SGBucket& b, SGMaterialLib *matlib, int latPoints, transform->setMatrix(osg::Matrix::rotate(toOsg(hlOr))* osg::Matrix::translate(toOsg(cartCenter))); transform->addChild(geode); - transform->setNodeMask( ~simgear::MODELLIGHT_BIT ); + transform->setNodeMask( ~(simgear::CASTSHADOW_BIT | simgear::MODELLIGHT_BIT) ); return transform; } diff --git a/simgear/scene/tgdb/SGTexturedTriangleBin.hxx b/simgear/scene/tgdb/SGTexturedTriangleBin.hxx index 47939a75..8f5cc314 100644 --- a/simgear/scene/tgdb/SGTexturedTriangleBin.hxx +++ b/simgear/scene/tgdb/SGTexturedTriangleBin.hxx @@ -216,6 +216,7 @@ public: float vegetation_density, float cos_max_density_angle, float cos_zero_density_angle, + bool is_plantation, std::vector& points, std::vector& normals) { @@ -249,50 +250,104 @@ public: float area = 0.5f*length(normal); if (area <= SGLimitsf::min()) continue; - - // Determine the number of trees, taking into account vegetation - // density (which is linear) and the slope density factor. - // Use a zombie door method to create the proper random chance - // of a tree being created for partial values. - int woodcount = (int) (vegetation_density * vegetation_density * - slope_density * - area / wood_coverage + mt_rand(&seed)); - - for (int j = 0; j < woodcount; j++) { - float a = mt_rand(&seed); - float b = mt_rand(&seed); + if (is_plantation) { // regularly-spaced vegetation - if ( a + b > 1.0f ) { - a = 1.0f - a; - b = 1.0f - b; - } + int separation = (int) ceil(sqrt(wood_coverage)); + float max_x = ceil(max(max(v1.x(),v2.x()),v0.x())); + float min_x = floor(min(min(v1.x(),v2.x()),v0.x())); + float max_y = ceil(max(max(v1.y(),v2.y()),v0.y())); + float min_y = floor(min(min(v1.y(),v2.y()),v0.y())); - float c = 1.0f - a - b; + // equation of the plane ax+by+cz+d=0, need d - SGVec3f randomPoint = a*v0 + b*v1 + c*v2; - - if (object_mask != NULL) { - SGVec2f texCoord = a*t0 + b*t1 + c*t2; - - // Check this random point against the object mask - // green (for trees) channel. - osg::Image* img = object_mask->getImage(); - unsigned int x = (int) (img->s() * texCoord.x()) % img->s(); - unsigned int y = (int) (img->t() * texCoord.y()) % img->t(); - - if (mt_rand(&seed) < img->getColor(x, y).g()) { - // The red channel contains the rotation for this object - points.push_back(randomPoint); - normals.push_back(normalize(normal)); + float d = -1*(normal.x()*v0.x() + normal.y()*v0.y()+normal.z()*v0.z()); + + // Now loop over a grid, skipping points not in the triangle + + int x_steps = (int) (max_x - min_x)/separation; + int y_steps = (int) (max_y - min_y)/separation; + SGVec2f v02d = SGVec2f(v0.x(),v0.y()); + SGVec2f v12d = SGVec2f(v1.x(),v1.y()); + SGVec2f v22d = SGVec2f(v2.x(),v2.y()); + + for (int jx = 0; jx < x_steps; jx++) { + float ptx = min_x + jx * separation; + + for (int jy = 0; jy < y_steps; jy++) { + float pty = min_y + jy * separation; + SGVec2f newpt = SGVec2f(ptx,pty); + if (!point_in_triangle(newpt,v02d,v12d,v22d)) + continue; + + // z = (-ax-by-d)/c; c is not zero as + // that would be alpha of 1.0 + + float ptz = (-normal.x()*ptx - normal.y()*pty-d)/normal.z(); + SGVec3f randomPoint = SGVec3f(ptx,pty,ptz); + + if (object_mask != NULL) { + // Check this point against the object mask + // green (for trees) channel. + osg::Image* img = object_mask->getImage(); + unsigned int x = (int) (img->s() * newpt.x()) % img->s(); + unsigned int y = (int) (img->t() * newpt.y()) % img->t(); + + if (mt_rand(&seed) < img->getColor(x, y).g()) { + // The red channel contains the rotation for this object + points.push_back(randomPoint); + normals.push_back(normalize(normal)); + } + } else { + points.push_back(randomPoint); + normals.push_back(normalize(normal)); + } } - } else { - points.push_back(randomPoint); - normals.push_back(normalize(normal)); - } + } + } else { + // Determine the number of trees, taking into account vegetation + // density (which is linear) and the slope density factor. + // Use a zombie door method to create the proper random chance + // of a tree being created for partial values. + int woodcount = (int) (vegetation_density * vegetation_density * + slope_density * + area / wood_coverage + mt_rand(&seed)); + + for (int j = 0; j < woodcount; j++) { + float a = mt_rand(&seed); + float b = mt_rand(&seed); + + if ( a + b > 1.0f ) { + a = 1.0f - a; + b = 1.0f - b; + } + + float c = 1.0f - a - b; + + SGVec3f randomPoint = a*v0 + b*v1 + c*v2; + + if (object_mask != NULL) { + SGVec2f texCoord = a*t0 + b*t1 + c*t2; + + // Check this random point against the object mask + // green (for trees) channel. + osg::Image* img = object_mask->getImage(); + unsigned int x = (int) (img->s() * texCoord.x()) % img->s(); + unsigned int y = (int) (img->t() * texCoord.y()) % img->t(); + + if (mt_rand(&seed) < img->getColor(x, y).g()) { + // The red channel contains the rotation for this object + points.push_back(randomPoint); + normals.push_back(normalize(normal)); + } + } else { + points.push_back(randomPoint); + normals.push_back(normalize(normal)); + } + } } } } - + void addRandomPoints(double coverage, double spacing, osg::Texture2D* object_mask, diff --git a/simgear/scene/tgdb/SGTileDetailsCallback.hxx b/simgear/scene/tgdb/SGTileDetailsCallback.hxx index 6a16c5da..9fc8a36a 100644 --- a/simgear/scene/tgdb/SGTileDetailsCallback.hxx +++ b/simgear/scene/tgdb/SGTileDetailsCallback.hxx @@ -753,6 +753,7 @@ public: vegetation_density, mat->get_cos_tree_max_density_slope_angle(), mat->get_cos_tree_zero_density_slope_angle(), + mat->get_is_plantation(), randomPoints, randomPointNormals); @@ -903,64 +904,48 @@ public: } Effect* runwayEffect = 0; - if (runwayLights.getNumLights() > 0 - || !rabitLights.empty() - || !reilLights.empty() - || !odalLights.empty() - || taxiLights.getNumLights() > 0) { - + if (runwayLights.getNumLights() > 0 || taxiLights.getNumLights() > 0) { runwayEffect = getLightEffect(16, osg::Vec3(1, 0.001, 0.0002), 1, 16, true, _options); } - + if (runwayLights.getNumLights() > 0 || !rabitLights.empty() || !reilLights.empty() || !odalLights.empty() || !holdshortLights.empty() || !guardLights.empty()) { - osg::Group* rwyLights = new osg::Group; - osg::StateSet* ss = lightManager->getRunwayLightStateSet(); - rwyLights->setStateSet(ss); - rwyLights->setNodeMask(RUNWAYLIGHTS_BIT); - - if (runwayLights.getNumLights() != 0) { - EffectGeode* geode = new EffectGeode; - geode->setEffect(runwayEffect); - - osg::Drawable* rldraw = SGLightFactory::getLights(runwayLights); - geode->addDrawable( rldraw ); - - rwyLights->addChild(geode); - } + osg::Group* rwyLightsGroup = new osg::Group; + rwyLightsGroup->setStateSet(lightManager->getRunwayLightStateSet()); + rwyLightsGroup->setNodeMask(RUNWAYLIGHTS_BIT); + SGDirectionalLightListBin::const_iterator i; - for (i = rabitLights.begin(); - i != rabitLights.end(); ++i) { - osg::Node* seqNode = SGLightFactory::getSequenced(*i, _options); - rwyLights->addChild( seqNode ); + + for (i = rabitLights.begin() ; i != rabitLights.end() ; ++i) { + rwyLightsGroup->addChild(SGLightFactory::getSequenced(*i, _options)); } - for (i = reilLights.begin(); - i != reilLights.end(); ++i) { - osg::Node* seqNode = SGLightFactory::getSequenced(*i, _options); - rwyLights->addChild(seqNode); + for (i = reilLights.begin() ; i != reilLights.end() ; ++i) { + rwyLightsGroup->addChild(SGLightFactory::getReil(*i, _options)); } - for (i = holdshortLights.begin(); - i != holdshortLights.end(); ++i) { - osg::Node* seqNode = SGLightFactory::getHoldShort(*i, _options); - rwyLights->addChild(seqNode); + for (i = holdshortLights.begin() ; i != holdshortLights.end() ; ++i) { + rwyLightsGroup->addChild(SGLightFactory::getHoldShort(*i, _options)); } - for (i = guardLights.begin(); - i != guardLights.end(); ++i) { - osg::Node* seqNode = SGLightFactory::getGuard(*i, _options); - rwyLights->addChild(seqNode); + for (i = guardLights.begin() ; i != guardLights.end() ; ++i) { + rwyLightsGroup->addChild(SGLightFactory::getGuard(*i, _options)); } SGLightListBin::const_iterator j; - for (j = odalLights.begin(); - j != odalLights.end(); ++j) { - osg::Node* seqNode = SGLightFactory::getOdal(*j, _options); - rwyLights->addChild(seqNode); + for (j = odalLights.begin() ; j != odalLights.end() ; ++j) { + rwyLightsGroup->addChild(SGLightFactory::getOdal(*j, _options)); } - lightGroup->addChild(rwyLights); + + if (runwayLights.getNumLights() > 0) { + osg::ref_ptr geode = new EffectGeode; + geode->setEffect(runwayEffect); + geode->addDrawable(SGLightFactory::getLights(runwayLights)); + rwyLightsGroup->addChild(geode); + } + + lightGroup->addChild(rwyLightsGroup); } if (taxiLights.getNumLights() > 0) { @@ -1115,7 +1100,7 @@ public: if (forestNode.valid()) objectLOD->addChild(forestNode.get(), 0, 2.0 * object_range + SG_TILE_RADIUS); if (buildingNode.valid()) objectLOD->addChild(buildingNode.get(), 0, 2.0 * object_range + SG_TILE_RADIUS); - unsigned nodeMask = SG_NODEMASK_CASTSHADOW_BIT | SG_NODEMASK_RECEIVESHADOW_BIT | SG_NODEMASK_TERRAIN_BIT; + unsigned nodeMask = SG_NODEMASK_RECEIVESHADOW_BIT | SG_NODEMASK_TERRAIN_BIT; objectLOD->setNodeMask(nodeMask); } diff --git a/simgear/scene/tgdb/obj.cxx b/simgear/scene/tgdb/obj.cxx index 2efe814e..5d3461f5 100644 --- a/simgear/scene/tgdb/obj.cxx +++ b/simgear/scene/tgdb/obj.cxx @@ -159,6 +159,6 @@ SGLoadBTG(const std::string& path, const simgear::SGReaderWriterOptions* options transform->addChild(pagedLOD); } - transform->setNodeMask( ~simgear::MODELLIGHT_BIT ); + transform->setNodeMask( ~(simgear::CASTSHADOW_BIT | simgear::MODELLIGHT_BIT) ); return transform; } diff --git a/simgear/scene/tgdb/pt_lights.cxx b/simgear/scene/tgdb/pt_lights.cxx index be82127e..11ba4539 100644 --- a/simgear/scene/tgdb/pt_lights.cxx +++ b/simgear/scene/tgdb/pt_lights.cxx @@ -214,8 +214,7 @@ SGLightFactory::getLights(const SGLightBin& lights, unsigned inc, float alphaOff geometry->setDataVariance(osg::Object::STATIC); geometry->setVertexArray(vertices); geometry->setNormalBinding(osg::Geometry::BIND_OFF); - geometry->setColorArray(colors); - geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX); + geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); osg::DrawArrays* drawArrays; drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS, @@ -275,7 +274,7 @@ SGLightFactory::getLights(const SGDirectionalLightBin& lights) //stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF); osg::DrawArrays* drawArrays; - drawArrays = new osg::DrawArrays(osg::PrimitiveSet::POINTS, + drawArrays = new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, vertices->size()); geometry->addPrimitiveSet(drawArrays); return geometry; @@ -370,18 +369,46 @@ SGLightFactory::getSequenced(const SGDirectionalLightBin& lights, const SGReader // generate a repeatable random seed sg_srandom(unsigned(lights.getLight(0).position[0])); - float flashTime = 2e-2 + 5e-3*sg_random(); + float flashTime = 0.065 + 0.003 * sg_random(); osg::Sequence* sequence = new osg::Sequence; sequence->setDefaultTime(flashTime); - Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001), - 6.0f, 10.0f, true, options); + Effect* effect = getLightEffect(24.0f, osg::Vec3(1.0, 0.0001, 0.000001), + 1.0f, 24.0f, true, options); for (int i = lights.getNumLights() - 1; 0 <= i; --i) { EffectGeode* egeode = new EffectGeode; egeode->setEffect(effect); egeode->addDrawable(getLightDrawable(lights.getLight(i))); sequence->addChild(egeode, flashTime); } - sequence->addChild(new osg::Group, 1 + 1e-1*sg_random()); + sequence->addChild(new osg::Group, 1.9 + (0.1 * sg_random()) - (lights.getNumLights() * flashTime)); + sequence->setInterval(osg::Sequence::LOOP, 0, -1); + sequence->setDuration(1.0f, -1); + sequence->setMode(osg::Sequence::START); + sequence->setSync(true); + return sequence; +} + +osg::Node* +SGLightFactory::getReil(const SGDirectionalLightBin& lights, const SGReaderWriterOptions* options) +{ + if (lights.getNumLights() <= 0) + return 0; + + // generate a repeatable random seed + sg_srandom(unsigned(lights.getLight(0).position[0])); + float flashTime = 0.065 + 0.003 * sg_random(); + osg::Sequence* sequence = new osg::Sequence; + sequence->setDefaultTime(flashTime); + Effect* effect = getLightEffect(24.0f, osg::Vec3(1.0, 0.0001, 0.000001), + 1.0f, 24.0f, true, options); + EffectGeode* egeode = new EffectGeode; + egeode->setEffect(effect); + + for (int i = lights.getNumLights() - 1; 0 <= i; --i) { + egeode->addDrawable(getLightDrawable(lights.getLight(i))); + } + sequence->addChild(egeode, flashTime); + sequence->addChild(new osg::Group, 1.9 + 0.1 * sg_random() - flashTime); sequence->setInterval(osg::Sequence::LOOP, 0, -1); sequence->setDuration(1.0f, -1); sequence->setMode(osg::Sequence::START); @@ -397,11 +424,11 @@ SGLightFactory::getOdal(const SGLightBin& lights, const SGReaderWriterOptions* o // generate a repeatable random seed sg_srandom(unsigned(lights.getLight(0).position[0])); - float flashTime = 2e-2 + 5e-3*sg_random(); + float flashTime = 0.065 + 0.003 * sg_random(); osg::Sequence* sequence = new osg::Sequence; sequence->setDefaultTime(flashTime); - Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.0001, 0.00000001), - 6.0, 10.0, false, options); + Effect* effect = getLightEffect(20.0f, osg::Vec3(1.0, 0.0001, 0.000001), + 1.0f, 20.0f, false, options); // centerline lights for (int i = lights.getNumLights() - 1; i >= 2; i--) { EffectGeode* egeode = new EffectGeode; @@ -409,18 +436,18 @@ SGLightFactory::getOdal(const SGLightBin& lights, const SGReaderWriterOptions* o egeode->addDrawable(getLightDrawable(lights.getLight(i))); sequence->addChild(egeode, flashTime); } + // add extra empty group for a break + sequence->addChild(new osg::Group, 4 * flashTime); // runway end lights - osg::Group* group = new osg::Group; + EffectGeode* egeode = new EffectGeode; + egeode->setEffect(effect); for (unsigned i = 0; i < 2; ++i) { - EffectGeode* egeode = new EffectGeode; - egeode->setEffect(effect); egeode->addDrawable(getLightDrawable(lights.getLight(i))); - group->addChild(egeode); } - sequence->addChild(group, flashTime); + sequence->addChild(egeode, flashTime); // add an extra empty group for a break - sequence->addChild(new osg::Group, 2 + 1e-1*sg_random()); + sequence->addChild(new osg::Group, 1.9 + (0.1 * sg_random()) - ((lights.getNumLights() + 2) * flashTime)); sequence->setInterval(osg::Sequence::LOOP, 0, -1); sequence->setDuration(1.0f, -1); sequence->setMode(osg::Sequence::START); @@ -437,23 +464,22 @@ SGLightFactory::getHoldShort(const SGDirectionalLightBin& lights, const SGReader return 0; sg_srandom(unsigned(lights.getLight(0).position[0])); - float flashTime = 1 + 0.1 * sg_random(); + float flashTime = 0.9 + 0.2 * sg_random(); osg::Sequence* sequence = new osg::Sequence; // start with lights off - sequence->addChild(new osg::Group, flashTime); + sequence->addChild(new osg::Group, 0.2); // ...and increase the lights in steps - for (int i = 2; i < 7; i+=2) { - Effect* effect = getLightEffect(i, osg::Vec3(1, 0.001, 0.000002), - 0.0f, i, true, options); + for (int i = 0; i < 5; i++) { + Effect* effect = getLightEffect(12.0f + i, osg::Vec3(1, 0.001, 0.0002), + 1.0f, 12.0f + i, true, options); EffectGeode* egeode = new EffectGeode; + egeode->setEffect(effect); for (unsigned int j = 0; j < lights.getNumLights(); ++j) { egeode->addDrawable(getLightDrawable(lights.getLight(j))); - egeode->setEffect(effect); } - sequence->addChild(egeode, (i==6) ? flashTime : 0.1); + sequence->addChild(egeode, (i==4) ? flashTime : 0.1); } - sequence->setInterval(osg::Sequence::SWING, 0, -1); sequence->setDuration(1.0f, -1); sequence->setMode(osg::Sequence::START); @@ -470,11 +496,11 @@ SGLightFactory::getGuard(const SGDirectionalLightBin& lights, const SGReaderWrit // generate a repeatable random seed sg_srandom(unsigned(lights.getLight(0).position[0])); - float flashTime = 1.0f + 1*sg_random(); + float flashTime = 0.9 + 0.2 * sg_random(); osg::Sequence* sequence = new osg::Sequence; sequence->setDefaultTime(flashTime); - Effect* effect = getLightEffect(10.0f, osg::Vec3(1.0, 0.001, 0.000002), - 0.0f, 8.0f, true, options); + Effect* effect = getLightEffect(16.0f, osg::Vec3(1.0, 0.001, 0.0002), + 1.0f, 16.0f, true, options); for (unsigned int i = 0; i < lights.getNumLights(); ++i) { EffectGeode* egeode = new EffectGeode; egeode->setEffect(effect); diff --git a/simgear/scene/tgdb/pt_lights.hxx b/simgear/scene/tgdb/pt_lights.hxx index 1097c4b6..005aea1d 100644 --- a/simgear/scene/tgdb/pt_lights.hxx +++ b/simgear/scene/tgdb/pt_lights.hxx @@ -87,6 +87,9 @@ public: static osg::Node* getSequenced(const SGDirectionalLightBin& lights, const simgear::SGReaderWriterOptions* options); + static osg::Node* + getReil(const SGDirectionalLightBin& lights, const simgear::SGReaderWriterOptions* options); + static osg::Node* getOdal(const SGLightBin& lights, const simgear::SGReaderWriterOptions* options); diff --git a/simgear/scene/viewer/ClusteredShading.cxx b/simgear/scene/viewer/ClusteredShading.cxx index e42883a2..ba521a4d 100644 --- a/simgear/scene/viewer/ClusteredShading.cxx +++ b/simgear/scene/viewer/ClusteredShading.cxx @@ -27,6 +27,7 @@ #include +#include #include namespace simgear { @@ -40,7 +41,7 @@ const int MAX_SPOTLIGHTS = 256; // It must be a multiple of the size of a vec4 as per the std140 layout rules. // See https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_uniform_buffer_object.txt const int POINTLIGHT_BLOCK_SIZE = 20; -const int SPOTLIGHT_BLOCK_SIZE = 8; +const int SPOTLIGHT_BLOCK_SIZE = 28; ClusteredShading::ClusteredShading(osg::Camera *camera, const SGPropertyNode *config) : @@ -180,12 +181,38 @@ ClusteredShading::update(const SGLightList &light_list) PointlightBound point; point.light = light; point.position = osg::Vec4f(0.0f, 0.0f, 0.0f, 1.0f) * - osg::computeLocalToWorld(light->getParentalNodePaths()[0]) * - _camera->getViewMatrix(); + // The parenthesis are very important: if the vector is + // multiplied by the local to world matrix first we'll have + // precision issues + (osg::computeLocalToWorld(light->getParentalNodePaths()[0]) * + _camera->getViewMatrix()); point.range = light->getRange(); + _point_bounds.push_back(point); } else if (light->getType() == SGLight::Type::SPOT) { + SpotlightBound spot; + spot.light = light; + spot.position = osg::Vec4f(0.0f, 0.0f, 0.0f, 1.0f) * + (osg::computeLocalToWorld(light->getParentalNodePaths()[0]) * + _camera->getViewMatrix()); + spot.direction = osg::Vec4f(0.0f, 0.0f, -1.0f, 0.0f) * + (osg::computeLocalToWorld(light->getParentalNodePaths()[0]) * + _camera->getViewMatrix()); + float range = light->getRange(); + float angle = light->getSpotCutoff() * SG_DEGREES_TO_RADIANS; + spot.cos_cutoff = cos(angle); + if(angle > SGD_PI_4) { + spot.bounding_sphere.radius = range * tan(angle); + } else { + spot.bounding_sphere.radius = + range * 0.5f / pow(spot.cos_cutoff, 2.0f); + } + + spot.bounding_sphere.center = + spot.position + spot.direction * spot.bounding_sphere.radius; + + _spot_bounds.push_back(spot); } } if (_point_bounds.size() > MAX_POINTLIGHTS || @@ -265,8 +292,9 @@ ClusteredShading::update(const SGLightList &light_list) _light_grid->dirty(); _light_indices->dirty(); - // Upload pointlight data + // Upload pointlight and spotlight data writePointlightData(); + writeSpotlightData(); } void @@ -301,6 +329,7 @@ ClusteredShading::assignLightsToSlice(int slice) GLuint local_point_count = 0; GLuint local_spot_count = 0; + // Test point lights for (GLushort point_iterator = 0; point_iterator < _point_bounds.size(); ++point_iterator) { @@ -328,25 +357,40 @@ ClusteredShading::assignLightsToSlice(int slice) } } + // Test spot lights + for (GLushort spot_iterator = 0; + spot_iterator < _spot_bounds.size(); + ++spot_iterator) { + SpotlightBound spot = _spot_bounds[spot_iterator]; + + // Perform frustum-sphere collision tests + float distance = 0.0f; + for (int j = 0; j < 6; j++) { + distance = subfrustum.plane[j] * spot.bounding_sphere.center + + spot.bounding_sphere.radius; + if (distance <= 0.0f) + break; + } + + if (distance > 0.0f) { + // Update light index list + indices[_global_light_count] = spot_iterator; + ++local_spot_count; + ++_global_light_count; // Atomic increment + } + + if (_global_light_count >= MAX_LIGHT_INDICES) { + throw sg_range_exception( + "Clustered shading light index count is over the hardcoded limit (" + + std::to_string(MAX_LIGHT_INDICES) + ")"); + } + } + // Update light grid grid[(z_offset + i) * 3 + 0] = start_offset; grid[(z_offset + i) * 3 + 1] = local_point_count; grid[(z_offset + i) * 3 + 2] = local_spot_count; } - - // for (int y = 0; y < _n_vtiles; ++y) { - // for (int x = 0; x < _n_htiles; ++x) { - // std::cout << grid[(y * _n_htiles + x) * 3 + 0] << "," - // << grid[(y * _n_htiles + x) * 3 + 1] << " "; - // } - // std::cout << std::endl; - // } - // std::cout << "\n\n"; - - // for (int i = 0; i < n_vtiles * n_htiles; ++i) { - // std::cout << indices[i] << " "; - // } - // std::cout << "\n"; } void @@ -385,6 +429,51 @@ ClusteredShading::writePointlightData() _pointlight_data->dirty(); } +void +ClusteredShading::writeSpotlightData() +{ + GLfloat *data = reinterpret_cast(&(*_spotlight_data)[0]); + + for (const auto &spot : _spot_bounds) { + // vec4 position + *data++ = spot.position.x(); + *data++ = spot.position.y(); + *data++ = spot.position.z(); + *data++ = 1.0f; + // vec4 direction + *data++ = spot.direction.x(); + *data++ = spot.direction.y(); + *data++ = spot.direction.z(); + *data++ = 0.0f; + // vec4 ambient + *data++ = spot.light->getAmbient().x(); + *data++ = spot.light->getAmbient().y(); + *data++ = spot.light->getAmbient().z(); + *data++ = spot.light->getAmbient().w(); + // vec4 diffuse + *data++ = spot.light->getDiffuse().x(); + *data++ = spot.light->getDiffuse().y(); + *data++ = spot.light->getDiffuse().z(); + *data++ = spot.light->getDiffuse().w(); + // vec4 specular + *data++ = spot.light->getSpecular().x(); + *data++ = spot.light->getSpecular().y(); + *data++ = spot.light->getSpecular().z(); + *data++ = spot.light->getSpecular().w(); + // vec4 attenuation (x = constant, y = linear, z = quadratic, w = range) + *data++ = spot.light->getConstantAttenuation(); + *data++ = spot.light->getLinearAttenuation(); + *data++ = spot.light->getQuadraticAttenuation(); + *data++ = spot.light->getRange(); + // float cos_cutoff + *data++ = spot.cos_cutoff; + // float exponent + *data++ = spot.light->getSpotExponent(); + // Needs 2N padding (8 bytes) + } + _spotlight_data->dirty(); +} + float ClusteredShading::getDepthForSlice(int slice) const { diff --git a/simgear/scene/viewer/ClusteredShading.hxx b/simgear/scene/viewer/ClusteredShading.hxx index 4843a102..fed11f1f 100644 --- a/simgear/scene/viewer/ClusteredShading.hxx +++ b/simgear/scene/viewer/ClusteredShading.hxx @@ -42,19 +42,25 @@ protected: }; struct PointlightBound { - SGLight *light; + SGLight *light = nullptr; osg::Vec4f position; - float range; + float range = 0.0f; }; struct SpotlightBound { - SGLight *light; + SGLight *light = nullptr; osg::Vec4f position; - float range; + osg::Vec4f direction; + float cos_cutoff = 0.0f; + struct { + osg::Vec4f center; + float radius = 0.0f; + } bounding_sphere; }; void threadFunc(int thread_id); void assignLightsToSlice(int slice); void writePointlightData(); + void writeSpotlightData(); float getDepthForSlice(int slice) const; osg::observer_ptr _camera; diff --git a/simgear/scene/viewer/CompositorPass.cxx b/simgear/scene/viewer/CompositorPass.cxx index 7e82765d..2187a494 100644 --- a/simgear/scene/viewer/CompositorPass.cxx +++ b/simgear/scene/viewer/CompositorPass.cxx @@ -36,6 +36,25 @@ #include "Compositor.hxx" #include "CompositorUtil.hxx" +namespace { +osgUtil::RenderBin::RenderBinList +removeTransparentBins(simgear::EffectCullVisitor *cv) +{ + osgUtil::RenderBin::RenderBinList transparent_bins; + osgUtil::RenderStage *stage = cv->getRenderStage(); + osgUtil::RenderBin::RenderBinList &rbl = stage->getRenderBinList(); + for (auto rbi = rbl.begin(); rbi != rbl.end(); ) { + if (rbi->second->getSortMode() == osgUtil::RenderBin::SORT_BACK_TO_FRONT) { + transparent_bins.insert(std::make_pair(rbi->first, rbi->second)); + rbl.erase(rbi++); + } else { + ++rbi; + } + } + return transparent_bins; +} +} // anonymous namespace + namespace simgear { namespace compositor { @@ -112,15 +131,15 @@ PassBuilder::build(Compositor *compositor, const SGPropertyNode *root, camera->setClearStencil(root->getIntValue("clear-stencil", 0)); GLbitfield clear_mask = 0; - if (root->getBoolValue("clear-color-bit", true)) - clear_mask |= GL_COLOR_BUFFER_BIT; - if (root->getBoolValue("clear-accum-bit", false)) - clear_mask |= GL_ACCUM_BUFFER_BIT; - if (root->getBoolValue("clear-depth-bit", true)) - clear_mask |= GL_DEPTH_BUFFER_BIT; - if (root->getBoolValue("clear-stencil-bit", false)) - clear_mask |= GL_STENCIL_BUFFER_BIT; - // Default clear mask is GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, as in OSG + std::stringstream ss; + std::string bit; + // Default clear mask as in OSG + ss << root->getStringValue("clear-mask", "color depth"); + while (ss >> bit) { + if (bit == "color") clear_mask |= GL_COLOR_BUFFER_BIT; + else if (bit == "depth") clear_mask |= GL_DEPTH_BUFFER_BIT; + else if (bit == "stencil") clear_mask |= GL_STENCIL_BUFFER_BIT; + } camera->setClearMask(clear_mask); PropertyList p_bindings = root->getChildren("binding"); @@ -384,9 +403,12 @@ public: virtual void operator()(osg::Node *node, osg::NodeVisitor *nv) { osg::Camera *camera = static_cast(node); + EffectCullVisitor *cv = dynamic_cast(nv); traverse(node, nv); + removeTransparentBins(cv); + // The light matrix uniform is updated after the traverse in case the // OSG near/far plane calculations were enabled osg::Matrixf light_matrix = @@ -497,19 +519,18 @@ public: osg::Vec4 aim4 = osg::Vec4(bs.center(), 1.0) * view_inverse; osg::Vec3 aim(aim4.x(), aim4.y(), aim4.z()); - osg::Vec3 up(0.0f, 1.0f, 0.0f); osg::Matrixd &light_view_matrix = camera->getViewMatrix(); light_view_matrix.makeLookAt( - aim + (light_dir * bs.radius() * 2.0f), + aim + light_dir * (bs.radius() + 10.0f), aim, - aim); + osg::Vec3(0.0f, 0.0f, 1.0f)); osg::Matrixd &light_proj_matrix = camera->getProjectionMatrix(); light_proj_matrix.makeOrtho( -bs.radius(), bs.radius(), -bs.radius(), bs.radius(), - -bs.radius() * 6.0f, bs.radius() * 6.0f); + 1.0, bs.radius() * 10.0); // Do texel snapping to prevent flickering or shimmering. // We are using double precision vectors and matrices because in FG @@ -520,8 +541,8 @@ public: osg::Vec2d shadow_origin(shadow_origin4.x(), shadow_origin4.y()); shadow_origin = osg::Vec2d(shadow_origin.x() * _half_sm_size.x(), shadow_origin.y() * _half_sm_size.y()); - osg::Vec2d rounded_origin(std::round(shadow_origin.x()), - std::round(shadow_origin.y())); + osg::Vec2d rounded_origin(std::floor(shadow_origin.x()), + std::floor(shadow_origin.y())); osg::Vec2d rounding = rounded_origin - shadow_origin; rounding = osg::Vec2d(rounding.x() / _half_sm_size.x(), rounding.y() / _half_sm_size.y()); @@ -546,8 +567,7 @@ struct ShadowMapPassBuilder : public PassBuilder { osg::Camera *camera = pass->camera; camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); - camera->setCullingMode(camera->getCullingMode() & - ~osg::CullSettings::SMALL_FEATURE_CULLING); + camera->setCullingMode(osg::CullSettings::ENABLE_ALL_CULLING); //camera->setComputeNearFarMode( // osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); diff --git a/simgear/sound/CMakeLists.txt b/simgear/sound/CMakeLists.txt index 884e9520..c63556bb 100644 --- a/simgear/sound/CMakeLists.txt +++ b/simgear/sound/CMakeLists.txt @@ -38,28 +38,13 @@ if(ENABLE_TESTS AND ENABLE_SOUND) COMPILE_DEFINITIONS "SRC_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}\"" ) endfunction() - set( SOUND_TEST_LIBS - ${TEST_LIBS} - ) + set( SOUND_TEST_LIBS ${TEST_LIBS} ) if (USE_AEONWAVE) - if (SIMGEAR_SHARED) - else() - set(SOUND_TEST_LIBS ${SOUND_TEST_LIBS} - ${AAX_LIBRARY} - ) - endif() - + set(SOUND_TEST_LIBS ${SOUND_TEST_LIBS} ${AAX_LIBRARY}) create_test(aeonwave_test1) - else () - if (SIMGEAR_SHARED) - else() - set(SOUND_TEST_LIBS ${SOUND_TEST_LIBS} - ${OPENAL_LIBRARY} - ) - endif() - + set(SOUND_TEST_LIBS ${SOUND_TEST_LIBS} ${OPENAL_LIBRARY}) create_test(openal_test1) endif() diff --git a/simgear/sound/sample.cxx b/simgear/sound/sample.cxx index 89c26056..e3db3445 100644 --- a/simgear/sound/sample.cxx +++ b/simgear/sound/sample.cxx @@ -4,7 +4,7 @@ // Modified to match the new SoundSystem by Erik Hofman, October 2009 // // Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt -// Copyright (C) 2009 Erik Hofman +// Copyright (C) 2009-2019 Erik Hofman // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as @@ -49,24 +49,6 @@ using std::string; // empty constructor SGSoundSampleInfo::SGSoundSampleInfo() : _refname(random_string()), - _bits(16), - _tracks(1), - _samples(0), - _frequency(22500), - _compressed(false), - _loop(false), - _static_changed(true), - _playing(false), - _pitch(1.0f), - _volume(1.0f), - _master_volume(1.0f), - _use_pos_props(false), - _out_of_range(false), - _inner_angle(360.0f), - _outer_angle(360.0f), - _outer_gain(0.0f), - _reference_dist(500.0f), - _max_dist(3000.0f), _absolute_pos(SGVec3d::zeros()), _relative_pos(SGVec3d::zeros()), _direction(SGVec3d::zeros()), @@ -97,27 +79,9 @@ std::string SGSoundSampleInfo::random_string() // SGSoundSample // -// empty constructor -SGSoundSample::SGSoundSample() : - _is_file(false), - _changed(true), - _valid_source(false), - _source(SGSoundMgr::NO_SOURCE), - _data(NULL), - _valid_buffer(false), - _buffer(SGSoundMgr::NO_BUFFER) -{ -} - // constructor SGSoundSample::SGSoundSample(const char *file, const SGPath& currentDir) : - _is_file(true), - _changed(true), - _valid_source(false), - _source(SGSoundMgr::NO_SOURCE), - _data(NULL), - _valid_buffer(false), - _buffer(SGSoundMgr::NO_BUFFER) + _is_file(true) { SGPath p = simgear::ResourceManager::instance()->findPath(file, currentDir); _refname = p.utf8Str(); @@ -125,13 +89,7 @@ SGSoundSample::SGSoundSample(const char *file, const SGPath& currentDir) : // constructor SGSoundSample::SGSoundSample( const unsigned char** data, - int len, int freq, int format ) : - _is_file(false), - _changed(true), - _valid_source(false), - _source(SGSoundMgr::NO_SOURCE), - _valid_buffer(false), - _buffer(SGSoundMgr::NO_BUFFER) + int len, int freq, int format ) { SG_LOG( SG_SOUND, SG_DEBUG, "In memory sounds sample" ); _data = (unsigned char*)*data; *data = NULL; diff --git a/simgear/sound/sample.hxx b/simgear/sound/sample.hxx index 4329a273..a67524d7 100644 --- a/simgear/sound/sample.hxx +++ b/simgear/sound/sample.hxx @@ -1,10 +1,10 @@ // sample.hxx -- Audio sample encapsulation class -// +// // Written by Curtis Olson, started April 2004. // Modified to match the new SoundSystem by Erik Hofman, October 2009 // // Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt -// Copyright (C) 2009 Erik Hofman +// Copyright (C) 2009-2019 Erik Hofman // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as @@ -32,7 +32,8 @@ #include #include - +#include "soundmgr.hxx" + enum { SG_SAMPLE_MONO = 1, SG_SAMPLE_STEREO = 2, @@ -76,7 +77,7 @@ public: * Returns the block alignment of this audio sample. * @return block alignment in bytes */ - inline unsigned int get_block_align() { + inline unsigned int get_block_align() { return _block_align; } @@ -164,10 +165,28 @@ public: /** * Get maximum distance (in meters) of this sound. * This is the distance where this sound is no longer audible. - * @return dist Maximum distance + * @return Maximum distance */ inline float get_max_dist() { return _max_dist; } + /** + * Get the temperature (in degrees Celsius) at the current altitude. + * @return temperature in degrees Celsius + */ + inline float get_temperature() { return _degC; } + + /** + * Get the relative humidity at the current altitude. + * @return Percent relative humidity (0.0 to 1.0) + */ + inline float get_humidity() { return _humidity; } + + /** + * Get the pressure at the current altitude. + * @return Pressure in kPa + */ + inline float get_pressure() { return _pressure; } + /** * Test if static data of audio sample configuration has changed. * Calling this function will reset the flag so calling it a second @@ -181,32 +200,35 @@ public: protected: // static sound emitter info std::string _refname; - unsigned int _bits; - unsigned int _tracks; - unsigned int _samples; - unsigned int _frequency; - unsigned int _block_align; - bool _compressed; - bool _loop; + unsigned int _bits = 16; + unsigned int _tracks = 1; + unsigned int _samples = 0; + unsigned int _frequency = 22500; + unsigned int _block_align = 2; + bool _compressed = false; + bool _loop = false; // dynamic sound emitter info (non 3d) - bool _static_changed; - bool _playing; + bool _static_changed = true; + bool _playing = false; - float _pitch; - float _volume; - float _master_volume; + float _pitch = 1.0f; + float _volume = 1.0f; + float _master_volume = 1.0f; // dynamic sound emitter info (3d) - bool _use_pos_props; - bool _out_of_range; + bool _use_pos_props = false; + bool _out_of_range = false; - float _inner_angle; - float _outer_angle; - float _outer_gain; + float _inner_angle = 360.0f; + float _outer_angle = 360.0f; + float _outer_gain = 0.0f; - float _reference_dist; - float _max_dist; + float _reference_dist = 500.0f; + float _max_dist = 3000.0f; + float _pressure = 101.325f; + float _humidity = 0.5f; + float _degC = 20.0f; SGPropertyNode_ptr _pos_prop[3]; SGVec3d _absolute_pos; // absolute position @@ -233,7 +255,7 @@ public: * Empty constructor, can be used to read data to the systems * memory and not to the driver. */ - SGSoundSample(); + SGSoundSample() {}; /** * Constructor @@ -307,7 +329,7 @@ public: */ inline void play_once() { play(false); } - /** + /** * Schedule this audio sample to play looped. * @see #play */ @@ -389,7 +411,7 @@ public: */ inline void set_buffer(unsigned int bid) { _buffer = bid; _valid_buffer = true; _changed = true; - } + } /** * Get the buffer-id of this source @@ -409,7 +431,7 @@ public: inline void no_valid_buffer() { _valid_buffer = false; } /** - * Set the playback pitch of this audio sample. + * Set the playback pitch of this audio sample. * Should be between 0.0 and 2.0 for maximum compatibility. * @param p Pitch */ @@ -540,6 +562,20 @@ public: _velocity = vel; _changed = true; } + /** + * Set both the temperature and relative humidity at the current altitude. + * @param t Temperature in degrees Celsius + * @param h Percent relative humidity (0.0 to 1.0) + * @param p Pressure in kPa; + */ + inline void set_atmosphere(float t, float h, float p) { + if (fabsf(_degC - t) > 1.0f || fabsf(_humidity - h) > 0.1f || + fabsf(_pressure - p) > 1.0f) + { + _degC = t, _humidity = h; _pressure = p; _static_changed = true; + } + } + /** * Set reference distance (in meters) of this sound. * This is the distance where the gain will be half. @@ -563,19 +599,19 @@ public: void update_pos_and_orientation(); protected: - bool _is_file; - bool _changed; + bool _is_file = false; + bool _changed = true; // Sources are points emitting sound. - bool _valid_source; - unsigned int _source; + bool _valid_source = false; + unsigned int _source = SGSoundMgr::NO_SOURCE; private: - unsigned char* _data; + unsigned char* _data = NULL; // Buffers hold sound data. - bool _valid_buffer; - unsigned int _buffer; + bool _valid_buffer = false; + unsigned int _buffer = SGSoundMgr::NO_BUFFER; }; #endif // _SG_SAMPLE_HXX diff --git a/simgear/sound/sample_group.cxx b/simgear/sound/sample_group.cxx index 12bbd3ba..d303dee5 100644 --- a/simgear/sound/sample_group.cxx +++ b/simgear/sound/sample_group.cxx @@ -2,7 +2,7 @@ // // Written for the new SoundSystem by Erik Hofman, October 2009 // -// Copyright (C) 2009 Erik Hofman +// Copyright (C) 2009-2019 Erik Hofman // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as @@ -31,16 +31,7 @@ #include "soundmgr.hxx" #include "sample_group.hxx" -SGSampleGroup::SGSampleGroup () : - _smgr(NULL), - _refname(""), - _active(false), - _changed(false), - _pause(false), - _volume(1.0), - _tied_to_listener(false), - _velocity(SGVec3d::zeros()), - _orientation(SGQuatd::zeros()) +SGSampleGroup::SGSampleGroup () { _samples.clear(); } @@ -48,14 +39,7 @@ SGSampleGroup::SGSampleGroup () : SGSampleGroup::SGSampleGroup ( SGSoundMgr *smgr, const std::string &refname ): _smgr(smgr), - _refname(refname), - _active(false), - _changed(false), - _pause(false), - _volume(1.0), - _tied_to_listener(false), - _velocity(SGVec3d::zeros()), - _orientation(SGQuatd::zeros()) + _refname(refname) { _smgr->add(this, refname); _samples.clear(); @@ -318,6 +302,7 @@ void SGSampleGroup::update_pos_and_orientation() { sample->set_rotation( ec2body ); sample->set_position(base_position); sample->set_velocity( velocity ); + sample->set_atmosphere( _degC, _humidity, _pressure ); // Test if a sample is farther away than max distance, if so // stop the sound playback and free it's source. diff --git a/simgear/sound/sample_group.hxx b/simgear/sound/sample_group.hxx index a40dee47..be63224d 100644 --- a/simgear/sound/sample_group.hxx +++ b/simgear/sound/sample_group.hxx @@ -6,7 +6,7 @@ // // Written for the new SoundSystem by Erik Hofman, October 2009 // -// Copyright (C) 2009 Erik Hofman +// Copyright (C) 2009-2019 Erik Hofman // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public @@ -131,7 +131,7 @@ public: * @return true if the audio sample exsists and is scheduled for playing */ bool play( const std::string& refname, bool looping = false ); - + /** * Request to start playing the referred audio sample looping. * @param refname Reference name of the audio sample to start playing @@ -173,7 +173,7 @@ public: /** * Set the velocity vector of this sample group. * This is in the local frame coordinate system; x=north, y=east, z=down - * @param vel Velocity vector + * @param vel Velocity vector */ void set_velocity( const SGVec3d& vel ) { _velocity = vel; _changed = true; @@ -196,29 +196,43 @@ public: _orientation = ori; _changed = true; } + /** + * Set both the temperature and relative humidity at the current altitude. + * @param t Temperature in degrees Celsius + * @param h Percent relative humidity (0.0 to 1.0) + * @param p Pressure in kPa + */ + void set_atmosphere(float t, float h, float p ) { + _degC = t, _humidity = h; _pressure = p; _changed = true; + } + /** * Tie this sample group to the listener position, orientation and velocity */ void tie_to_listener() { _tied_to_listener = true; } protected: - SGSoundMgr *_smgr; - std::string _refname; - bool _active; + SGSoundMgr *_smgr = NULL; + std::string _refname = ""; + bool _active = false; private: void cleanup_removed_samples(); void start_playing_sample(SGSoundSample *sample); void check_playing_sample(SGSoundSample *sample); - - bool _changed; - bool _pause; - float _volume; - bool _tied_to_listener; - SGVec3d _velocity; + bool _changed = false; + bool _pause = false; + float _volume = 1.0f; + float _pressure = 101.325f; + float _humidity = 0.5f; + float _degC = 20.0f; + + bool _tied_to_listener = false; + + SGVec3d _velocity = SGVec3d::zeros(); + SGQuatd _orientation = SGQuatd::zeros(); SGGeod _base_pos; - SGQuatd _orientation; sample_map _samples; std::vector< SGSharedPtr > _removed_samples; diff --git a/simgear/sound/soundmgr.hxx b/simgear/sound/soundmgr.hxx index 6837a35f..a3360dfa 100644 --- a/simgear/sound/soundmgr.hxx +++ b/simgear/sound/soundmgr.hxx @@ -11,7 +11,7 @@ // Modified for the new SoundSystem by Erik Hofman, October 2009 // // Copyright (C) 2001 Curtis L. Olson - http://www.flightgear.org/~curt -// Copyright (C) 2009 Erik Hofman +// Copyright (C) 2009-2019 Erik Hofman // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public @@ -68,7 +68,7 @@ public: * Select a specific sound device. * Requires a init/reinit call before sound is actually switched. */ - inline void select_device(const char* devname) {_device_name = devname;} + inline void select_device(const char* devname) { _device_name = devname; } /** * Test is the sound manager is in a working condition. diff --git a/simgear/sound/soundmgr_aeonwave.cxx b/simgear/sound/soundmgr_aeonwave.cxx index 111fa10e..7a0fa32f 100644 --- a/simgear/sound/soundmgr_aeonwave.cxx +++ b/simgear/sound/soundmgr_aeonwave.cxx @@ -191,7 +191,7 @@ void SGSoundMgr::init() TRY( d->_aax.set(dsp) ); dsp = aax::dsp(d->_aax, AAX_DISTANCE_FILTER); - TRY( dsp.set(AAX_AL_INVERSE_DISTANCE_CLAMPED) ); + TRY( dsp.set(AAX_ISO9613_DISTANCE) ); TRY( d->_aax.set(dsp) ); dsp = aax::dsp(d->_aax, AAX_VELOCITY_EFFECT); @@ -257,7 +257,7 @@ void SGSoundMgr::stop() if (is_working()) { _active = false; - TRY( d->_aax.set(AAX_STOPPED) ); + TRY( d->_aax.set(AAX_PROCESSED) ); _renderer = "unknown"; _vendor = "unknown"; @@ -313,12 +313,6 @@ void SGSoundMgr::update( double dt ) TRY( dsp.set(AAX_GAIN, _volume) ); TRY( d->_aax.set(dsp) ); -#if 0 - // TODO: altitude dependent - dsp = d->_aax.get(AAX_VELOCITY_EFFECT); - TRY( dsp.set(AAX_SOUND_VELOCITY, 340.3f) ); - TRY( d->_aax.set(dsp) ); -#endif aax::Matrix64 mtx = d->_mtx; mtx.inverse(); TRY( d->_aax.sensor_matrix(mtx) ); @@ -417,11 +411,8 @@ void SGSoundMgr::release_source( unsigned int source ) if ( source_it != d->_sources.end() ) { aax::Emitter& emitter = source_it->second; - enum aaxState state = emitter.state(); - if (state != AAX_PROCESSED) { - TRY( emitter.set(AAX_PROCESSED) ); - TRY( d->_aax.remove(emitter) ); - } + TRY( emitter.set(AAX_PROCESSED) ); + TRY( d->_aax.remove(emitter) ); TRY( emitter.remove_buffer() ); d->_sources.erase(source_it); } @@ -568,7 +559,7 @@ void SGSoundMgr::sample_play( SGSoundSample *sample ) aax::dsp dsp = emitter.get(AAX_DISTANCE_FILTER); TRY( dsp.set(AAX_ROLLOFF_FACTOR, 0.3f) ); - TRY( dsp.set(AAX_AL_INVERSE_DISTANCE_CLAMPED) ); + TRY( dsp.set(AAX_ISO9613_DISTANCE) ); TRY( emitter.set(dsp) ); TRY( emitter.set(AAX_LOOPING, sample->is_looping()) ); @@ -627,7 +618,7 @@ bool SGSoundMgr::is_sample_stopped(SGSoundSample *sample) assert(sample->is_valid_source()); aax::Emitter& emitter = d->get_emitter(sample->get_source()); int result = emitter.state(); - return (result == AAX_STOPPED); + return (result == AAX_PROCESSED); #else return true; #endif @@ -657,15 +648,18 @@ void SGSoundMgr::update_sample_config( SGSoundSample *sample, SGVec3d& position, TRY( emitter.set(dsp) ); if ( sample->has_static_data_changed() ) { - dsp = emitter.get(AAX_ANGULAR_FILTER); - TRY( dsp.set(AAX_INNER_ANGLE, sample->get_innerangle()) ); - TRY( dsp.set(AAX_OUTER_ANGLE, sample->get_outerangle()) ); + dsp = emitter.get(AAX_DIRECTIONAL_FILTER); + TRY( dsp.set(AAX_INNER_ANGLE, sample->get_innerangle(), AAX_DEGREES) ); + TRY( dsp.set(AAX_OUTER_ANGLE, sample->get_outerangle(), AAX_DEGREES) ); TRY( dsp.set(AAX_OUTER_GAIN, sample->get_outergain()) ); TRY( emitter.set(dsp) ); dsp = emitter.get(AAX_DISTANCE_FILTER); TRY( dsp.set(AAX_REF_DISTANCE, sample->get_reference_dist()) ); TRY( dsp.set(AAX_MAX_DISTANCE, sample->get_max_dist()) ); + TRY( dsp.set(AAX_RELATIVE_HUMIDITY, sample->get_humidity()) ); + TRY( dsp.set(AAX_TEMPERATURE, sample->get_temperature(), + AAX_DEGREES_CELSIUS) ); TRY( emitter.set(dsp) ); } } @@ -693,6 +687,7 @@ vector SGSoundMgr::get_available_devices() } } } + testForError("get_available_devices"); #endif return devices; } diff --git a/simgear/sound/soundmgr_test.cxx b/simgear/sound/soundmgr_test.cxx index a9004890..68c075f3 100644 --- a/simgear/sound/soundmgr_test.cxx +++ b/simgear/sound/soundmgr_test.cxx @@ -29,6 +29,9 @@ int main( int argc, char *argv[] ) { smgr->set_volume(0.9); smgr->activate(); + // prevent NaNs + smgr->set_position( SGVec3d(0, 0, 0), SGGeod::fromDegFt(0, 0, 0) ); + SGPath srcDir(SRC_DIR); SGSoundSample *sample1 = new SGSoundSample("jet.wav", srcDir); diff --git a/simgear/sound/soundmgr_test2.cxx b/simgear/sound/soundmgr_test2.cxx index bab5c39a..373f616e 100644 --- a/simgear/sound/soundmgr_test2.cxx +++ b/simgear/sound/soundmgr_test2.cxx @@ -30,6 +30,9 @@ int main( int argc, char *argv[] ) { SGPath srcDir(SRC_DIR); + // prevent NaNs + smgr->set_position( SGVec3d(0, 0, 0), SGGeod::fromDegFt(0, 0, 0) ); + printf("default position and orientation\n"); SGSoundSample *sample1 = new SGSoundSample("jet.wav", srcDir); sample1->set_volume(1.0); diff --git a/simgear/structure/SGExpression.cxx b/simgear/structure/SGExpression.cxx index e6b38500..4e2a4f83 100644 --- a/simgear/structure/SGExpression.cxx +++ b/simgear/structure/SGExpression.cxx @@ -95,9 +95,11 @@ SGReadValueFromString(const char* str, bool& value) SG_LOG(SG_IO, SG_ALERT, "Cannot read string content."); return false; } + std::stringstream s; s.str(std::string(str)); s >> value; + if (!s.fail()) return true; @@ -109,10 +111,12 @@ SGReadValueFromString(const char* str, bool& value) value = true; return true; } + if (stdstr == "false" || stdstr == "False" || stdstr == "FALSE") { - value = true; + value = false; return true; } + return false; }