mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-08 20:50:19 -06:00
Merge pull request #1552 from OpenSpace/feature/model-animation
Feature/model animation
This commit is contained in:
@@ -60,6 +60,7 @@ local initializeAndAddNodes = function()
|
||||
Renderable = {
|
||||
Type = "RenderableModel",
|
||||
GeometryFile = models .. "/ISS.fbx",
|
||||
ModelScale = "Centimeter",
|
||||
LightSources = {
|
||||
{
|
||||
Type = "SceneGraphLightSource",
|
||||
|
||||
Submodule ext/ghoul updated: bddc5370d7...23b9673bfc
@@ -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
|
||||
|
||||
@@ -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__
|
||||
|
||||
@@ -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, static_cast<int>(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<const char*, static_cast<int>(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<const char*, static_cast<int>(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<double, std::string> 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
|
||||
|
||||
@@ -368,7 +368,7 @@ void DashboardItemDistance::render(glm::vec2& penPosition) {
|
||||
}
|
||||
else {
|
||||
const DistanceUnit unit = static_cast<DistanceUnit>(_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<DistanceUnit>(_requestedUnit.value());
|
||||
double convertedD = convertDistance(d, unit);
|
||||
double convertedD = convertMeters(d, unit);
|
||||
dist = { convertedD, nameForDistanceUnit(unit, convertedD != 1.0) };
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ void DashboardItemVelocity::render(glm::vec2& penPosition) {
|
||||
}
|
||||
else {
|
||||
const DistanceUnit unit = static_cast<DistanceUnit>(_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<DistanceUnit>(_requestedUnit.value());
|
||||
double convertedD = convertDistance(d, unit);
|
||||
double convertedD = convertMeters(d, unit);
|
||||
dist = { convertedD, nameForDistanceUnit(unit, convertedD != 1.0) };
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <openspace/engine/globals.h>
|
||||
#include <openspace/rendering/renderengine.h>
|
||||
#include <openspace/util/time.h>
|
||||
#include <openspace/util/timeconversion.h>
|
||||
#include <openspace/util/updatestructures.h>
|
||||
#include <openspace/scene/scene.h>
|
||||
#include <openspace/scene/lightsource.h>
|
||||
@@ -40,13 +41,12 @@
|
||||
#include <ghoul/misc/profiling.h>
|
||||
#include <ghoul/opengl/openglstatecache.h>
|
||||
#include <ghoul/opengl/programobject.h>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
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<const char*, 12> 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<std::variant<ScaleUnit, double>> modelScale;
|
||||
|
||||
// Set if invisible parts (parts with no textures or materials) of the model
|
||||
// should be forced to render or not.
|
||||
std::optional<bool> forceRenderInvisible;
|
||||
|
||||
// [[codegen::verbatim(EnableAnimationInfo.description)]]
|
||||
std::optional<bool> enableAnimation;
|
||||
|
||||
// The date and time that the model animation should start.
|
||||
// In format 'YYYY MM DD hh:mm:ss'.
|
||||
std::optional<std::string> 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<std::variant<AnimationTimeUnit, float>> 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> animationMode;
|
||||
|
||||
// [[codegen::verbatim(AmbientIntensityInfo.description)]]
|
||||
std::optional<double> ambientIntensity;
|
||||
|
||||
// [[codegen::verbatim(DiffuseIntensityInfo.description)]]
|
||||
std::optional<double> diffuseIntensity;
|
||||
|
||||
// [[codegen::verbatim(SpecularIntensityInfo.description)]]
|
||||
std::optional<double> specularIntensity;
|
||||
|
||||
// [[codegen::verbatim(ShadingInfo.description)]]
|
||||
std::optional<bool> performShading;
|
||||
|
||||
// [[codegen::verbatim(DisableFaceCullingInfo.description)]]
|
||||
std::optional<bool> disableFaceCulling;
|
||||
|
||||
// [[codegen::verbatim(ModelTransformInfo.description)]]
|
||||
std::optional<glm::dmat3x3> modelTransform;
|
||||
|
||||
// [[codegen::verbatim(RotationVecInfo.description)]]
|
||||
std::optional<glm::dvec3> rotationVector;
|
||||
|
||||
// [[codegen::verbatim(LightSourcesInfo.description)]]
|
||||
std::optional<std::vector<ghoul::Dictionary>> lightSources
|
||||
[[codegen::reference("core_light_source")]];
|
||||
|
||||
// [[codegen::verbatim(DisableDepthTestInfo.description)]]
|
||||
std::optional<bool> disableDepthTest;
|
||||
|
||||
// [[codegen::verbatim(BlendingOptionInfo.description)]]
|
||||
std::optional<std::string> blendingOption;
|
||||
|
||||
// [[codegen::verbatim(EnableOpacityBlendingInfo.description)]]
|
||||
std::optional<bool> 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<Parameters>();
|
||||
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<Parameters>(dictionary);
|
||||
|
||||
addProperty(_opacity);
|
||||
registerUpdateRenderBinFromOpacity();
|
||||
|
||||
if (dictionary.hasKey(KeyForceRenderInvisible)) {
|
||||
_forceRenderInvisible = dictionary.value<bool>(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<std::string>(KeyGeomModelFile)) {
|
||||
// Handle single file
|
||||
file = absPath(dictionary.value<std::string>(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<Parameters::ScaleUnit>(*p.modelScale)) {
|
||||
Parameters::ScaleUnit scaleUnit =
|
||||
std::get<Parameters::ScaleUnit>(*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<ghoul::Dictionary>(KeyGeomModelFile)) {
|
||||
LWARNING("Loading a model with several files is deprecated and will be "
|
||||
"removed in a future release"
|
||||
);
|
||||
|
||||
ghoul::Dictionary fileDictionary = dictionary.value<ghoul::Dictionary>(
|
||||
KeyGeomModelFile
|
||||
);
|
||||
std::vector<std::unique_ptr<ghoul::modelgeometry::ModelGeometry>> geometries;
|
||||
|
||||
for (std::string_view k : fileDictionary.keys()) {
|
||||
// Handle each file
|
||||
file = absPath(fileDictionary.value<std::string>(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<ghoul::modelgeometry::ModelGeometry> 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<double>(*p.modelScale)) {
|
||||
_modelScale = std::get<double>(*p.modelScale);
|
||||
}
|
||||
else {
|
||||
throw ghoul::MissingCaseException();
|
||||
}
|
||||
}
|
||||
|
||||
if (dictionary.hasKey(ModelTransformInfo.identifier)) {
|
||||
_modelTransform = dictionary.value<glm::dmat3>(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<double>(AmbientIntensityInfo.identifier);
|
||||
}
|
||||
if (dictionary.hasKey(DiffuseIntensityInfo.identifier)) {
|
||||
_diffuseIntensity = dictionary.value<double>(DiffuseIntensityInfo.identifier);
|
||||
}
|
||||
if (dictionary.hasKey(SpecularIntensityInfo.identifier)) {
|
||||
_specularIntensity = dictionary.value<double>(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<bool>(ShadingInfo.identifier);
|
||||
if (p.animationTimeScale.has_value()) {
|
||||
if (!_geometry->hasAnimation()) {
|
||||
LWARNING("Animation time scale given to model without animation");
|
||||
}
|
||||
else if (std::holds_alternative<float>(*p.animationTimeScale)) {
|
||||
_geometry->setTimeScale(std::get<float>(*p.animationTimeScale));
|
||||
}
|
||||
else if (std::holds_alternative<Parameters::AnimationTimeUnit>(*p.animationTimeScale)) {
|
||||
Parameters::AnimationTimeUnit animationTimeUnit =
|
||||
std::get<Parameters::AnimationTimeUnit>(*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<bool>(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<bool>(DisableFaceCullingInfo.identifier);
|
||||
if (p.modelTransform.has_value()) {
|
||||
_modelTransform = *p.modelTransform;
|
||||
}
|
||||
|
||||
if (dictionary.hasKey(LightSourcesInfo.identifier)) {
|
||||
const ghoul::Dictionary& lsDictionary =
|
||||
dictionary.value<ghoul::Dictionary>(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 = LightSource::createFromDictionary(
|
||||
lsDictionary.value<ghoul::Dictionary>(k)
|
||||
);
|
||||
if (p.lightSources.has_value()) {
|
||||
std::vector<ghoul::Dictionary> lightsources = *p.lightSources;
|
||||
|
||||
for (const ghoul::Dictionary& lsDictionary : lightsources) {
|
||||
std::unique_ptr<LightSource> 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<glm::dvec3>(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<std::string>(
|
||||
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<bool>(
|
||||
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<LightSource>& 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>& 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>& 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
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <openspace/properties/scalar/boolproperty.h>
|
||||
#include <openspace/properties/scalar/floatproperty.h>
|
||||
#include <openspace/properties/vector/vec3property.h>
|
||||
#include <openspace/util/distanceconversion.h>
|
||||
#include <ghoul/misc/managedmemoryuniqueptr.h>
|
||||
#include <ghoul/io/model/modelreader.h>
|
||||
#include <ghoul/opengl/uniformcache.h>
|
||||
@@ -70,9 +71,21 @@ public:
|
||||
static documentation::Documentation Documentation();
|
||||
|
||||
private:
|
||||
enum class AnimationMode {
|
||||
Once = 0,
|
||||
LoopFromStart,
|
||||
LoopInfinitely,
|
||||
BounceFromStart,
|
||||
BounceInfinitely
|
||||
};
|
||||
|
||||
std::unique_ptr<ghoul::modelgeometry::ModelGeometry> _geometry;
|
||||
double _modelScale = 1.0;
|
||||
bool _forceRenderInvisible = false;
|
||||
bool _notifyInvisibleDropped = true;
|
||||
std::string _animationStart;
|
||||
AnimationMode _animationMode = AnimationMode::Once;
|
||||
properties::BoolProperty _enableAnimation;
|
||||
|
||||
properties::FloatProperty _ambientIntensity;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
#include <ghoul/opengl/texture.h>
|
||||
#include <ghoul/opengl/textureunit.h>
|
||||
#include <modules/spacecraftinstruments/util/imagesequencer.h>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
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<bool> 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<double> 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<Parameters>();
|
||||
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<Parameters>(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<std::string>(KeyGeomModelFile)) {
|
||||
// Handle single file
|
||||
file = absPath(dictionary.value<std::string>(KeyGeomModelFile));
|
||||
_geometry = ghoul::io::ModelReader::ref().loadModel(
|
||||
file,
|
||||
ghoul::io::ModelReader::ForceRenderInvisible::No,
|
||||
ghoul::io::ModelReader::NotifyInvisibleDropped::Yes
|
||||
);
|
||||
}
|
||||
else if (dictionary.hasValue<ghoul::Dictionary>(KeyGeomModelFile)) {
|
||||
LWARNING("Loading a model with several files is deprecated and will be "
|
||||
"removed in a future release"
|
||||
);
|
||||
|
||||
ghoul::Dictionary fileDictionary = dictionary.value<ghoul::Dictionary>(
|
||||
KeyGeomModelFile
|
||||
);
|
||||
std::vector<std::unique_ptr<ghoul::modelgeometry::ModelGeometry>> geometries;
|
||||
|
||||
for (std::string_view k : fileDictionary.keys()) {
|
||||
// Handle each file
|
||||
file = absPath(fileDictionary.value<std::string>(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<ghoul::modelgeometry::ModelGeometry> 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<ghoul::Dictionary>(keyProjection)
|
||||
p.projection
|
||||
);
|
||||
|
||||
double boundingSphereRadius = 1.0e9;
|
||||
if (dictionary.hasValue<double>(keyBoundingSphereRadius)) {
|
||||
boundingSphereRadius = dictionary.value<double>(keyBoundingSphereRadius);
|
||||
}
|
||||
double boundingSphereRadius = p.boundingSphereRadius.value_or(1.0e9);
|
||||
setBoundingSphere(boundingSphereRadius);
|
||||
|
||||
if (dictionary.hasValue<bool>(PerformShadingInfo.identifier)) {
|
||||
_performShading = dictionary.value<bool>(PerformShadingInfo.identifier);
|
||||
}
|
||||
_performShading = p.performShading.value_or(_performShading);
|
||||
|
||||
addProperty(_performShading);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#include <ghoul/misc/misc.h>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
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<std::string>(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
|
||||
{
|
||||
|
||||
Submodule support/coding/codegen updated: 2efd176817...4b06f1ad45
@@ -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
|
||||
|
||||
258
tests/test_distanceconversion.cpp
Normal file
258
tests/test_distanceconversion.cpp
Normal file
@@ -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 <openspace/util/distanceconstants.h>
|
||||
#include <openspace/util/distanceconversion.h>
|
||||
|
||||
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));
|
||||
}
|
||||
Reference in New Issue
Block a user