Merge pull request #1552 from OpenSpace/feature/model-animation

Feature/model animation
This commit is contained in:
Malin E
2021-04-21 10:40:36 +02:00
committed by GitHub
17 changed files with 951 additions and 433 deletions

View File

@@ -60,6 +60,7 @@ local initializeAndAddNodes = function()
Renderable = {
Type = "RenderableModel",
GeometryFile = models .. "/ISS.fbx",
ModelScale = "Centimeter",
LightSources = {
{
Type = "SceneGraphLightSource",

View File

@@ -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

View File

@@ -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__

View File

@@ -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

View File

@@ -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) };
}

View File

@@ -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) };
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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));
}

View File

@@ -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
{

View File

@@ -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

View 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));
}