载入中...
搜索中...
未找到
SkyProfile.cpp
浏览该文件的文档.
2#include <algorithm>
3#include <cmath>
4#include <limits>
5#include <stdexcept>
6#include <string_view>
7#include "common/Value.h"
11
12namespace eve::daynight {
13namespace {
14void fields(const Value& value, std::initializer_list<std::string_view> names) {
15 if (!value.isObject()) throw std::runtime_error("Expected sky profile object");
16 const auto keys = value.keys();
17 if (keys.size() != names.size()) throw std::runtime_error("Missing or extra sky profile fields");
18 for (const auto& key : keys)
19 if (std::find(names.begin(), names.end(), key) == names.end())
20 throw std::runtime_error("Unknown sky profile field: " + key);
21}
22const Value& field(const Value& value, const char* name) {
23 const auto* result = value.find(name);
24 if (!result) throw std::runtime_error(std::string("Missing sky profile field: ") + name);
25 return *result;
26}
27double number(const Value& value) {
28 if (!value.isNumeric()) throw std::runtime_error("Sky profile requires a number");
29 const double result = value.isInt64() ? static_cast<double>(value.asInt()) : value.asDouble();
30 if (!std::isfinite(result)) throw std::runtime_error("Nonfinite sky profile number");
31 return result;
32}
33double number(const Value& value, const char* name) { return number(field(value, name)); }
34float scalar(const Value& value, const char* name) {
35 const double result = number(value, name);
36 if (std::abs(result) > std::numeric_limits<float>::max()) throw std::runtime_error("Sky profile float overflow");
37 const float converted = static_cast<float>(result);
38 if (result != 0 && converted == 0) throw std::runtime_error("Sky profile float underflow");
39 return converted;
40}
41template <typename T, size_t N>
42std::array<T, N> vector(const Value& object, const char* name) {
43 const auto& value = field(object, name);
44 if (!value.isArray() || value.arraySize() != N) throw std::runtime_error("Invalid sky profile vector size");
45 std::array<T, N> result{};
46 for (size_t i = 0; i < N; ++i) {
47 const double item = number(value.at(i));
48 if (std::abs(item) > std::numeric_limits<T>::max()) throw std::runtime_error("Sky profile vector overflow");
49 result[i] = static_cast<T>(item);
50 if (item != 0 && result[i] == 0) throw std::runtime_error("Sky profile vector underflow");
51 }
52 return result;
53}
54void text(const Value& object, const char* name, std::string_view expected) {
55 const auto& value = field(object, name);
56 if (!value.isString() || value.asString() != expected)
57 throw std::runtime_error(std::string("Unsupported sky profile ") + name);
58}
59} // namespace
66SkyProfile::SkyProfile(std::shared_ptr<const Impl> data) : data_(std::move(data)) {}
67const SkyRuntimeSettings& SkyProfile::settings() const noexcept { return data_->settings; }
68const graphics::SkyAtmosphereParameters& SkyProfile::atmosphere() const noexcept { return data_->atmosphere; }
69const std::string& SkyProfile::wispsAsset() const noexcept { return data_->wispsAsset; }
70
71std::optional<uint32_t> SkyProfile::proceduralSeed() const noexcept { return data_->seed; }
73 try {
74 const auto& version = field(document, "version");
75 if (!version.isInt64() || version.asInt() < 1 || version.asInt() > 6)
76 throw std::runtime_error("Unsupported sky profile version");
77 if (version.asInt() == 1)
78 fields(document, {"schema", "version", "mode", "clock", "weather", "sun", "atmosphere"});
79 else if (version.asInt() == 2)
80 fields(document, {"schema", "version", "mode", "clock", "weather", "sun", "atmosphere", "heightFog"});
81 else if (version.asInt() == 3)
82 fields(document,
83 {"schema", "version", "mode", "clock", "weather", "sun", "atmosphere", "heightFog", "wispsAsset"});
84 else if (version.asInt() == 6)
85 fields(document, {"schema", "version", "mode", "clock", "weather", "sun", "atmosphere", "heightFog",
86 "wispsAsset", "cloudMotion", "proceduralSeed"});
87 else
88 fields(document, {"schema", "version", "mode", "clock", "weather", "sun", "atmosphere", "heightFog",
89 "wispsAsset", "cloudMotion"});
90 text(document, "schema", "eve.sky-profile");
91 text(document, "mode", "atmosphere");
92 auto result = std::make_shared<Impl>();
93 if (version.asInt() >= 3) {
94 const auto& path = field(document, "wispsAsset");
95 if (version.asInt() == 6) {
96 const auto& seed = field(document, "proceduralSeed");
97 if (!seed.isInt64() || seed.asInt() < 0 || seed.asInt() > 4294967295LL)
98 throw std::runtime_error("Invalid procedural sky seed");
99 if (path.isNull()) result->seed = static_cast<uint32_t>(seed.asInt());
100 }
101 if (!result->seed && (!path.isString() || path.asString().empty() || path.asString().size() > 4096))
102 throw std::runtime_error("Invalid wisps manifest path");
103 if (!result->seed) result->wispsAsset = path.asString();
104 }
105 if (version.asInt() >= 4) {
106 const auto& source = field(document, "cloudMotion");
107 fields(source, {"phase", "speed", "timeScale", "windMultiplier"});
108 result->settings.cloudMotion = {number(source, "phase"), number(source, "speed"),
109 number(source, "timeScale"), number(source, "windMultiplier")};
110 }
111 auto motionValid = result->settings.cloudMotion.validate();
112 if (!motionValid) return Result<SkyProfile>::failure(motionValid.status());
113 const auto& clock = field(document, "clock");
114 fields(clock, {"initialHour", "hoursPerSecond"});
115 result->settings.clock = {number(clock, "initialHour"), number(clock, "hoursPerSecond")};
116 const auto& weather = field(document, "weather");
117 fields(weather, {"cloudCoverage", "fog", "rain", "snow", "windXZ"});
118 result->settings.weather = {number(weather, "cloudCoverage"), number(weather, "fog"), number(weather, "rain"),
119 number(weather, "snow"), vector<double, 2>(weather, "windXZ")};
120 const auto& sun = field(document, "sun");
121 fields(sun, {"pitchDegrees", "yawDegrees", "irradiance"});
122 result->settings.sunPitchDegrees = number(sun, "pitchDegrees");
123 result->settings.sunYawDegrees = number(sun, "yawDegrees");
124 result->settings.sunIrradiance = vector<float, 3>(sun, "irradiance");
125 const auto& atmosphere = field(document, "atmosphere");
126 fields(atmosphere, {"rayleigh", "rayleighHeightKm", "mieScattering", "mieHeightKm", "mieAbsorption",
127 "mieAnisotropy", "ozoneAbsorption", "ozoneCenterKm", "ozoneHalfWidthKm", "groundRadiusKm",
128 "atmosphereHeightKm", "groundAlbedo", "multiScatteringFactor"});
129 auto& p = result->atmosphere;
130 p.rayleigh = vector<float, 3>(atmosphere, "rayleigh");
131 p.rayleighHeightKm = scalar(atmosphere, "rayleighHeightKm");
132 p.mieScattering = vector<float, 3>(atmosphere, "mieScattering");
133 p.mieHeightKm = scalar(atmosphere, "mieHeightKm");
134 p.mieAbsorption = vector<float, 3>(atmosphere, "mieAbsorption");
135 p.mieAnisotropy = scalar(atmosphere, "mieAnisotropy");
136 p.ozoneAbsorption = vector<float, 3>(atmosphere, "ozoneAbsorption");
137 p.ozoneCenterKm = scalar(atmosphere, "ozoneCenterKm");
138 p.ozoneHalfWidthKm = scalar(atmosphere, "ozoneHalfWidthKm");
139 p.groundRadiusKm = scalar(atmosphere, "groundRadiusKm");
140 p.atmosphereHeightKm = scalar(atmosphere, "atmosphereHeightKm");
141 p.groundAlbedo = vector<float, 3>(atmosphere, "groundAlbedo");
142 p.multiScatteringFactor = scalar(atmosphere, "multiScatteringFactor");
143 if (version.asInt() >= 2) {
144 const auto& source = field(document, "heightFog");
145 if (version.asInt() >= 5)
146 fields(source, {"densityPerMetre", "heightFalloffPerMetre", "baseHeightMetres", "startDistanceMetres",
147 "inscattering", "maximumOpacity", "directionalInscattering", "directionalExponent",
148 "directionalStartDistanceMetres", "atmosphereContribution", "skyDistanceMetres",
149 "directionalElevationRange"});
150 else
151 fields(source, {"densityPerMetre", "heightFalloffPerMetre", "baseHeightMetres", "startDistanceMetres",
152 "inscattering", "maximumOpacity", "directionalInscattering", "directionalExponent",
153 "directionalStartDistanceMetres", "atmosphereContribution", "skyDistanceMetres"});
154 auto& fog = p.heightFog;
155 if (version.asInt() >= 5)
156 fog.directionalElevationRange = vector<float, 2>(source, "directionalElevationRange");
157 fog.densityPerMetre = scalar(source, "densityPerMetre");
158 fog.heightFalloffPerMetre = scalar(source, "heightFalloffPerMetre");
159 fog.baseHeightMetres = scalar(source, "baseHeightMetres");
160 fog.startDistanceMetres = scalar(source, "startDistanceMetres");
161 fog.inscattering = vector<float, 3>(source, "inscattering");
162 fog.maximumOpacity = scalar(source, "maximumOpacity");
163 fog.directionalInscattering = vector<float, 3>(source, "directionalInscattering");
164 fog.directionalExponent = scalar(source, "directionalExponent");
165 fog.directionalStartDistanceMetres = scalar(source, "directionalStartDistanceMetres");
166 fog.atmosphereContribution = scalar(source, "atmosphereContribution");
167 fog.skyDistanceMetres = scalar(source, "skyDistanceMetres");
168 }
169 auto timeline = SkyTimeline::create(result->settings.clock, result->settings.weather);
170 if (!timeline) return Result<SkyProfile>::failure(timeline.status());
171 auto orbit = SkySolarOrbit::create(result->settings.sunPitchDegrees, result->settings.sunYawDegrees);
172 if (!orbit) return Result<SkyProfile>::failure(orbit.status());
173 for (float intensity : result->settings.sunIrradiance)
174 if (intensity < 0) throw std::runtime_error("Solar irradiance must be nonnegative");
175 auto optical = p.validate();
176 if (!optical) return Result<SkyProfile>::failure(optical.status());
177 return Result<SkyProfile>::success(SkyProfile(std::move(result)));
178 } catch (const std::exception& error) {
181 }
182}
183} // namespace eve::daynight
double value
glm::vec4 p[6]
std::uint32_t key
std::string text
std::string name
std::string error
Definition Package.cpp:60
std::string path
Definition PlayHost.cpp:110
std::vector< std::string > fields
Definition PlayHost.cpp:111
std::uint32_t seed
Definition PointSet.cpp:807
float begin
double number
const UnitySourceAsset & source
Owning, renderer-independent dynamic values.
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
Immutable validated atmospheric sky profile, schema eve.sky-profile versions 1 through 6.
Definition SkyProfile.h:29
static Result< SkyProfile > decode(const Value &document)
Validate a complete profile without creating GPU resources or changing any runtime.
const SkyRuntimeSettings & settings() const noexcept
Borrow runtime settings, valid for this profile's lifetime.
const graphics::SkyAtmosphereParameters & atmosphere() const noexcept
Borrow optical settings, valid for this profile's lifetime.
const std::string & wispsAsset() const noexcept
Borrow optional VFS wisps manifest path; empty for versions 1/2 or explicitly selected procedural ass...
std::optional< uint32_t > proceduralSeed() const noexcept
Explicit procedural-v1 seed when version 6 selects a null asset; absent for loaded/legacy profiles.
static Result< SkySolarOrbit > create(double pitchDegrees, double yawDegrees)
Prepare a daily orbit with pitch [-90,90] and yaw [-360,360] degrees.
static Result< SkyTimeline > create(const SkyClock &clock, const SkyWeather &weather)
Construct validated state. Clock hours are [0,24), speed [0,24] hours/second.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
const EditorValue * field(const EditorValue &value, const char *name)
std::optional< uint32_t > seed
graphics::SkyAtmosphereParameters atmosphere
Independent runtime settings; not a serialized profile or a legacy DayNight override.
Definition SkyRuntime.h:21
Spherical atmospheric coefficients in inverse kilometres and kilometre heights.