Program Listing for File EnvironmentDescription.h
↰ Return to documentation for file (include/flamegpu/model/EnvironmentDescription.h
)
#ifndef INCLUDE_FLAMEGPU_MODEL_ENVIRONMENTDESCRIPTION_H_
#define INCLUDE_FLAMEGPU_MODEL_ENVIRONMENTDESCRIPTION_H_
#include <string>
#include <typeinfo>
#include <typeindex>
#include <utility>
#include <vector>
#include <memory>
#include "flamegpu/exception/FLAMEGPUException.h"
#include "flamegpu/runtime/environment/HostEnvironment.cuh"
#include "flamegpu/detail/Any.h"
#include "flamegpu/model/EnvironmentData.h"
#include "flamegpu/detail/type_decode.h"
#include "flamegpu/simulation/CUDAEnsemble.h"
namespace flamegpu {
class CEnvironmentDirectedGraphDescription;
class EnvironmentDirectedGraphDescription;
class CEnvironmentDescription {
friend struct EnvironmentData;
public:
explicit CEnvironmentDescription(std::shared_ptr<EnvironmentData> data);
explicit CEnvironmentDescription(std::shared_ptr<const EnvironmentData> data);
CEnvironmentDescription(const CEnvironmentDescription& other_agent) = default;
CEnvironmentDescription(CEnvironmentDescription&& other_agent) = default;
CEnvironmentDescription& operator=(const CEnvironmentDescription& other_agent) = default;
CEnvironmentDescription& operator=(CEnvironmentDescription&& other_agent) = default;
bool operator==(const CEnvironmentDescription& rhs) const;
bool operator!=(const CEnvironmentDescription& rhs) const;
template<typename T>
T getProperty(const std::string &name) const;
template<typename T, flamegpu::size_type N>
std::array<T, N> getProperty(const std::string &name) const;
template<typename T>
T getProperty(const std::string &name, flamegpu::size_type index) const;
#ifdef SWIG
template<typename T>
std::vector<T> getPropertyArray(const std::string &name) const;
#endif
bool getConst(const std::string &name) const;
CEnvironmentDirectedGraphDescription getDirectedGraph(const std::string& graph_name) const;
protected:
std::shared_ptr<EnvironmentData> environment;
};
class EnvironmentDescription : public CEnvironmentDescription {
public:
explicit EnvironmentDescription(std::shared_ptr<EnvironmentData> data);
EnvironmentDescription(const EnvironmentDescription& other_env) = default;
EnvironmentDescription(EnvironmentDescription&& other_env) = default;
EnvironmentDescription& operator=(const EnvironmentDescription& other_env) = default;
EnvironmentDescription& operator=(EnvironmentDescription&& other_env) = default;
template<typename T>
void newProperty(const std::string &name, T value, bool isConst = false);
template<typename T, flamegpu::size_type N>
void newProperty(const std::string &name, const std::array<T, N> &value, bool isConst = false);
#ifdef SWIG
template<typename T>
void newPropertyArray(const std::string &name, const std::vector<T> &value, const bool isConst = false);
#endif
template<typename T, flamegpu::size_type I = 1, flamegpu::size_type J = 1, flamegpu::size_type K = 1, flamegpu::size_type W = 1>
void newMacroProperty(const std::string& name);
#ifdef SWIG
template<typename T>
void newMacroProperty_swig(const std::string& name, flamegpu::size_type I = 1, flamegpu::size_type J = 1, flamegpu::size_type K = 1, flamegpu::size_type W = 1);
#endif
EnvironmentDirectedGraphDescription newDirectedGraph(const std::string &graph_name);
EnvironmentDirectedGraphDescription getDirectedGraph(const std::string& graph_name);
template<typename T>
T setProperty(const std::string &name, T value);
template<typename T, flamegpu::size_type N>
std::array<T, N> setProperty(const std::string &name, const std::array<T, N> &value);
template<typename T>
T setProperty(const std::string &name, flamegpu::size_type index, T value);
#ifdef SWIG
template<typename T>
std::vector<T> setPropertyArray(const std::string &name, const std::vector<T> &value);
#endif
private:
void newProperty(const std::string &name, const char *ptr, size_t length, bool isConst, flamegpu::size_type elements, const std::type_index &type);
};
template<typename T>
void EnvironmentDescription::newProperty(const std::string &name, T value, bool isConst) {
if (!name.empty() && name[0] == '_') {
THROW exception::ReservedName("Environment property names cannot begin with '_', this is reserved for internal usage, "
"in EnvironmentDescription::newProperty().");
}
// Limited to Arithmetic types
// Compound types would allow host pointers inside structs to be passed
static_assert(std::is_arithmetic<typename detail::type_decode<T>::type_t>::value || std::is_enum<typename detail::type_decode<T>::type_t>::value,
"Only arithmetic types can be used as environmental properties");
if (environment->properties.find(name) != environment->properties.end()) {
THROW exception::DuplicateEnvProperty("Environmental property with name '%s' already exists, "
"in EnvironmentDescription::newProperty().",
name.c_str());
}
newProperty(name, reinterpret_cast<const char*>(&value), sizeof(T), isConst, detail::type_decode<T>::len_t, typeid(typename detail::type_decode<T>::type_t));
}
template<typename T, flamegpu::size_type N>
void EnvironmentDescription::newProperty(const std::string &name, const std::array<T, N> &value, bool isConst) {
if (!name.empty() && name[0] == '_') {
THROW exception::ReservedName("Environment property names cannot begin with '_', this is reserved for internal usage, "
"in EnvironmentDescription::newProperty().");
}
static_assert(detail::type_decode<T>::len_t * N > 0, "Environment property arrays must have a length greater than 0.");
// Limited to Arithmetic types
// Compound types would allow host pointers inside structs to be passed
static_assert(std::is_arithmetic<typename detail::type_decode<T>::type_t>::value || std::is_enum<typename detail::type_decode<T>::type_t>::value,
"Only arithmetic types can be used as environmental properties");
if (environment->properties.find(name) != environment->properties.end()) {
THROW exception::DuplicateEnvProperty("Environmental property with name '%s' already exists, "
"in EnvironmentDescription::newProperty().",
name.c_str());
}
newProperty(name, reinterpret_cast<const char*>(value.data()), N * sizeof(T), isConst, detail::type_decode<T>::len_t * N, typeid(typename detail::type_decode<T>::type_t));
}
#ifdef SWIG
template<typename T>
void EnvironmentDescription::newPropertyArray(const std::string &name, const std::vector<T> &value, bool isConst) {
if (!name.empty() && name[0] == '_') {
THROW exception::ReservedName("Environment property names cannot begin with '_', this is reserved for internal usage, "
"in EnvironmentDescription::newPropertyArray().");
}
if (value.size() == 0) {
THROW exception::InvalidEnvProperty("Environment property arrays must have a length greater than 0."
"in EnvironmentDescription::newPropertyArray().");
}
// Limited to Arithmetic types
// Compound types would allow host pointers inside structs to be passed
static_assert(std::is_arithmetic<typename detail::type_decode<T>::type_t>::value || std::is_enum<typename detail::type_decode<T>::type_t>::value,
"Only arithmetic types can be used as environmental properties");
if (environment->properties.find(name) != environment->properties.end()) {
THROW exception::DuplicateEnvProperty("Environmental property with name '%s' already exists, "
"in EnvironmentDescription::newPropertyArray().",
name.c_str());
}
newProperty(name, reinterpret_cast<const char*>(value.data()), value.size() * sizeof(T), isConst, detail::type_decode<T>::len_t * value.size(), typeid(typename detail::type_decode<T>::type_t));
}
#endif
template<typename T>
T CEnvironmentDescription::getProperty(const std::string &name) const {
// Limited to Arithmetic types
// Compound types would allow host pointers inside structs to be passed
static_assert(std::is_arithmetic<typename detail::type_decode<T>::type_t>::value || std::is_enum<typename detail::type_decode<T>::type_t>::value,
"Only arithmetic types can be used as environmental properties");
auto &&i = environment->properties.find(name);
if (i != environment->properties.end()) {
if (i->second.data.type != std::type_index(typeid(typename detail::type_decode<T>::type_t))) {
THROW exception::InvalidEnvPropertyType("Environmental property ('%s') type (%s) does not match template argument T (%s), "
"in EnvironmentDescription::getProperty().",
name.c_str(), i->second.data.type.name(), typeid(typename detail::type_decode<T>::type_t).name());
}
if (i->second.data.elements != detail::type_decode<T>::len_t) {
THROW exception::InvalidEnvPropertyType("Length of named environmental property (%u) does not match vector length (%u), "
"in EnvironmentDescription::getProperty().",
i->second.data.elements, detail::type_decode<T>::len_t);
}
return *reinterpret_cast<T*>(i->second.data.ptr);
}
THROW exception::InvalidEnvProperty("Environmental property with name '%s' does not exist, "
"in EnvironmentDescription::getProperty().",
name.c_str());
}
template<typename T, flamegpu::size_type N>
std::array<T, N> CEnvironmentDescription::getProperty(const std::string &name) const {
// Limited to Arithmetic types
// Compound types would allow host pointers inside structs to be passed
static_assert(std::is_arithmetic<typename detail::type_decode<T>::type_t>::value || std::is_enum<typename detail::type_decode<T>::type_t>::value,
"Only arithmetic types can be used as environmental properties");
auto &&i = environment->properties.find(name);
if (i != environment->properties.end()) {
if (i->second.data.type != std::type_index(typeid(typename detail::type_decode<T>::type_t))) {
THROW exception::InvalidEnvPropertyType("Environmental property array ('%s') type (%s) does not match template argument T (%s), "
"in EnvironmentDescription::getProperty().",
name.c_str(), i->second.data.type.name(), typeid(typename detail::type_decode<T>::type_t).name());
}
if (i->second.data.elements != detail::type_decode<T>::len_t * N) {
THROW exception::InvalidEnvPropertyType("Length of named environmental property array (%u) does not match requested length (%u), "
"in EnvironmentDescription::getProperty().",
i->second.data.elements, detail::type_decode<T>::len_t * N);
}
// Copy old data to return
std::array<T, N> rtn;
memcpy(rtn.data(), reinterpret_cast<T*>(i->second.data.ptr), N * sizeof(T));
return rtn;
}
THROW exception::InvalidEnvProperty("Environmental property with name '%s' does not exist, "
"in EnvironmentDescription::getProperty().",
name.c_str());
}
template<typename T>
T CEnvironmentDescription::getProperty(const std::string &name, flamegpu::size_type index) const {
// Limited to Arithmetic types
// Compound types would allow host pointers inside structs to be passed
static_assert(std::is_arithmetic<typename detail::type_decode<T>::type_t>::value || std::is_enum<typename detail::type_decode<T>::type_t>::value,
"Only arithmetic types can be used as environmental properties");
auto &&i = environment->properties.find(name);
if (i != environment->properties.end()) {
if (i->second.data.type != std::type_index(typeid(typename detail::type_decode<T>::type_t))) {
THROW exception::InvalidEnvPropertyType("Environmental property array ('%s') type (%s) does not match template argument T (%s), "
"in EnvironmentDescription::getProperty().",
name.c_str(), i->second.data.type.name(), typeid(typename detail::type_decode<T>::type_t).name());
}
if (i->second.data.elements % detail::type_decode<T>::len_t != 0) {
THROW exception::InvalidEnvPropertyType("Environmental property array ('%s') length (%u) does not divide by vector length (%u), "
"in EnvironmentDescription::getPropertyArray().",
name.c_str(), i->second.data.elements, detail::type_decode<T>::len_t);
}
const unsigned int t_index = detail::type_decode<T>::len_t * index + detail::type_decode<T>::len_t;
if (i->second.data.elements < t_index || t_index < index) {
THROW exception::OutOfBoundsException("Index (%u) exceeds named environmental property array's length (%u), "
"in EnvironmentDescription::getProperty().",
index, i->second.data.elements / detail::type_decode<T>::len_t);
}
// Copy old data to return
return *(reinterpret_cast<T*>(i->second.data.ptr) + index);
}
THROW exception::InvalidEnvProperty("Environmental property with name '%s' does not exist, "
"in EnvironmentDescription::getProperty().",
name.c_str());
}
#ifdef SWIG
template<typename T>
std::vector<T> CEnvironmentDescription::getPropertyArray(const std::string& name) const {
// Limited to Arithmetic types
// Compound types would allow host pointers inside structs to be passed
static_assert(std::is_arithmetic<typename detail::type_decode<T>::type_t>::value || std::is_enum<typename detail::type_decode<T>::type_t>::value,
"Only arithmetic types can be used as environmental properties");
auto &&i = environment->properties.find(name);
if (i != environment->properties.end()) {
if (i->second.data.type != std::type_index(typeid(typename detail::type_decode<T>::type_t))) {
THROW exception::InvalidEnvPropertyType("Environmental property array ('%s') type (%s) does not match template argument T (%s), "
"in EnvironmentDescription::getPropertyArray().",
name.c_str(), i->second.data.type.name(), typeid(typename detail::type_decode<T>::type_t).name());
}
if (i->second.data.elements % detail::type_decode<T>::len_t != 0) {
THROW exception::InvalidEnvPropertyType("Environmental property array ('%s') length (%u) does not divide by vector length (%d), "
"in EnvironmentDescription::getPropertyArray().",
name.c_str(), i->second.data.elements, detail::type_decode<T>::len_t);
}
// Copy old data to return
std::vector<T> rtn(i->second.data.elements / detail::type_decode<T>::len_t);
memcpy(rtn.data(), reinterpret_cast<T*>(i->second.data.ptr), i->second.data.elements * sizeof(typename detail::type_decode<T>::type_t));
return rtn;
}
THROW exception::InvalidEnvProperty("Environmental property with name '%s' does not exist, "
"in EnvironmentDescription::getPropertyArray().",
name.c_str());
}
#endif
template<typename T>
T EnvironmentDescription::setProperty(const std::string &name, T value) {
if (!name.empty() && name[0] == '_') {
THROW exception::ReservedName("Environment property names cannot begin with '_', this is reserved for internal usage, "
"in EnvironmentDescription::setProperty().");
}
// Limited to Arithmetic types
// Compound types would allow host pointers inside structs to be passed
static_assert(std::is_arithmetic<typename detail::type_decode<T>::type_t>::value || std::is_enum<typename detail::type_decode<T>::type_t>::value,
"Only arithmetic types can be used as environmental properties");
auto &&i = environment->properties.find(name);
if (i != environment->properties.end()) {
if (i->second.data.type != std::type_index(typeid(typename detail::type_decode<T>::type_t))) {
THROW exception::InvalidEnvPropertyType("Environmental property ('%s') type (%s) does not match template argument T (%s), "
"in EnvironmentDescription::setProperty().",
name.c_str(), i->second.data.type.name(), typeid(typename detail::type_decode<T>::type_t).name());
}
if (i->second.data.elements != detail::type_decode<T>::len_t) {
THROW exception::InvalidEnvPropertyType("Length of named environmental property (%u) does not match vector length (%u), "
"in EnvironmentDescription::setProperty().",
i->second.data.elements, detail::type_decode<T>::len_t);
}
// Copy old data to return
T rtn = *reinterpret_cast<T*>(i->second.data.ptr);
// Store data
memcpy(i->second.data.ptr, &value, sizeof(T));
return rtn;
}
THROW exception::InvalidEnvProperty("Environmental property with name '%s' does not exist, "
"in EnvironmentDescription::setProperty().",
name.c_str());
}
template<typename T, flamegpu::size_type N>
std::array<T, N> EnvironmentDescription::setProperty(const std::string &name, const std::array<T, N> &value) {
if (!name.empty() && name[0] == '_') {
THROW exception::ReservedName("Environment property names cannot begin with '_', this is reserved for internal usage, "
"in EnvironmentDescription::setProperty().");
}
// Limited to Arithmetic types
// Compound types would allow host pointers inside structs to be passed
static_assert(std::is_arithmetic<typename detail::type_decode<T>::type_t>::value || std::is_enum<typename detail::type_decode<T>::type_t>::value,
"Only arithmetic types can be used as environmental properties");
auto &&i = environment->properties.find(name);
if (i != environment->properties.end()) {
if (i->second.data.type != std::type_index(typeid(typename detail::type_decode<T>::type_t))) {
THROW exception::InvalidEnvPropertyType("Environmental property array ('%s') type (%s) does not match template argument T (%s), "
"in EnvironmentDescription::setProperty().",
name.c_str(), i->second.data.type.name(), typeid(typename detail::type_decode<T>::type_t).name());
}
if (i->second.data.elements != N * detail::type_decode<T>::len_t) {
THROW exception::InvalidEnvPropertyType("Length of named environmental property array (%u) does not match requested length (%u), "
"in EnvironmentDescription::setProperty().",
i->second.data.elements, N * detail::type_decode<T>::len_t);
}
// Copy old data to return
std::array<T, N> rtn;
memcpy(rtn.data(), reinterpret_cast<T*>(i->second.data.ptr), N * sizeof(T));
// Store data
memcpy(reinterpret_cast<T*>(i->second.data.ptr), value.data(), N * sizeof(T));
return rtn;
}
THROW exception::InvalidEnvProperty("Environmental property with name '%s' does not exist, "
"in EnvironmentDescription::setProperty().",
name.c_str());
}
template<typename T>
T EnvironmentDescription::setProperty(const std::string &name, flamegpu::size_type index, T value) {
if (!name.empty() && name[0] == '_') {
THROW exception::ReservedName("Environment property names cannot begin with '_', this is reserved for internal usage, "
"in EnvironmentDescription::setProperty().");
}
// Limited to Arithmetic types
// Compound types would allow host pointers inside structs to be passed
static_assert(std::is_arithmetic<typename detail::type_decode<T>::type_t>::value || std::is_enum<typename detail::type_decode<T>::type_t>::value,
"Only arithmetic types can be used as environmental properties");
auto &&i = environment->properties.find(name);
if (i != environment->properties.end()) {
if (i->second.data.type != std::type_index(typeid(typename detail::type_decode<T>::type_t))) {
THROW exception::InvalidEnvPropertyType("Environmental property array ('%s') type (%s) does not match template argument T (%s), "
"in EnvironmentDescription::setProperty().",
name.c_str(), i->second.data.type.name(), typeid(typename detail::type_decode<T>::type_t).name());
}
if (i->second.data.elements % detail::type_decode<T>::len_t != 0) {
THROW exception::InvalidEnvPropertyType("Environmental property array ('%s') length (%u) does not divide by vector length (%u), "
"in EnvironmentDescription::setProperty().",
name.c_str(), i->second.data.elements, detail::type_decode<T>::len_t);
}
const unsigned int t_index = detail::type_decode<T>::len_t * index + detail::type_decode<T>::len_t;
if (i->second.data.elements < t_index || t_index < index) {
THROW exception::OutOfBoundsException("Index (%u) exceeds named environmental property array's length (%u), "
"in EnvironmentDescription::setProperty().",
index, i->second.data.elements / detail::type_decode<T>::len_t);
}
// Copy old data to return
T rtn = *(reinterpret_cast<T*>(i->second.data.ptr) + index);
// Store data
memcpy(reinterpret_cast<T*>(i->second.data.ptr) + index, &value, sizeof(T));
return rtn;
}
THROW exception::InvalidEnvProperty("Environmental property with name '%s' does not exist, "
"in EnvironmentDescription::setProperty().",
name.c_str());
}
#ifdef SWIG
template<typename T>
std::vector<T> EnvironmentDescription::setPropertyArray(const std::string& name, const std::vector<T>& value) {
if (!name.empty() && name[0] == '_') {
THROW exception::ReservedName("Environment property names cannot begin with '_', this is reserved for internal usage, "
"in EnvironmentDescription::set().");
}
// Limited to Arithmetic types
// Compound types would allow host pointers inside structs to be passed
static_assert(std::is_arithmetic<typename detail::type_decode<T>::type_t>::value || std::is_enum<typename detail::type_decode<T>::type_t>::value,
"Only arithmetic types can be used as environmental properties");
auto &&i = environment->properties.find(name);
if (i != environment->properties.end()) {
if (i->second.data.type != std::type_index(typeid(typename detail::type_decode<T>::type_t))) {
THROW exception::InvalidEnvPropertyType("Environmental property array ('%s') type (%s) does not match template argument T (%s), "
"in EnvironmentDescription::setPropertyArray().",
name.c_str(), i->second.data.type.name(), typeid(typename detail::type_decode<T>::type_t).name());
}
if (i->second.data.elements % detail::type_decode<T>::len_t != 0) {
THROW exception::InvalidEnvPropertyType("Environmental property array ('%s') length (%u) does not divide by vector length (%u), "
"in EnvironmentDescription::setPropertyArray().",
name.c_str(), i->second.data.elements, detail::type_decode<T>::len_t);
}
if (i->second.data.elements != value.size() * detail::type_decode<T>::len_t) {
THROW exception::OutOfBoundsException("Length of named environmental property array (%u) does not match length of provided vector (%llu), "
"in EnvironmentDescription::setPropertyArray().",
i->second.data.elements / detail::type_decode<T>::len_t, value.size());
}
// Copy old data to return
std::vector<T> rtn(i->second.data.elements / detail::type_decode<T>::len_t);
memcpy(rtn.data(), reinterpret_cast<T*>(i->second.data.ptr), i->second.data.elements * sizeof(typename detail::type_decode<T>::type_t));
// Store data
memcpy(reinterpret_cast<T*>(i->second.data.ptr), value.data(), i->second.data.elements * sizeof(typename detail::type_decode<T>::type_t));
return rtn;
}
THROW exception::InvalidEnvProperty("Environmental property with name '%s' does not exist, "
"in EnvironmentDescription::set().",
name.c_str());
}
#endif
template<typename T, flamegpu::size_type I, flamegpu::size_type J, flamegpu::size_type K, flamegpu::size_type W>
void EnvironmentDescription::newMacroProperty(const std::string& name) {
if (!name.empty() && name[0] == '_') {
THROW exception::ReservedName("Environment macro property names cannot begin with '_', this is reserved for internal usage, "
"in EnvironmentDescription::newMacroProperty().");
}
// Limited to Arithmetic types
// Compound types would allow host pointers inside structs to be passed
static_assert(std::is_arithmetic<T>::value || std::is_enum<T>::value,
"Only arithmetic types can be used as environmental macro properties");
static_assert(I > 0, "Environment macro properties must have a length greater than 0 in the first axis.");
static_assert(J > 0, "Environment macro properties must have a length greater than 0 in the second axis.");
static_assert(K > 0, "Environment macro properties must have a length greater than 0 in the third axis.");
static_assert(W > 0, "Environment macro properties must have a length greater than 0 in the fourth axis.");
if (environment->macro_properties.find(name) != environment->macro_properties.end()) {
THROW exception::DuplicateEnvProperty("Environmental macro property with name '%s' already exists, "
"in EnvironmentDescription::newMacroProperty().",
name.c_str());
}
environment->macro_properties.emplace(name, EnvironmentData::MacroPropData(typeid(T), sizeof(T), { I, J, K, W }));
}
#ifdef SWIG
template<typename T>
void EnvironmentDescription::newMacroProperty_swig(const std::string& name, flamegpu::size_type I, flamegpu::size_type J, flamegpu::size_type K, flamegpu::size_type W) {
if (!name.empty() && name[0] == '_') {
THROW exception::ReservedName("Environment macro property names cannot begin with '_', this is reserved for internal usage, "
"in EnvironmentDescription::newMacroProperty().");
}
// Limited to Arithmetic types
// Compound types would allow host pointers inside structs to be passed
static_assert(std::is_arithmetic<T>::value || std::is_enum<T>::value,
"Only arithmetic types can be used as environmental macro properties");
if (I <= 0) {
THROW exception::DuplicateEnvProperty("Environmental macro property with name '%s' must have a length greater than 0 in the first axis, "
"in EnvironmentDescription::newMacroProperty().",
name.c_str());
} else if (J <= 0) {
THROW exception::DuplicateEnvProperty("Environmental macro property with name '%s' must have a length greater than 0 in the second axis, "
"in EnvironmentDescription::newMacroProperty().",
name.c_str());
} else if (K <= 0) {
THROW exception::DuplicateEnvProperty("Environmental macro property with name '%s' must have a length greater than 0 in the third axis, "
"in EnvironmentDescription::newMacroProperty().",
name.c_str());
} else if (W <= 0) {
THROW exception::DuplicateEnvProperty("Environmental macro property with name '%s' must have a length greater than 0 in the fourth axis, "
"in EnvironmentDescription::newMacroProperty().",
name.c_str());
} else if (environment->macro_properties.find(name) != environment->macro_properties.end()) {
THROW exception::DuplicateEnvProperty("Environmental macro property with name '%s' already exists, "
"in EnvironmentDescription::newMacroProperty().",
name.c_str());
}
environment->macro_properties.emplace(name, EnvironmentData::MacroPropData(typeid(T), sizeof(T), { I, J, K, W }));
}
#endif
} // namespace flamegpu
#endif // INCLUDE_FLAMEGPU_MODEL_ENVIRONMENTDESCRIPTION_H_