diff --git a/data/assets/scene/solarsystem/planets/earth/satellites/misc/iss.asset b/data/assets/scene/solarsystem/planets/earth/satellites/misc/iss.asset index 4d3ae8016b..a21b3bd1a2 100644 --- a/data/assets/scene/solarsystem/planets/earth/satellites/misc/iss.asset +++ b/data/assets/scene/solarsystem/planets/earth/satellites/misc/iss.asset @@ -60,6 +60,7 @@ local initializeAndAddNodes = function() Renderable = { Type = "RenderableModel", GeometryFile = models .. "/ISS.fbx", + ModelScale = "Centimeter", LightSources = { { Type = "SceneGraphLightSource", diff --git a/ext/ghoul b/ext/ghoul index bddc5370d7..23b9673bfc 160000 --- a/ext/ghoul +++ b/ext/ghoul @@ -1 +1 @@ -Subproject commit bddc5370d7dd8bc8ab05f2a234a12f2d0e266ca4 +Subproject commit 23b9673bfc2e92cba4d4aa05c808aa833b150f8a diff --git a/include/openspace/documentation/verifier.h b/include/openspace/documentation/verifier.h index 7cde68a2aa..b973be4b5f 100644 --- a/include/openspace/documentation/verifier.h +++ b/include/openspace/documentation/verifier.h @@ -191,6 +191,17 @@ struct DirectoryVerifier : public StringVerifier { std::string type() const override; }; +/** + * A Verifier that checks whether a given key inside a ghoul::Dictionary is a string and + * a valid date time + */ +struct DateTimeVerifier : public StringVerifier { + TestResult operator()(const ghoul::Dictionary& dict, + const std::string& key) const override; + + std::string type() const override; +}; + /** * A Verifier that checks whether a given key inside a ghoul::Dictionary is another * ghoul::Dictionary. The constructor takes a list of DocumentationEntry%s, which are used diff --git a/include/openspace/util/distanceconstants.h b/include/openspace/util/distanceconstants.h index 695e463324..4184263485 100644 --- a/include/openspace/util/distanceconstants.h +++ b/include/openspace/util/distanceconstants.h @@ -35,6 +35,12 @@ namespace openspace::distanceconstants { constexpr double LightHour = LightDay / 24; constexpr double AstronomicalUnit = 1.495978707E11; constexpr double Parsec = 3.0856776E16; + + constexpr double Inch = 0.0254; + constexpr double Foot = 0.3048; + constexpr double Yard = 0.9144; + constexpr double Chain = 20.1168; + constexpr double Mile = 1609.344; } // openspace::distanceconstants #endif // __OPENSPACE_CORE___DISTANCECONSTANTS___H__ diff --git a/include/openspace/util/distanceconversion.h b/include/openspace/util/distanceconversion.h index 79258ba131..252948c720 100644 --- a/include/openspace/util/distanceconversion.h +++ b/include/openspace/util/distanceconversion.h @@ -39,6 +39,8 @@ enum class DistanceUnit { Nanometer = 0, Micrometer, Millimeter, + Centimeter, + Decimeter, Meter, Kilometer, AU, @@ -66,6 +68,8 @@ enum class DistanceUnit { constexpr const char* DistanceUnitNanometer = "nanometer"; constexpr const char* DistanceUnitMicrometer = "micrometer"; constexpr const char* DistanceUnitMillimeter = "millimeter"; +constexpr const char* DistanceUnitCentimeter = "centimeter"; +constexpr const char* DistanceUnitDecimeter = "decimeter"; constexpr const char* DistanceUnitMeter = "meter"; constexpr const char* DistanceUnitKilometer = "km"; constexpr const char* DistanceUnitAU = "AU"; @@ -91,6 +95,8 @@ constexpr const char* DistanceUnitLeague = "league"; constexpr const char* DistanceUnitNanometers = "nanometers"; constexpr const char* DistanceUnitMicrometers = "micrometers"; constexpr const char* DistanceUnitMillimeters = "millimeters"; +constexpr const char* DistanceUnitCentimeters = "centimeters"; +constexpr const char* DistanceUnitDecimeters = "decimeters"; constexpr const char* DistanceUnitMeters = "meters"; constexpr const char* DistanceUnitKilometers = "km"; constexpr const char* DistanceUnitAUs = "AU"; @@ -114,18 +120,20 @@ constexpr const char* DistanceUnitLeagues = "leagues"; constexpr const std::array(DistanceUnit::League) + 1> DistanceUnits = { DistanceUnit::Nanometer, DistanceUnit::Micrometer, DistanceUnit::Millimeter, - DistanceUnit::Meter, DistanceUnit::Kilometer, DistanceUnit::AU, - DistanceUnit::Lighthour, DistanceUnit::Lightday, DistanceUnit::Lightmonth, - DistanceUnit::Lightyear, DistanceUnit::Parsec, DistanceUnit::Kiloparsec, - DistanceUnit::Megaparsec, DistanceUnit::Gigaparsec, DistanceUnit::Thou, - DistanceUnit::Inch, DistanceUnit::Foot, DistanceUnit::Yard, DistanceUnit::Chain, - DistanceUnit::Furlong, DistanceUnit::Mile, DistanceUnit::League + DistanceUnit::Centimeter, DistanceUnit::Decimeter, DistanceUnit::Meter, + DistanceUnit::Kilometer, DistanceUnit::AU, DistanceUnit::Lighthour, + DistanceUnit::Lightday, DistanceUnit::Lightmonth, DistanceUnit::Lightyear, + DistanceUnit::Parsec, DistanceUnit::Kiloparsec, DistanceUnit::Megaparsec, + DistanceUnit::Gigaparsec, DistanceUnit::Thou, DistanceUnit::Inch, + DistanceUnit::Foot, DistanceUnit::Yard, DistanceUnit::Chain, DistanceUnit::Furlong, + DistanceUnit::Mile, DistanceUnit::League }; constexpr const std::array(DistanceUnit::League) + 1> DistanceUnitNamesSingular = { DistanceUnitNanometer, DistanceUnitMicrometer, DistanceUnitMillimeter, - DistanceUnitMeter, DistanceUnitKilometer, DistanceUnitAU, DistanceUnitLighthour, + DistanceUnitCentimeter, DistanceUnitDecimeter, DistanceUnitMeter, + DistanceUnitKilometer, DistanceUnitAU, DistanceUnitLighthour, DistanceUnitLightday, DistanceUnitLightmonth, DistanceUnitLightyear, DistanceUnitParsec, DistanceUnitKiloparsec, DistanceUnitMegaparsec, DistanceUnitGigaparsec, DistanceUnitThou, DistanceUnitInch, DistanceUnitFoot, @@ -136,7 +144,8 @@ DistanceUnitNamesSingular = { constexpr const std::array(DistanceUnit::League) + 1> DistanceUnitNamesPlural = { DistanceUnitNanometers, DistanceUnitMicrometers, DistanceUnitMillimeters, - DistanceUnitMeters, DistanceUnitKilometers, DistanceUnitAUs, DistanceUnitLighthours, + DistanceUnitCentimeters, DistanceUnitDecimeters, DistanceUnitMeters, + DistanceUnitKilometers, DistanceUnitAUs, DistanceUnitLighthours, DistanceUnitLightdays, DistanceUnitLightmonths, DistanceUnitLightyears, DistanceUnitParsecs, DistanceUnitKiloparsecs, DistanceUnitMegaparsecs, DistanceUnitGigaparsecs, DistanceUnitThous, DistanceUnitInches, DistanceUnitFeet, @@ -168,6 +177,8 @@ constexpr const char* nameForDistanceUnit(DistanceUnit unit, bool pluralForm = f case DistanceUnit::Nanometer: case DistanceUnit::Micrometer: case DistanceUnit::Millimeter: + case DistanceUnit::Centimeter: + case DistanceUnit::Decimeter: case DistanceUnit::Meter: case DistanceUnit::Kilometer: case DistanceUnit::AU: @@ -232,7 +243,7 @@ constexpr DistanceUnit distanceUnitFromString(const char* unitName) { std::pair simplifyDistance(double meters, bool forceSingularForm = false); -constexpr double convertDistance(double meters, DistanceUnit requestedUnit) { +constexpr double convertMeters(double meters, DistanceUnit requestedUnit) { switch (requestedUnit) { case DistanceUnit::Nanometer: return meters / 1e-9; @@ -240,6 +251,10 @@ constexpr double convertDistance(double meters, DistanceUnit requestedUnit) { return meters / 1e-6; case DistanceUnit::Millimeter: return meters / 1e-3; + case DistanceUnit::Centimeter: + return meters / 1e-2; + case DistanceUnit::Decimeter: + return meters / 1e-1; case DistanceUnit::Meter: return meters; case DistanceUnit::Kilometer: @@ -262,33 +277,90 @@ constexpr double convertDistance(double meters, DistanceUnit requestedUnit) { return meters / (1e6 * distanceconstants::Parsec); case DistanceUnit::Gigaparsec: return meters / (1e9 * distanceconstants::Parsec); - // Such wow, such coefficients case DistanceUnit::Thou: - return (meters * 1000.0 / 25.4) * 1000.0; // m -> mm -> inch -> thou + return meters / (1e-3 * distanceconstants::Inch); case DistanceUnit::Inch: - return (meters * 1000.0 / 25.4); // m -> mm -> inch + return meters / distanceconstants::Inch; case DistanceUnit::Foot: - return (meters * 1000.0 / 25.4) / 12.0; // m -> mm -> inch -> feet + return meters / distanceconstants::Foot; case DistanceUnit::Yard: - // m -> mm -> inch -> feet -> yard - return (meters * 1000.0 / 25.4) / 12.0 / 3.0; + return meters / distanceconstants::Yard; case DistanceUnit::Chain: - // m -> mm -> inch -> feet -> yard -> chain - return (meters * 1000.0 / 25.4) / 12.0 / 3.0 / 22.0; + return meters / distanceconstants::Chain; case DistanceUnit::Furlong: - // m -> mm -> inch -> feet -> yard -> chain -> furlong - return (meters * 1000.0 / 25.4) / 12.0 / 3.0 / 22.0 / 10.0; + return meters / (10.0 * distanceconstants::Chain); case DistanceUnit::Mile: - // m -> mm -> inch -> feet -> yard -> chain -> furlong -> mile - return (meters * 1000.0 / 25.4) / 12.0 / 3.0 / 22.0 / 10.0 / 8.0; + return meters / distanceconstants::Mile; case DistanceUnit::League: - // m -> mm -> inch -> feet -> yard -> chain -> furlong -> mile -> league - return (meters * 1000.0 / 25.4) / 12.0 / 3.0 / 22.0 / 10.0 / 8.0 / 3.0; + return meters / (3.0 * distanceconstants::Mile); default: throw ghoul::MissingCaseException(); } } +constexpr double toMeter(DistanceUnit unit) { + switch (unit) { + case DistanceUnit::Nanometer: + return 1e-9; + case DistanceUnit::Micrometer: + return 1e-6; + case DistanceUnit::Millimeter: + return 1e-3; + case DistanceUnit::Centimeter: + return 1e-2; + case DistanceUnit::Decimeter: + return 1e-1; + case DistanceUnit::Meter: + return 1.0; + case DistanceUnit::Kilometer: + return 1000.0; + case DistanceUnit::AU: + return distanceconstants::AstronomicalUnit; + case DistanceUnit::Lighthour: + return distanceconstants::LightHour; + case DistanceUnit::Lightday: + return distanceconstants::LightDay; + case DistanceUnit::Lightmonth: + return distanceconstants::LightMonth; + case DistanceUnit::Lightyear: + return distanceconstants::LightYear; + case DistanceUnit::Parsec: + return distanceconstants::Parsec; + case DistanceUnit::Kiloparsec: + return 1e3 * distanceconstants::Parsec; + case DistanceUnit::Megaparsec: + return 1e6 * distanceconstants::Parsec; + case DistanceUnit::Gigaparsec: + return 1e9 * distanceconstants::Parsec; + case DistanceUnit::Thou: + return 1e-3 * distanceconstants::Inch; + case DistanceUnit::Inch: + return distanceconstants::Inch; + case DistanceUnit::Foot: + return distanceconstants::Foot; + case DistanceUnit::Yard: + return distanceconstants::Yard; + case DistanceUnit::Chain: + return distanceconstants::Chain; + case DistanceUnit::Furlong: + return 10.0 * distanceconstants::Chain; + case DistanceUnit::Mile: + return distanceconstants::Mile; + case DistanceUnit::League: + return 3.0 * distanceconstants::Mile; + default: + throw ghoul::MissingCaseException(); + } +} + +constexpr double convertUnit(DistanceUnit fromUnit, DistanceUnit toUnit) { + return convertMeters(toMeter(fromUnit), toUnit); +} + +constexpr double convertDistance(double distance, DistanceUnit fromUnit, DistanceUnit toUnit) { + return distance * convertUnit(fromUnit, toUnit); +} + float convertMasPerYearToMeterPerSecond(float masPerYear, float parallax); } // namespace openspace diff --git a/modules/base/dashboard/dashboarditemdistance.cpp b/modules/base/dashboard/dashboarditemdistance.cpp index bcca68c879..bfb364ad2d 100644 --- a/modules/base/dashboard/dashboarditemdistance.cpp +++ b/modules/base/dashboard/dashboarditemdistance.cpp @@ -368,7 +368,7 @@ void DashboardItemDistance::render(glm::vec2& penPosition) { } else { const DistanceUnit unit = static_cast(_requestedUnit.value()); - const double convertedD = convertDistance(d, unit); + const double convertedD = convertMeters(d, unit); dist = { convertedD, nameForDistanceUnit(unit, convertedD != 1.0) }; } @@ -399,7 +399,7 @@ glm::vec2 DashboardItemDistance::size() const { } else { DistanceUnit unit = static_cast(_requestedUnit.value()); - double convertedD = convertDistance(d, unit); + double convertedD = convertMeters(d, unit); dist = { convertedD, nameForDistanceUnit(unit, convertedD != 1.0) }; } diff --git a/modules/base/dashboard/dashboarditemvelocity.cpp b/modules/base/dashboard/dashboarditemvelocity.cpp index d71e4c585d..587e83f4f3 100644 --- a/modules/base/dashboard/dashboarditemvelocity.cpp +++ b/modules/base/dashboard/dashboarditemvelocity.cpp @@ -133,7 +133,7 @@ void DashboardItemVelocity::render(glm::vec2& penPosition) { } else { const DistanceUnit unit = static_cast(_requestedUnit.value()); - const double convertedD = convertDistance(speedPerSecond, unit); + const double convertedD = convertMeters(speedPerSecond, unit); dist = { convertedD, nameForDistanceUnit(unit, convertedD != 1.0) }; } @@ -159,7 +159,7 @@ glm::vec2 DashboardItemVelocity::size() const { } else { DistanceUnit unit = static_cast(_requestedUnit.value()); - double convertedD = convertDistance(d, unit); + double convertedD = convertMeters(d, unit); dist = { convertedD, nameForDistanceUnit(unit, convertedD != 1.0) }; } diff --git a/modules/base/rendering/renderablemodel.cpp b/modules/base/rendering/renderablemodel.cpp index e72cd9f986..a13fccfaa7 100644 --- a/modules/base/rendering/renderablemodel.cpp +++ b/modules/base/rendering/renderablemodel.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -40,13 +41,12 @@ #include #include #include +#include +#include namespace { constexpr const char* _loggerCat = "RenderableModel"; - constexpr const char* ProgramName = "ModelProgram"; - constexpr const char* KeyGeomModelFile = "GeometryFile"; - constexpr const char* KeyForceRenderInvisible = "ForceRenderInvisible"; constexpr const int DefaultBlending = 0; constexpr const int AdditiveBlending = 1; @@ -62,6 +62,12 @@ namespace { { "Color Adding", ColorAddingBlending } }; + constexpr openspace::properties::Property::PropertyInfo EnableAnimationInfo = { + "EnableAnimation", + "Enable Animation", + "Enable or disable the animation for the model if it has any" + }; + constexpr const std::array UniformNames = { "opacity", "nLightSources", "lightDirectionsViewSpace", "lightIntensities", "modelViewTransform", "normalTransform", "projectionTransform", @@ -126,9 +132,10 @@ namespace { }; constexpr openspace::properties::Property::PropertyInfo BlendingOptionInfo = { - "BledingOption", + "BlendingOption", "Blending Options", - "Debug option for blending colors." + "Changes the blending function used to calculate the colors of the model with " + "respect to the opacity." }; constexpr openspace::properties::Property::PropertyInfo EnableOpacityBlendingInfo = { @@ -136,110 +143,113 @@ namespace { "Enable Opacity Blending", "Enable Opacity Blending." }; + + struct [[codegen::Dictionary(RenderableModel)]] Parameters { + // The file or files that should be loaded in this RenderableModel. The file can + // contain filesystem tokens. This specifies the model that is rendered by + // the Renderable. + std::filesystem::path geometryFile; + + enum class ScaleUnit { + Nanometer, + Micrometer, + Millimeter, + Centimeter, + Decimeter, + Meter, + Kilometer + }; + + // The scale of the model. For example if the model is in centimeters + // then ModelScale = Centimeter or ModelScale = 0.01 + std::optional> modelScale; + + // Set if invisible parts (parts with no textures or materials) of the model + // should be forced to render or not. + std::optional forceRenderInvisible; + + // [[codegen::verbatim(EnableAnimationInfo.description)]] + std::optional enableAnimation; + + // The date and time that the model animation should start. + // In format 'YYYY MM DD hh:mm:ss'. + std::optional animationStartTime [[codegen::datetime()]]; + + enum class AnimationTimeUnit { + Nanosecond, + Microsecond, + Millisecond, + Second, + Minute + }; + + // The time scale for the animation relative to seconds. + // Ex, if animation is in milliseconds then AnimationTimeScale = 0.001 or + // AnimationTimeScale = Millisecond, default is Second + std::optional> animationTimeScale; + + enum class AnimationMode { + Once, + LoopFromStart, + LoopInfinitely, + BounceFromStart, + BounceInfinitely + }; + + // The mode of how the animation should be played back. + // Default is animation is played back once at the start time. + // For a more detailed description see: + // http://wiki.openspaceproject.com/docs/builders/model-animation + std::optional animationMode; + + // [[codegen::verbatim(AmbientIntensityInfo.description)]] + std::optional ambientIntensity; + + // [[codegen::verbatim(DiffuseIntensityInfo.description)]] + std::optional diffuseIntensity; + + // [[codegen::verbatim(SpecularIntensityInfo.description)]] + std::optional specularIntensity; + + // [[codegen::verbatim(ShadingInfo.description)]] + std::optional performShading; + + // [[codegen::verbatim(DisableFaceCullingInfo.description)]] + std::optional disableFaceCulling; + + // [[codegen::verbatim(ModelTransformInfo.description)]] + std::optional modelTransform; + + // [[codegen::verbatim(RotationVecInfo.description)]] + std::optional rotationVector; + + // [[codegen::verbatim(LightSourcesInfo.description)]] + std::optional> lightSources + [[codegen::reference("core_light_source")]]; + + // [[codegen::verbatim(DisableDepthTestInfo.description)]] + std::optional disableDepthTest; + + // [[codegen::verbatim(BlendingOptionInfo.description)]] + std::optional blendingOption; + + // [[codegen::verbatim(EnableOpacityBlendingInfo.description)]] + std::optional enableOpacityBlending; + }; +#include "renderablemodel_codegen.cpp" } // namespace namespace openspace { documentation::Documentation RenderableModel::Documentation() { - using namespace documentation; - return { - "RenderableModel", - "base_renderable_model", - { - { - KeyGeomModelFile, - new OrVerifier({ new StringVerifier, new StringListVerifier }), - Optional::No, - "The file or files that should be loaded in this RenderableModel. The file can " - "contain filesystem tokens or can be specified relatively to the " - "location of the .mod file. " - "This specifies the model that is rendered by the Renderable." - }, - { - KeyForceRenderInvisible, - new BoolVerifier, - Optional::Yes, - "Set if invisible parts (parts with no textures or materials) of the model " - "should be forced to render or not." - }, - { - AmbientIntensityInfo.identifier, - new DoubleVerifier, - Optional::Yes, - AmbientIntensityInfo.description - }, - { - DiffuseIntensityInfo.identifier, - new DoubleVerifier, - Optional::Yes, - DiffuseIntensityInfo.description - }, - { - SpecularIntensityInfo.identifier, - new DoubleVerifier, - Optional::Yes, - SpecularIntensityInfo.description - }, - { - ShadingInfo.identifier, - new BoolVerifier, - Optional::Yes, - ShadingInfo.description - }, - { - DisableFaceCullingInfo.identifier, - new BoolVerifier, - Optional::Yes, - DisableFaceCullingInfo.description - }, - { - ModelTransformInfo.identifier, - new DoubleMatrix3Verifier, - Optional::Yes, - ModelTransformInfo.description - }, - { - RotationVecInfo.identifier, - new DoubleVector3Verifier, - Optional::Yes, - RotationVecInfo.description - }, - { - LightSourcesInfo.identifier, - new TableVerifier({ - { - "*", - new ReferencingVerifier("core_light_source"), - Optional::Yes - } - }), - Optional::Yes, - LightSourcesInfo.description - }, - { - DisableDepthTestInfo.identifier, - new BoolVerifier, - Optional::Yes, - DisableDepthTestInfo.description - }, - { - BlendingOptionInfo.identifier, - new StringVerifier, - Optional::Yes, - BlendingOptionInfo.description - }, - { - EnableOpacityBlendingInfo.identifier, - new BoolVerifier, - Optional::Yes, - EnableOpacityBlendingInfo.description - }, - } - }; + documentation::Documentation doc = codegen::doc(); + doc.id = "base_renderable_model"; + return doc; } RenderableModel::RenderableModel(const ghoul::Dictionary& dictionary) : Renderable(dictionary) + , _enableAnimation(EnableAnimationInfo, false) , _ambientIntensity(AmbientIntensityInfo, 0.2f, 0.f, 1.f) , _diffuseIntensity(DiffuseIntensityInfo, 1.f, 0.f, 1.f) , _specularIntensity(SpecularIntensityInfo, 1.f, 0.f, 1.f) @@ -260,17 +270,13 @@ RenderableModel::RenderableModel(const ghoul::Dictionary& dictionary) ) , _lightSourcePropertyOwner({ "LightSources", "Light Sources" }) { - documentation::testSpecificationAndThrow( - Documentation(), - dictionary, - "RenderableModel" - ); + const Parameters p = codegen::bake(dictionary); addProperty(_opacity); registerUpdateRenderBinFromOpacity(); - if (dictionary.hasKey(KeyForceRenderInvisible)) { - _forceRenderInvisible = dictionary.value(KeyForceRenderInvisible); + if (p.forceRenderInvisible.has_value()) { + _forceRenderInvisible = *p.forceRenderInvisible; if (!_forceRenderInvisible) { // Asset file have specifically said to not render invisible parts, @@ -279,103 +285,165 @@ RenderableModel::RenderableModel(const ghoul::Dictionary& dictionary) } } - if (dictionary.hasKey(KeyGeomModelFile)) { - std::string file; + std::string file = absPath(p.geometryFile.string()); + _geometry = ghoul::io::ModelReader::ref().loadModel( + file, + ghoul::io::ModelReader::ForceRenderInvisible(_forceRenderInvisible), + ghoul::io::ModelReader::NotifyInvisibleDropped(_notifyInvisibleDropped) + ); - if (dictionary.hasValue(KeyGeomModelFile)) { - // Handle single file - file = absPath(dictionary.value(KeyGeomModelFile)); - _geometry = ghoul::io::ModelReader::ref().loadModel( - file, - ghoul::io::ModelReader::ForceRenderInvisible(_forceRenderInvisible), - ghoul::io::ModelReader::NotifyInvisibleDropped(_notifyInvisibleDropped) - ); + if (p.modelScale.has_value()) { + if (std::holds_alternative(*p.modelScale)) { + Parameters::ScaleUnit scaleUnit = + std::get(*p.modelScale); + DistanceUnit distanceUnit; + + switch (scaleUnit) { + case Parameters::ScaleUnit::Nanometer: + distanceUnit = DistanceUnit::Nanometer; + break; + case Parameters::ScaleUnit::Micrometer: + distanceUnit = DistanceUnit::Micrometer; + break; + case Parameters::ScaleUnit::Millimeter: + distanceUnit = DistanceUnit::Millimeter; + break; + case Parameters::ScaleUnit::Centimeter: + distanceUnit = DistanceUnit::Centimeter; + break; + case Parameters::ScaleUnit::Decimeter: + distanceUnit = DistanceUnit::Decimeter; + break; + case Parameters::ScaleUnit::Meter: + distanceUnit = DistanceUnit::Meter; + break; + case Parameters::ScaleUnit::Kilometer: + distanceUnit = DistanceUnit::Kilometer; + break; + default: + throw ghoul::MissingCaseException(); + } + _modelScale = convertUnit(distanceUnit, DistanceUnit::Meter); } - else if (dictionary.hasValue(KeyGeomModelFile)) { - LWARNING("Loading a model with several files is deprecated and will be " - "removed in a future release" - ); - - ghoul::Dictionary fileDictionary = dictionary.value( - KeyGeomModelFile - ); - std::vector> geometries; - - for (std::string_view k : fileDictionary.keys()) { - // Handle each file - file = absPath(fileDictionary.value(k)); - geometries.push_back(ghoul::io::ModelReader::ref().loadModel( - file, - ghoul::io::ModelReader::ForceRenderInvisible(_forceRenderInvisible), - ghoul::io::ModelReader::NotifyInvisibleDropped(_notifyInvisibleDropped) - )); - } - - if (!geometries.empty()) { - std::unique_ptr combinedGeometry = - std::move(geometries[0]); - - // Combine all models into one ModelGeometry - for (unsigned int i = 1; i < geometries.size(); ++i) { - for (ghoul::io::ModelMesh& mesh : geometries[i]->meshes()) { - combinedGeometry->meshes().push_back( - std::move(mesh) - ); - } - - for (ghoul::modelgeometry::ModelGeometry::TextureEntry& texture : - geometries[i]->textureStorage()) - { - combinedGeometry->textureStorage().push_back( - std::move(texture) - ); - } - } - _geometry = std::move(combinedGeometry); - _geometry->calculateBoundingRadius(); - } + else if (std::holds_alternative(*p.modelScale)) { + _modelScale = std::get(*p.modelScale); + } + else { + throw ghoul::MissingCaseException(); } } - if (dictionary.hasKey(ModelTransformInfo.identifier)) { - _modelTransform = dictionary.value(ModelTransformInfo.identifier); + if (p.animationStartTime.has_value()) { + if (!_geometry->hasAnimation()) { + LWARNING("Animation start time given to model without animation"); + } + _animationStart = *p.animationStartTime; } - if (dictionary.hasKey(AmbientIntensityInfo.identifier)) { - _ambientIntensity = dictionary.value(AmbientIntensityInfo.identifier); - } - if (dictionary.hasKey(DiffuseIntensityInfo.identifier)) { - _diffuseIntensity = dictionary.value(DiffuseIntensityInfo.identifier); - } - if (dictionary.hasKey(SpecularIntensityInfo.identifier)) { - _specularIntensity = dictionary.value(SpecularIntensityInfo.identifier); + if (p.enableAnimation.has_value()) { + if (!_geometry->hasAnimation()) { + LWARNING("Attempting to enable animation for a model that does not have any"); + } + else if (*p.enableAnimation && _animationStart.empty()) { + LWARNING("Cannot enable animation without a given start time"); + } + else { + _enableAnimation = *p.enableAnimation; + _geometry->enableAnimation(_enableAnimation); + } } - if (dictionary.hasKey(ShadingInfo.identifier)) { - _performShading = dictionary.value(ShadingInfo.identifier); + if (p.animationTimeScale.has_value()) { + if (!_geometry->hasAnimation()) { + LWARNING("Animation time scale given to model without animation"); + } + else if (std::holds_alternative(*p.animationTimeScale)) { + _geometry->setTimeScale(std::get(*p.animationTimeScale)); + } + else if (std::holds_alternative(*p.animationTimeScale)) { + Parameters::AnimationTimeUnit animationTimeUnit = + std::get(*p.animationTimeScale); + TimeUnit timeUnit; + + switch (animationTimeUnit) { + case Parameters::AnimationTimeUnit::Nanosecond: + timeUnit = TimeUnit::Nanosecond; + break; + case Parameters::AnimationTimeUnit::Microsecond: + timeUnit = TimeUnit::Microsecond; + break; + case Parameters::AnimationTimeUnit::Millisecond: + timeUnit = TimeUnit::Millisecond; + break; + case Parameters::AnimationTimeUnit::Second: + timeUnit = TimeUnit::Second; + break; + case Parameters::AnimationTimeUnit::Minute: + timeUnit = TimeUnit::Minute; + break; + default: + throw ghoul::MissingCaseException(); + } + + _geometry->setTimeScale(convertTime(1.0, timeUnit, TimeUnit::Second)); + } + else { + throw ghoul::MissingCaseException(); + } } - if (dictionary.hasKey(DisableDepthTestInfo.identifier)) { - _disableDepthTest = dictionary.value(DisableDepthTestInfo.identifier); + if (p.animationMode.has_value()) { + if (!_geometry->hasAnimation()) { + LWARNING("Animation mode given to model without animation"); + } + + switch (*p.animationMode) { + case Parameters::AnimationMode::LoopFromStart: + _animationMode = AnimationMode::LoopFromStart; + break; + case Parameters::AnimationMode::LoopInfinitely: + _animationMode = AnimationMode::LoopInfinitely; + break; + case Parameters::AnimationMode::BounceFromStart: + _animationMode = AnimationMode::BounceFromStart; + break; + case Parameters::AnimationMode::BounceInfinitely: + _animationMode = AnimationMode::BounceInfinitely; + break; + case Parameters::AnimationMode::Once: + _animationMode = AnimationMode::Once; + break; + default: + throw ghoul::MissingCaseException(); + } } - if (dictionary.hasKey(DisableFaceCullingInfo.identifier)) { - _disableFaceCulling = dictionary.value(DisableFaceCullingInfo.identifier); + if (p.modelTransform.has_value()) { + _modelTransform = *p.modelTransform; } - if (dictionary.hasKey(LightSourcesInfo.identifier)) { - const ghoul::Dictionary& lsDictionary = - dictionary.value(LightSourcesInfo.identifier); + _ambientIntensity = p.ambientIntensity.value_or(_ambientIntensity); + _diffuseIntensity = p.diffuseIntensity.value_or(_diffuseIntensity); + _specularIntensity = p.specularIntensity.value_or(_specularIntensity); + _performShading = p.performShading.value_or(_performShading); + _disableDepthTest = p.disableDepthTest.value_or(_disableDepthTest); + _disableFaceCulling = p.disableFaceCulling.value_or(_disableFaceCulling); - for (std::string_view k : lsDictionary.keys()) { - std::unique_ptr lightSource = LightSource::createFromDictionary( - lsDictionary.value(k) - ); + if (p.lightSources.has_value()) { + std::vector lightsources = *p.lightSources; + + for (const ghoul::Dictionary& lsDictionary : lightsources) { + std::unique_ptr lightSource = + LightSource::createFromDictionary(lsDictionary); _lightSourcePropertyOwner.addPropertySubOwner(lightSource.get()); _lightSources.push_back(std::move(lightSource)); } } + if (_geometry->hasAnimation()) { + addProperty(_enableAnimation); + } + addPropertySubOwner(_lightSourcePropertyOwner); addProperty(_ambientIntensity); addProperty(_diffuseIntensity); @@ -390,9 +458,21 @@ RenderableModel::RenderableModel(const ghoul::Dictionary& dictionary) _modelTransform = glm::mat4_cast(glm::quat(glm::radians(_rotationVec.value()))); }); + _enableAnimation.onChange([this]() { + if (!_geometry->hasAnimation()) { + LWARNING("Attempting to enable animation for a model that does not have any"); + } + else if (_enableAnimation && _animationStart.empty()) { + LWARNING("Cannot enable animation without a given start time"); + _enableAnimation = false; + } + else { + _geometry->enableAnimation(_enableAnimation); + } + }); - if (dictionary.hasKey(RotationVecInfo.identifier)) { - _rotationVec = dictionary.value(RotationVecInfo.identifier); + if (p.rotationVector.has_value()) { + _rotationVec = *p.rotationVector; } _blendingFuncOption.addOption(DefaultBlending, "Default"); @@ -403,18 +483,12 @@ RenderableModel::RenderableModel(const ghoul::Dictionary& dictionary) addProperty(_blendingFuncOption); - if (dictionary.hasKey(BlendingOptionInfo.identifier)) { - const std::string blendingOpt = dictionary.value( - BlendingOptionInfo.identifier - ); + if (p.blendingOption.has_value()) { + const std::string blendingOpt = *p.blendingOption; _blendingFuncOption.set(BlendingMapping[blendingOpt]); } - if (dictionary.hasKey(DisableDepthTestInfo.identifier)) { - _enableOpacityBlending = dictionary.value( - EnableOpacityBlendingInfo.identifier - ); - } + _enableOpacityBlending = p.enableOpacityBlending.value_or(_enableOpacityBlending); addProperty(_enableOpacityBlending); } @@ -426,6 +500,15 @@ bool RenderableModel::isReady() const { void RenderableModel::initialize() { ZoneScoped + if (_geometry->hasAnimation() && _enableAnimation && _animationStart.empty()) { + LWARNING("Model with animation not given any start time"); + } + else if (_geometry->hasAnimation() && !_enableAnimation) { + LINFO("Model with deactivated animation was found. " + "The animation can be activated by entering a start time in the asset file" + ); + } + for (const std::unique_ptr& ls : _lightSources) { ls->initialize(); } @@ -449,7 +532,7 @@ void RenderableModel::initializeGL() { _geometry->initialize(); _geometry->calculateBoundingRadius(); - setBoundingSphere(glm::sqrt(_geometry->boundingRadius())); + setBoundingSphere(glm::sqrt(_geometry->boundingRadius()) * _modelScale); } void RenderableModel::deinitializeGL() { @@ -466,114 +549,197 @@ void RenderableModel::deinitializeGL() { } void RenderableModel::render(const RenderData& data, RendererTasks&) { - _program->activate(); + const double distanceToCamera = glm::distance( + data.camera.positionVec3(), + data.modelTransform.translation + ); - _program->setUniform(_uniformCache.opacity, _opacity); + // This distance will be enough to render the model as one pixel if the field of + // view is 'fov' radians and the screen resolution is 'res' pixels. + // Formula from RenderableGlobe + constexpr double tfov = 0.5773502691896257; + constexpr int res = 2880; + const double maxDistance = res * boundingSphere() / tfov; - // Model transform and view transform needs to be in double precision - const glm::dmat4 modelTransform = - glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation - glm::dmat4(data.modelTransform.rotation) * // Spice rotation - glm::scale( - glm::dmat4(_modelTransform.value()), glm::dvec3(data.modelTransform.scale) - ); - const glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * - modelTransform; + if (distanceToCamera < maxDistance) { + _program->activate(); - int nLightSources = 0; - _lightIntensitiesBuffer.resize(_lightSources.size()); - _lightDirectionsViewSpaceBuffer.resize(_lightSources.size()); - for (const std::unique_ptr& lightSource : _lightSources) { - if (!lightSource->isEnabled()) { - continue; + _program->setUniform(_uniformCache.opacity, _opacity); + + // Model transform and view transform needs to be in double precision + const glm::dmat4 modelTransform = + glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation + glm::dmat4(data.modelTransform.rotation) * // Spice rotation + glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale)) * + glm::scale( + glm::dmat4(_modelTransform.value()), + glm::dvec3(_modelScale) // Model scale unit + ); + const glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * + modelTransform; + + int nLightSources = 0; + _lightIntensitiesBuffer.resize(_lightSources.size()); + _lightDirectionsViewSpaceBuffer.resize(_lightSources.size()); + for (const std::unique_ptr& lightSource : _lightSources) { + if (!lightSource->isEnabled()) { + continue; + } + _lightIntensitiesBuffer[nLightSources] = lightSource->intensity(); + _lightDirectionsViewSpaceBuffer[nLightSources] = + lightSource->directionViewSpace(data); + + ++nLightSources; } - _lightIntensitiesBuffer[nLightSources] = lightSource->intensity(); - _lightDirectionsViewSpaceBuffer[nLightSources] = - lightSource->directionViewSpace(data); - ++nLightSources; + _program->setUniform( + _uniformCache.nLightSources, + nLightSources + ); + _program->setUniform( + _uniformCache.lightIntensities, + _lightIntensitiesBuffer + ); + _program->setUniform( + _uniformCache.lightDirectionsViewSpace, + _lightDirectionsViewSpaceBuffer + ); + _program->setUniform( + _uniformCache.modelViewTransform, + glm::mat4(modelViewTransform) + ); + + glm::dmat4 normalTransform = glm::transpose(glm::inverse(modelViewTransform)); + + _program->setUniform( + _uniformCache.normalTransform, + glm::mat4(normalTransform) + ); + + _program->setUniform( + _uniformCache.projectionTransform, + data.camera.projectionMatrix() + ); + _program->setUniform(_uniformCache.ambientIntensity, _ambientIntensity); + _program->setUniform(_uniformCache.diffuseIntensity, _diffuseIntensity); + _program->setUniform(_uniformCache.specularIntensity, _specularIntensity); + _program->setUniform(_uniformCache.performShading, _performShading); + _program->setUniform(_uniformCache.opacityBlending, _enableOpacityBlending); + + if (_disableFaceCulling) { + glDisable(GL_CULL_FACE); + } + + glEnablei(GL_BLEND, 0); + switch (_blendingFuncOption) { + case DefaultBlending: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case AdditiveBlending: + glBlendFunc(GL_ONE, GL_ONE); + break; + case PointsAndLinesBlending: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case PolygonBlending: + glBlendFunc(GL_SRC_ALPHA_SATURATE, GL_ONE); + break; + case ColorAddingBlending: + glBlendFunc(GL_SRC_COLOR, GL_DST_COLOR); + break; + }; + + if (_disableDepthTest) { + glDisable(GL_DEPTH_TEST); + } + + _geometry->render(*_program); + if (_disableFaceCulling) { + glEnable(GL_CULL_FACE); + } + + global::renderEngine->openglStateCache().resetBlendState(); + + if (_disableDepthTest) { + glEnable(GL_DEPTH_TEST); + } + + _program->deactivate(); } - - _program->setUniform( - _uniformCache.nLightSources, - nLightSources - ); - _program->setUniform( - _uniformCache.lightIntensities, - _lightIntensitiesBuffer - ); - _program->setUniform( - _uniformCache.lightDirectionsViewSpace, - _lightDirectionsViewSpaceBuffer - ); - _program->setUniform( - _uniformCache.modelViewTransform, - glm::mat4(modelViewTransform) - ); - - glm::dmat4 normalTransform = glm::transpose(glm::inverse(modelViewTransform)); - - _program->setUniform( - _uniformCache.normalTransform, - glm::mat4(normalTransform) - ); - - _program->setUniform( - _uniformCache.projectionTransform, - data.camera.projectionMatrix() - ); - _program->setUniform(_uniformCache.ambientIntensity, _ambientIntensity); - _program->setUniform(_uniformCache.diffuseIntensity, _diffuseIntensity); - _program->setUniform(_uniformCache.specularIntensity, _specularIntensity); - _program->setUniform(_uniformCache.performShading, _performShading); - _program->setUniform(_uniformCache.opacityBlending, _enableOpacityBlending); - - if (_disableFaceCulling) { - glDisable(GL_CULL_FACE); - } - - glEnablei(GL_BLEND, 0); - switch (_blendingFuncOption) { - case DefaultBlending: - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case AdditiveBlending: - glBlendFunc(GL_ONE, GL_ONE); - break; - case PointsAndLinesBlending: - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case PolygonBlending: - glBlendFunc(GL_SRC_ALPHA_SATURATE, GL_ONE); - break; - case ColorAddingBlending: - glBlendFunc(GL_SRC_COLOR, GL_DST_COLOR); - break; - }; - - if (_disableDepthTest) { - glDisable(GL_DEPTH_TEST); - } - - _geometry->render(*_program); - if (_disableFaceCulling) { - glEnable(GL_CULL_FACE); - } - - global::renderEngine->openglStateCache().resetBlendState(); - - if (_disableDepthTest) { - glEnable(GL_DEPTH_TEST); - } - - _program->deactivate(); } -void RenderableModel::update(const UpdateData&) { +void RenderableModel::update(const UpdateData& data) { if (_program->isDirty()) { _program->rebuildFromFile(); ghoul::opengl::updateUniformLocations(*_program, _uniformCache, UniformNames); } + + if (_geometry->hasAnimation() && !_animationStart.empty()) { + double relativeTime; + double now = data.time.j2000Seconds(); + double startTime = data.time.convertTime(_animationStart); + double duration = _geometry->animationDuration(); + + // The animation works in a time range 0 to duration where 0 in the animation is + // the given _animationStart time in OpenSpace. The time in OpenSpace then has to + // be converted to the animation time range, so the animation knows which + // keyframes it should interpolate between for each frame. The conversion is + // done in different ways depending on the animation mode. + // Explanation: s indicates start time, / indicates animation is played once forwards, + // \ indicates animation is played once backwards, time moves to the right. + switch (_animationMode) { + case AnimationMode::LoopFromStart: + // Start looping from the start time + // s//// + relativeTime = std::fmod(now - startTime, duration); + break; + case AnimationMode::LoopInfinitely: + // Loop both before and after the start time where the model is + // in the initial position at the start time. std::fmod is not a + // true modulo function, it just calculates the remainder of the division + // which can be negative. To make it true modulo it is bumped up to + // positive values when it is negative + // //s// + relativeTime = std::fmod(now - startTime, duration); + if (relativeTime < 0.0) { + relativeTime += duration; + } + break; + case AnimationMode::BounceFromStart: + // Bounce from the start position. Bounce means to do the animation + // and when it ends, play the animation in reverse to make sure the model + // goes back to its initial position before starting again. Avoids a + // visible jump from the last position to the first position when loop + // starts again + // s/\/\/\/\ + relativeTime = + duration - abs(fmod(now - startTime, 2 * duration) - duration); + break; + case AnimationMode::BounceInfinitely: { + // Bounce both before and after the start time where the model is + // in the initial position at the start time + // /\/\s/\/\ + double modulo = fmod(now - startTime, 2 * duration); + if (modulo < 0.0) { + modulo += 2 * duration; + } + relativeTime = duration - abs(modulo - duration); + break; + } + case AnimationMode::Once: + // Play animation once starting from the start time and stay at the + // animation's last position when animation is over + // s/ + relativeTime = now - startTime; + if (relativeTime > duration) { + relativeTime = duration; + } + break; + default: + throw ghoul::MissingCaseException(); + } + _geometry->update(relativeTime); + } } } // namespace openspace diff --git a/modules/base/rendering/renderablemodel.h b/modules/base/rendering/renderablemodel.h index 42dbf8828d..d1d2cbc9fa 100644 --- a/modules/base/rendering/renderablemodel.h +++ b/modules/base/rendering/renderablemodel.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -70,9 +71,21 @@ public: static documentation::Documentation Documentation(); private: + enum class AnimationMode { + Once = 0, + LoopFromStart, + LoopInfinitely, + BounceFromStart, + BounceInfinitely + }; + std::unique_ptr _geometry; + double _modelScale = 1.0; bool _forceRenderInvisible = false; bool _notifyInvisibleDropped = true; + std::string _animationStart; + AnimationMode _animationMode = AnimationMode::Once; + properties::BoolProperty _enableAnimation; properties::FloatProperty _ambientIntensity; diff --git a/modules/base/shaders/model_vs.glsl b/modules/base/shaders/model_vs.glsl index df55892597..1a0e9cac14 100644 --- a/modules/base/shaders/model_vs.glsl +++ b/modules/base/shaders/model_vs.glsl @@ -40,9 +40,11 @@ out mat3 TBN; uniform mat4 modelViewTransform; uniform mat4 projectionTransform; uniform mat4 normalTransform; +uniform mat4 meshTransform; +uniform mat4 meshNormalTransform; void main() { - vs_positionCameraSpace = modelViewTransform * in_position; + vs_positionCameraSpace = modelViewTransform * (meshTransform * in_position); vec4 positionClipSpace = projectionTransform * vs_positionCameraSpace; vec4 positionScreenSpace = z_normalization(positionClipSpace); @@ -50,11 +52,11 @@ void main() { vs_st = in_st; vs_screenSpaceDepth = positionScreenSpace.w; - vs_normalViewSpace = normalize(mat3(normalTransform) * in_normal); + vs_normalViewSpace = normalize(mat3(normalTransform) * (mat3(meshNormalTransform) * in_normal)); // TBN matrix for normal mapping - vec3 T = normalize(vec3(modelViewTransform * vec4(in_tangent, 0.0))); - vec3 N = normalize(vec3(modelViewTransform * vec4(in_normal, 0.0))); + vec3 T = normalize(mat3(normalTransform) * (mat3(meshNormalTransform) * in_tangent)); + vec3 N = normalize(mat3(normalTransform) * (mat3(meshNormalTransform) * in_normal)); // Re-orthogonalize T with respect to N T = normalize(T - dot(T, N) * N); diff --git a/modules/spacecraftinstruments/rendering/renderablemodelprojection.cpp b/modules/spacecraftinstruments/rendering/renderablemodelprojection.cpp index d91c07fe59..376e7c509d 100644 --- a/modules/spacecraftinstruments/rendering/renderablemodelprojection.cpp +++ b/modules/spacecraftinstruments/rendering/renderablemodelprojection.cpp @@ -42,6 +42,8 @@ #include #include #include +#include +#include namespace { constexpr const char* _loggerCat = "RenderableModelProjection"; @@ -73,132 +75,61 @@ namespace { "location to the Sun. If this value is disabled, shading is disabled and the " "entire model is rendered brightly." }; + + struct [[codegen::Dictionary(RenderableModelProjection)]] Parameters { + // The file or files that should be loaded in this RenderableModel. The file can + // contain filesystem tokens or can be specified relatively to the + // location of the .mod file. + // This specifies the model that is rendered by the Renderable. + std::filesystem::path geometryFile; + + // Contains information about projecting onto this planet. + ghoul::Dictionary projection [[codegen::reference("newhorizons_projectioncomponent")]]; + + // [[codegen::verbatim(PerformShadingInfo.description)]] + std::optional performShading; + + // The radius of the bounding sphere of this object. This has to be a + // radius that is larger than anything that is rendered by it. It has to + // be at least as big as the convex hull of the object. The default value + // is 10e9 meters. + std::optional boundingSphereRadius; + }; +#include "renderablemodelprojection_codegen.cpp" } // namespace namespace openspace { documentation::Documentation RenderableModelProjection::Documentation() { - using namespace documentation; - - return { - "Renderable Model Projection", - "newhorizons_renderable_modelprojection", - { - { - KeyGeomModelFile, - new OrVerifier({ new StringVerifier, new StringListVerifier }), - Optional::No, - "The file or files that are used for rendering of this model" - }, - { - keyProjection, - new ReferencingVerifier("newhorizons_projectioncomponent"), - Optional::No, - "Contains information about projecting onto this planet." - }, - { - PerformShadingInfo.identifier, - new BoolVerifier, - Optional::Yes, - PerformShadingInfo.description - }, - { - keyBoundingSphereRadius, - new DoubleVerifier, - Optional::Yes, - "The radius of the bounding sphere of this object. This has to be a " - "radius that is larger than anything that is rendered by it. It has to " - "be at least as big as the convex hull of the object. The default value " - "is 10e9 meters." - } - } - }; + documentation::Documentation doc = codegen::doc(); + doc.id = "newhorizons_renderable_modelprojection"; + return doc; } RenderableModelProjection::RenderableModelProjection(const ghoul::Dictionary& dictionary) : Renderable(dictionary) , _performShading(PerformShadingInfo, true) { - documentation::testSpecificationAndThrow( - Documentation(), - dictionary, - "RenderableModelProjection" + const Parameters p = codegen::bake(dictionary); + + std::string file = absPath(p.geometryFile.string()); + _geometry = ghoul::io::ModelReader::ref().loadModel( + file, + ghoul::io::ModelReader::ForceRenderInvisible::No, + ghoul::io::ModelReader::NotifyInvisibleDropped::Yes ); - if (dictionary.hasKey(KeyGeomModelFile)) { - std::string file; - - if (dictionary.hasValue(KeyGeomModelFile)) { - // Handle single file - file = absPath(dictionary.value(KeyGeomModelFile)); - _geometry = ghoul::io::ModelReader::ref().loadModel( - file, - ghoul::io::ModelReader::ForceRenderInvisible::No, - ghoul::io::ModelReader::NotifyInvisibleDropped::Yes - ); - } - else if (dictionary.hasValue(KeyGeomModelFile)) { - LWARNING("Loading a model with several files is deprecated and will be " - "removed in a future release" - ); - - ghoul::Dictionary fileDictionary = dictionary.value( - KeyGeomModelFile - ); - std::vector> geometries; - - for (std::string_view k : fileDictionary.keys()) { - // Handle each file - file = absPath(fileDictionary.value(k)); - geometries.push_back(ghoul::io::ModelReader::ref().loadModel( - file, - ghoul::io::ModelReader::ForceRenderInvisible::No, - ghoul::io::ModelReader::NotifyInvisibleDropped::Yes - )); - } - - if (!geometries.empty()) { - std::unique_ptr combinedGeometry = - std::move(geometries[0]); - - // Combine all models into one ModelGeometry - for (unsigned int i = 1; i < geometries.size(); ++i) { - for (ghoul::io::ModelMesh& mesh : geometries[i]->meshes()) { - combinedGeometry->meshes().push_back( - std::move(mesh) - ); - } - - for (ghoul::modelgeometry::ModelGeometry::TextureEntry& texture : - geometries[i]->textureStorage()) - { - combinedGeometry->textureStorage().push_back( - std::move(texture) - ); - } - } - _geometry = std::move(combinedGeometry); - _geometry->calculateBoundingRadius(); - } - } - } - addPropertySubOwner(_projectionComponent); _projectionComponent.initialize( identifier(), - dictionary.value(keyProjection) + p.projection ); - double boundingSphereRadius = 1.0e9; - if (dictionary.hasValue(keyBoundingSphereRadius)) { - boundingSphereRadius = dictionary.value(keyBoundingSphereRadius); - } + double boundingSphereRadius = p.boundingSphereRadius.value_or(1.0e9); setBoundingSphere(boundingSphereRadius); - if (dictionary.hasValue(PerformShadingInfo.identifier)) { - _performShading = dictionary.value(PerformShadingInfo.identifier); - } + _performShading = p.performShading.value_or(_performShading); addProperty(_performShading); } diff --git a/modules/spacecraftinstruments/shaders/renderableModelProjection_vs.glsl b/modules/spacecraftinstruments/shaders/renderableModelProjection_vs.glsl index 80a73c107e..5d9ed84f35 100644 --- a/modules/spacecraftinstruments/shaders/renderableModelProjection_vs.glsl +++ b/modules/spacecraftinstruments/shaders/renderableModelProjection_vs.glsl @@ -37,13 +37,15 @@ out vec4 vs_ndc; uniform mat4 ProjectorMatrix; uniform mat4 ModelTransform; +uniform mat4 meshTransform; +uniform mat4 meshNormalTransform; uniform vec3 boresight; void main() { - vec4 raw_pos = psc_to_meter(in_position, vec2(1.0, 0.0)); + vec4 raw_pos = psc_to_meter((meshTransform * in_position), vec2(1.0, 0.0)); vs_position = ProjectorMatrix * ModelTransform * raw_pos; - vs_normal = normalize(ModelTransform * vec4(in_normal,0)); + vs_normal = normalize(ModelTransform * meshNormalTransform * vec4(in_normal,0)); vs_ndc = vs_position / vs_position.w; //match clipping plane diff --git a/modules/spacecraftinstruments/shaders/renderableModel_vs.glsl b/modules/spacecraftinstruments/shaders/renderableModel_vs.glsl index 4a7fff09a5..388b589732 100644 --- a/modules/spacecraftinstruments/shaders/renderableModel_vs.glsl +++ b/modules/spacecraftinstruments/shaders/renderableModel_vs.glsl @@ -39,10 +39,12 @@ uniform mat4 modelViewTransform; uniform mat4 projectionTransform; uniform vec3 cameraDirectionWorldSpace; uniform float _magnification; +uniform mat4 meshTransform; +uniform mat4 meshNormalTransform; void main() { - vec4 position = in_position; // Position already in homogenous coordinates + vec4 position = meshTransform * in_position; // Position already in homogenous coordinates position.xyz *= pow(10, _magnification); vs_positionCameraSpace = modelViewTransform * position; vec4 positionClipSpace = projectionTransform * vs_positionCameraSpace; @@ -52,5 +54,5 @@ void main() { gl_Position = vs_positionScreenSpace; // The normal transform should be the transposed inverse of the model transform? - vs_normalViewSpace = normalize(mat3(modelViewTransform) * in_normal); + vs_normalViewSpace = normalize(mat3(modelViewTransform) * (mat3(meshNormalTransform) * in_normal)); } diff --git a/src/documentation/verifier.cpp b/src/documentation/verifier.cpp index d0930d33bd..a4a9c20d9b 100644 --- a/src/documentation/verifier.cpp +++ b/src/documentation/verifier.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include namespace openspace::documentation { @@ -271,6 +273,57 @@ std::string DirectoryVerifier::type() const { return "Directory"; } +TestResult DateTimeVerifier::operator()(const ghoul::Dictionary& dict, + const std::string& key) const +{ + TestResult res = StringVerifier::operator()(dict, key); + if (!res.success) { + return res; + } + + std::string dateTime = dict.value(key); + std::string format = "%Y %m %d %H:%M:%S"; // YYYY MM DD hh:mm:ss + + std::tm t = {}; + std::istringstream ss(dateTime); + ss >> std::get_time(&t, format.c_str()); + + // first check format (automatically checks if valid time) + if (ss.fail()) { + res.success = false; + TestResult::Offense off; + off.offender = key; + off.reason = TestResult::Offense::Reason::Verification; + off.explanation = "Not a valid format, should be: YYYY MM DD hh:mm:ss"; + res.offenses.push_back(off); + } + // then check if valid date + else { + // normalize e.g. 29/02/2013 would become 01/03/2013 + std::tm t_copy(t); + time_t when = mktime(&t_copy); + std::tm* norm = localtime(&when); + + // validate (is the normalized date still the same?): + if (norm->tm_mday != t.tm_mday && + norm->tm_mon != t.tm_mon && + norm->tm_year != t.tm_year) + { + res.success = false; + TestResult::Offense off; + off.offender = key; + off.reason = TestResult::Offense::Reason::Verification; + off.explanation = "Not a valid date"; + res.offenses.push_back(off); + } + } + return res; +} + +std::string DateTimeVerifier::type() const { + return "Date and time"; +} + TestResult Color3Verifier::operator()(const ghoul::Dictionary& dictionary, const std::string& key) const { diff --git a/support/coding/codegen b/support/coding/codegen index 2efd176817..4b06f1ad45 160000 --- a/support/coding/codegen +++ b/support/coding/codegen @@ -1 +1 @@ -Subproject commit 2efd1768176c1559b8eda3cbdcaeea2ded46e145 +Subproject commit 4b06f1ad4586289f0f1523915e11b1c81fe21202 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 217794fdc7..c68874247c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,6 +28,7 @@ add_executable( test_assetloader.cpp test_concurrentjobmanager.cpp test_concurrentqueue.cpp + test_distanceconversion.cpp test_configuration.cpp test_documentation.cpp test_iswamanager.cpp diff --git a/tests/test_distanceconversion.cpp b/tests/test_distanceconversion.cpp new file mode 100644 index 0000000000..56e277c36e --- /dev/null +++ b/tests/test_distanceconversion.cpp @@ -0,0 +1,258 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include "catch2/catch.hpp" + +#include +#include + +using namespace openspace; + +TEST_CASE("DistanceConversion: Convert to meters", "[distanceconversion]") { + const double unit = 1.0; + double res; + + res = convertDistance(unit, DistanceUnit::Nanometer, DistanceUnit::Meter); + REQUIRE(res == Approx(1e-9)); + + res = convertDistance(unit, DistanceUnit::Micrometer, DistanceUnit::Meter); + REQUIRE(res == Approx(1e-6)); + + res = convertDistance(unit, DistanceUnit::Millimeter, DistanceUnit::Meter); + REQUIRE(res == Approx(1e-3)); + + res = convertDistance(unit, DistanceUnit::Centimeter, DistanceUnit::Meter); + REQUIRE(res == Approx(1e-2)); + + res = convertDistance(unit, DistanceUnit::Decimeter, DistanceUnit::Meter); + REQUIRE(res == Approx(1e-1)); + + res = convertDistance(unit, DistanceUnit::Meter, DistanceUnit::Meter); + REQUIRE(res == Approx(1.0)); + + res = convertDistance(unit, DistanceUnit::Kilometer, DistanceUnit::Meter); + REQUIRE(res == Approx(1000.0)); + + res = convertDistance(unit, DistanceUnit::AU, DistanceUnit::Meter); + REQUIRE(res == Approx(1.495978707E11)); + + res = convertDistance(unit, DistanceUnit::Lighthour, DistanceUnit::Meter); + REQUIRE(res == Approx(1.0799921E12)); + + res = convertDistance(unit, DistanceUnit::Lightday, DistanceUnit::Meter); + REQUIRE(res == Approx(2.591981E13)); + + res = convertDistance(unit, DistanceUnit::Lightmonth, DistanceUnit::Meter); + REQUIRE(res == Approx(7.8839421E14)); + + res = convertDistance(unit, DistanceUnit::Lightyear, DistanceUnit::Meter); + REQUIRE(res == Approx(9.4607304725808E15)); + + res = convertDistance(unit, DistanceUnit::Parsec, DistanceUnit::Meter); + REQUIRE(res == Approx(3.0856776E16)); + + res = convertDistance(unit, DistanceUnit::Kiloparsec, DistanceUnit::Meter); + REQUIRE(res == Approx(1e3 * 3.0856776E16)); + + res = convertDistance(unit, DistanceUnit::Megaparsec, DistanceUnit::Meter); + REQUIRE(res == Approx(1e6 * 3.0856776E16)); + + res = convertDistance(unit, DistanceUnit::Gigaparsec, DistanceUnit::Meter); + REQUIRE(res == Approx(1e9 * 3.0856776E16)); + + res = convertDistance(unit, DistanceUnit::Thou, DistanceUnit::Meter); + REQUIRE(res == Approx(1e-3 * 0.0254)); + + res = convertDistance(unit, DistanceUnit::Inch, DistanceUnit::Meter); + REQUIRE(res == Approx(0.0254)); + + res = convertDistance(unit, DistanceUnit::Foot, DistanceUnit::Meter); + REQUIRE(res == Approx(0.3048)); + + res = convertDistance(unit, DistanceUnit::Yard, DistanceUnit::Meter); + REQUIRE(res == Approx(0.9144)); + + res = convertDistance(unit, DistanceUnit::Chain, DistanceUnit::Meter); + REQUIRE(res == Approx(20.1168)); + + res = convertDistance(unit, DistanceUnit::Furlong, DistanceUnit::Meter); + REQUIRE(res == Approx(10.0 * 20.1168)); + + res = convertDistance(unit, DistanceUnit::Mile, DistanceUnit::Meter); + REQUIRE(res == Approx(1609.344)); + + res = convertDistance(unit, DistanceUnit::League, DistanceUnit::Meter); + REQUIRE(res == Approx(3.0 * 1609.344)); +} + +TEST_CASE("DistanceConversion: Convert from meters", "[distanceconversion]") { + const double meters = 1.0; + double res; + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Nanometer); + REQUIRE(res == Approx(meters / 1e-9)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Micrometer); + REQUIRE(res == Approx(meters / 1e-6)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Millimeter); + REQUIRE(res == Approx(meters / 1e-3)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Centimeter); + REQUIRE(res == Approx(meters / 1e-2)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Decimeter); + REQUIRE(res == Approx(meters / 1e-1)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Meter); + REQUIRE(res == Approx(1.0)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Kilometer); + REQUIRE(res == Approx(meters / 1000.0)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::AU); + REQUIRE(res == Approx(meters / 1.495978707E11)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Lighthour); + REQUIRE(res == Approx(meters / 1.0799921E12)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Lightday); + REQUIRE(res == Approx(meters / 2.591981E13)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Lightmonth); + REQUIRE(res == Approx(meters / 7.8839421E14)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Lightyear); + REQUIRE(res == Approx(meters / 9.4607304725808E15)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Parsec); + REQUIRE(res == Approx(meters / 3.0856776E16)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Kiloparsec); + REQUIRE(res == Approx(meters / (1e3 * 3.0856776E16))); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Megaparsec); + REQUIRE(res == Approx(meters / (1e6 * 3.0856776E16))); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Gigaparsec); + REQUIRE(res == Approx(meters / (1e9 * 3.0856776E16))); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Thou); + REQUIRE(res == Approx(meters / (1e-3 * 0.0254))); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Inch); + REQUIRE(res == Approx(meters / 0.0254)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Foot); + REQUIRE(res == Approx(meters / 0.3048)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Yard); + REQUIRE(res == Approx(meters / 0.9144)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Chain); + REQUIRE(res == Approx(meters / 20.1168)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Furlong); + REQUIRE(res == Approx(meters / (10.0 * 20.1168))); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::Mile); + REQUIRE(res == Approx(meters / 1609.344)); + + res = convertDistance(meters, DistanceUnit::Meter, DistanceUnit::League); + REQUIRE(res == Approx(meters / (3.0 * 1609.344))); +} + +TEST_CASE("DistanceConversion: Cross conversion", "[distanceconversion]") { + const double unit = 1.0; + double res; + + res = convertDistance(unit, DistanceUnit::Nanometer, DistanceUnit::Kilometer); + REQUIRE(res == Approx(1e-12)); + + res = convertDistance(unit, DistanceUnit::Micrometer, DistanceUnit::Decimeter); + REQUIRE(res == Approx(1e-5)); + + res = convertDistance(unit, DistanceUnit::Millimeter, DistanceUnit::Nanometer); + REQUIRE(res == Approx(1e6)); + + res = convertDistance(unit, DistanceUnit::Centimeter, DistanceUnit::Micrometer); + REQUIRE(res == Approx(1e4)); + + res = convertDistance(unit, DistanceUnit::Decimeter, DistanceUnit::Millimeter); + REQUIRE(res == Approx(1e2)); + + res = convertDistance(unit, DistanceUnit::Kilometer, DistanceUnit::Centimeter); + REQUIRE(res == Approx(1e5)); + + res = convertDistance(unit, DistanceUnit::AU, DistanceUnit::Parsec); + REQUIRE(res == Approx(4.84813681e-6)); + + res = convertDistance(unit, DistanceUnit::Lighthour, DistanceUnit::Lightmonth); + REQUIRE(res == Approx(1.36986305e-3)); + + res = convertDistance(unit, DistanceUnit::Lightday, DistanceUnit::Kiloparsec); + REQUIRE(res == Approx(8.40003829e-7)); + + res = convertDistance(unit, DistanceUnit::Lightmonth, DistanceUnit::Lightday); + REQUIRE(res == Approx(30.4166662487)); + + res = convertDistance(unit, DistanceUnit::Lightyear, DistanceUnit::Gigaparsec); + REQUIRE(res == Approx(3.0660139e-10)); + + res = convertDistance(unit, DistanceUnit::Parsec, DistanceUnit::Lightyear); + REQUIRE(res == Approx(3.26156379673)); + + res = convertDistance(unit, DistanceUnit::Kiloparsec, DistanceUnit::AU); + REQUIRE(res == Approx(2.06264806E8)); + + res = convertDistance(unit, DistanceUnit::Megaparsec, DistanceUnit::Lighthour); + REQUIRE(res == Approx(2.85712978826E10)); + + res = convertDistance(unit, DistanceUnit::Gigaparsec, DistanceUnit::Megaparsec); + REQUIRE(res == Approx(1e3)); + + res = convertDistance(unit, DistanceUnit::Thou, DistanceUnit::Yard); + REQUIRE(res == Approx(2.77777778e-5)); + + res = convertDistance(unit, DistanceUnit::Inch, DistanceUnit::Foot); + REQUIRE(res == Approx(8.33333333e-2)); + + res = convertDistance(unit, DistanceUnit::Foot, DistanceUnit::Mile); + REQUIRE(res == Approx(1.89393939e-4)); + + res = convertDistance(unit, DistanceUnit::Yard, DistanceUnit::Chain); + REQUIRE(res == Approx(4.54545455e-2)); + + res = convertDistance(unit, DistanceUnit::Chain, DistanceUnit::League); + REQUIRE(res == Approx(4.16666666e-3)); + + res = convertDistance(unit, DistanceUnit::Furlong, DistanceUnit::Thou); + REQUIRE(res == Approx(7.92E6)); + + res = convertDistance(unit, DistanceUnit::Mile, DistanceUnit::Inch); + REQUIRE(res == Approx(6.3360E4)); + + res = convertDistance(unit, DistanceUnit::League, DistanceUnit::Furlong); + REQUIRE(res == Approx(24.0)); +}