载入中...
搜索中...
未找到
SkyWispsAsset.cpp
浏览该文件的文档.
1#include <algorithm>
2#include <cmath>
3#include <filesystem>
4#include <stdexcept>
5#include "common/Value.h"
11
12namespace eve::graphics {
13namespace {
14void fields(const Value& value, std::initializer_list<std::string_view> expected) {
15 if (!value.isObject() || value.keys().size() != expected.size())
16 throw std::runtime_error("Invalid wisps object fields");
17 for (const auto& key : value.keys())
18 if (std::find(expected.begin(), expected.end(), key) == expected.end())
19 throw std::runtime_error("Unknown wisps field: " + key);
20}
21const Value& field(const Value& value, const char* name) {
22 const auto* result = value.find(name);
23 if (!result) throw std::runtime_error(std::string("Missing wisps field: ") + name);
24 return *result;
25}
26std::string text(const Value& value, const char* name) {
27 const auto& result = field(value, name);
28 if (!result.isString()) throw std::runtime_error("Expected wisps string");
29 return result.asString();
30}
31float number(const Value& value) {
32 if (!value.isNumeric()) throw std::runtime_error("Expected wisps number");
33 const double v = value.isInt64() ? static_cast<double>(value.asInt()) : value.asDouble();
34 if (!std::isfinite(v) || std::abs(v) > 1e8) throw std::runtime_error("Wisps number outside supported range");
35 return static_cast<float>(v);
36}
37template <size_t N>
38std::array<float, N> vector(const Value& value) {
39 if (!value.isArray() || value.arraySize() != N) throw std::runtime_error("Invalid wisps vector size");
40 std::array<float, N> result{};
41 for (size_t i = 0; i < N; ++i) result[i] = number(value.at(i));
42 return result;
43}
44bool boolean(const Value& value) {
45 if (!value.isBool()) throw std::runtime_error("Expected wisps boolean");
46 return value.asBool();
47}
48std::shared_ptr<const filesystem::FileData> bytes(const std::string& path, size_t limit) {
50 if (!result) throw std::runtime_error(result.status().describe());
51 return std::move(result).takeValue();
52}
53Value document(const std::string& path, size_t limit) {
54 auto file = bytes(path, limit);
55 auto parsed = Value::fromJson(std::string(static_cast<const char*>(file->getData()), file->getSize()));
56 if (!parsed) throw std::runtime_error(parsed.status().describe());
57 return std::move(parsed).takeValue();
58}
59std::string resourcePath(const std::string& manifest, const std::string& relative) {
60 if (relative.empty() || relative.find_first_of("\\:") != std::string::npos)
61 throw std::runtime_error("Invalid relative wisps resource path");
62 const std::filesystem::path p(relative);
63 if (p.is_absolute()) throw std::runtime_error("Wisps resource must be relative");
64 for (const auto& part : p)
65 if (part == ".." || part == ".") throw std::runtime_error("Wisps resource escapes package");
66 return (std::filesystem::path(manifest).parent_path() / p).generic_string();
67}
68} // namespace
69SkyWispsAsset::SkyWispsAsset(std::shared_ptr<const Impl> data) : data_(std::move(data)) {}
70const SkyWispsLayer& SkyWispsAsset::layer() const noexcept { return data_->layer; }
71Result<SkyWispsAsset> SkyWispsAsset::load(const std::string& manifestPath) {
72 try {
73 const auto manifest = document(manifestPath, 65536);
74 const auto schema = text(manifest, "schema");
75 const bool lunar = schema == "eve.sky-wisps/7";
76 const bool packed = lunar || schema == "eve.sky-wisps/6";
77 if (lunar)
78 fields(manifest,
79 {"schema", "mesh", "density", "material", "contrast", "sunDisk", "sunDiskCurve", "daylight", "stars",
80 "starsNoise", "optics", "moonDisk", "moonColorTexture", "moonNormalTexture"});
81 else if (schema == "eve.sky-wisps/1")
82 fields(manifest, {"schema", "mesh", "density", "material"});
83 else if (schema == "eve.sky-wisps/2")
84 fields(manifest, {"schema", "mesh", "density", "material", "contrast"});
85 else if (schema == "eve.sky-wisps/3")
86 fields(manifest, {"schema", "mesh", "density", "material", "contrast", "sunDisk"});
87 else if (schema == "eve.sky-wisps/4")
88 fields(manifest, {"schema", "mesh", "density", "material", "contrast", "sunDisk", "sunDiskCurve"});
89 else if (schema == "eve.sky-wisps/5")
90 fields(manifest, {"schema", "mesh", "density", "material", "contrast", "sunDisk", "sunDiskCurve",
91 "daylight", "stars", "starsNoise"});
92 else if (schema == "eve.sky-wisps/6")
93 fields(manifest, {"schema", "mesh", "density", "material", "contrast", "sunDisk", "sunDiskCurve",
94 "daylight", "stars", "starsNoise", "optics"});
95 else
96 throw std::runtime_error("Unsupported wisps schema");
97 auto result = std::make_shared<Impl>();
98 const auto mesh = document(resourcePath(manifestPath, text(manifest, "mesh")), 32 * 1024 * 1024);
99 fields(mesh, {"schema", "space", "uvChannel", "layout", "topology", "vertices", "sourceSha256"});
100 if (text(mesh, "schema") != "eve.sky-mesh/1" || text(mesh, "space") != "unreal-local-centimetres" ||
101 text(mesh, "topology") != "triangle-list" || !field(mesh, "uvChannel").isInt64() ||
102 field(mesh, "uvChannel").asInt() != 3)
103 throw std::runtime_error("Unsupported wisps mesh contract");
104 const auto hash = text(mesh, "sourceSha256");
105 if (hash.size() != 64 || hash.find_first_not_of("0123456789abcdef") != std::string::npos)
106 throw std::runtime_error("Invalid source provenance hash");
107 const auto& layout = field(mesh, "layout");
108 constexpr const char* names[]{"x", "y", "z", "u", "v", "r", "g", "b", "a"};
109 if (!layout.isArray() || layout.arraySize() != 9) throw std::runtime_error("Invalid mesh layout");
110 for (size_t i = 0; i < 9; ++i)
111 if (!layout.at(i).isString() || layout.at(i).asString() != names[i])
112 throw std::runtime_error("Unsupported mesh layout");
113 const auto& vertices = field(mesh, "vertices");
114 if (!vertices.isArray() || vertices.arraySize() == 0 || vertices.arraySize() % 3 ||
115 vertices.arraySize() > 1000000)
116 throw std::runtime_error("Invalid sky mesh corner count");
117 result->corners.reserve(vertices.arraySize() * 9);
118 for (size_t i = 0; i < vertices.arraySize(); ++i) {
119 const auto corner = vector<9>(vertices.at(i));
120 result->corners.insert(result->corners.end(), corner.begin(), corner.end());
121 }
122 const auto& density = field(manifest, "density");
123 fields(density, {"path", "width", "height", "encoding"});
124 const auto dimension = [&](const char* name) {
125 const auto& n = field(density, name);
126 if (!n.isInt64() || n.asInt() < 1 || n.asInt() > 4096)
127 throw std::runtime_error("Invalid wisps texture dimensions");
128 return static_cast<uint32_t>(n.asInt());
129 };
130 auto& image = result->texture;
131 image.width = dimension("width");
132 image.height = dimension("height");
133 const auto encoding = text(density, "encoding");
134 if (encoding != "r8-linear" && encoding != "r8-srgb")
135 throw std::runtime_error("Unsupported wisps density encoding");
136 auto pixels = bytes(resourcePath(manifestPath, text(density, "path")), 16 * 1024 * 1024);
137 if (pixels->getSize() != size_t(image.width) * image.height)
138 throw std::runtime_error("Truncated wisps density texture");
139 const auto* source = static_cast<const std::byte*>(pixels->getData());
140 if (encoding == "r8-srgb") {
141 image.format = ShaderImageFormat::RGBA8Srgb;
142 result->pixels.resize(pixels->getSize() * 4);
143 for (size_t i = 0; i < pixels->getSize(); ++i) {
144 result->pixels[i * 4] = result->pixels[i * 4 + 1] = result->pixels[i * 4 + 2] = source[i];
145 result->pixels[i * 4 + 3] = std::byte{255};
146 }
147 } else {
148 image.format = ShaderImageFormat::R8;
149 result->pixels.assign(source, source + pixels->getSize());
150 }
151 image.bytes = result->pixels;
152 image.sampler = TextureSampler::linear();
153 image.sampler.repeatU = image.sampler.repeatV = true;
154 const auto& material = field(manifest, "material");
155 fields(material, {"domeScale", "color", "gradient", "movement", "moonForward", "morphRate", "morphPeriod",
156 "morphAmount", "opacity", "litIntensity", "cloudTime", "moonGradient", "staticClouds"});
157 auto& w = result->layer;
158 if (schema != "eve.sky-wisps/1") w.materialContrast = vector<3>(field(manifest, "contrast"));
159 w.authoredSkyLighting = schema != "eve.sky-wisps/1";
160 if (packed || schema == "eve.sky-wisps/3" || schema == "eve.sky-wisps/4" || schema == "eve.sky-wisps/5") {
161 const auto& disk = field(manifest, "sunDisk");
162 fields(disk, {"color", "shape"});
163 w.sunDiskColor = vector<3>(field(disk, "color"));
164 w.sunDiskShape = vector<3>(field(disk, "shape"));
165 if (*std::min_element(w.sunDiskColor.begin(), w.sunDiskColor.end()) < 0 || w.sunDiskShape[0] <= 0 ||
166 w.sunDiskShape[0] > 3.141593f || w.sunDiskShape[1] <= 0 || w.sunDiskShape[2] < 0)
167 throw std::runtime_error("Invalid solar disk radiance or shape");
168 }
169 if (packed || schema == "eve.sky-wisps/4" || schema == "eve.sky-wisps/5") {
170 const auto& curve = field(manifest, "sunDiskCurve");
171 if (!curve.isArray() || curve.arraySize() != 12)
172 throw std::runtime_error("Solar curve requires twelve RGB keys");
173 for (size_t i = 0; i < 12; ++i) {
174 auto key = vector<4>(curve.at(i));
175 if (key[0] < 0 || key[0] > 1 || key[1] < 0 || (i % 4 && key[0] <= w.sunDiskCurve[i - 1][0]) ||
176 (i % 4 == 3 && key[1] <= 0))
177 throw std::runtime_error("Invalid solar curve key order or value");
178 w.sunDiskCurve[i] = key;
179 }
180 w.sunDiskCurveEnabled = true;
181 }
182 if (schema == "eve.sky-wisps/5" || packed) {
183 const auto& cycle = field(manifest, "daylight");
184 fields(cycle, {"sun", "moon", "moonOrbit", "dayTint", "duskTint", "nightTint", "glow", "controls", "stars",
185 "starUv", "twinkle", "scatteringCurve"});
186 auto& d = w.daylight;
187 d.sun = vector<4>(field(cycle, "sun"));
188 d.moon = vector<4>(field(cycle, "moon"));
189 d.moonOrbit = vector<4>(field(cycle, "moonOrbit"));
190 d.dayTint = vector<4>(field(cycle, "dayTint"));
191 d.duskTint = vector<4>(field(cycle, "duskTint"));
192 d.nightTint = vector<4>(field(cycle, "nightTint"));
193 d.glow = vector<4>(field(cycle, "glow"));
194 d.controls = vector<4>(field(cycle, "controls"));
195 d.stars = vector<4>(field(cycle, "stars"));
196 d.starUv = vector<4>(field(cycle, "starUv"));
197 d.twinkle = vector<4>(field(cycle, "twinkle"));
198 const auto& keys = field(cycle, "scatteringCurve");
199 if (!keys.isArray() || keys.arraySize() != d.scatteringCurve.size())
200 throw std::runtime_error("Scattering curve requires 21 shared angle/RGB keys");
201 for (size_t i = 0; i < d.scatteringCurve.size(); ++i) d.scatteringCurve[i] = vector<4>(keys.at(i));
202 if (packed) {
203 const auto& optical = field(manifest, "optics");
204 fields(optical, {"rayDay", "rayDusk", "rayNight", "absorptionDay", "absorptionNight"});
205 d.rayDay = vector<4>(field(optical, "rayDay"));
206 d.rayDusk = vector<4>(field(optical, "rayDusk"));
207 d.rayNight = vector<4>(field(optical, "rayNight"));
208 d.absorptionDay = vector<4>(field(optical, "absorptionDay"));
209 d.absorptionNight = vector<4>(field(optical, "absorptionNight"));
210 w.opticalCycleEnabled = true;
211 }
212 if (lunar) {
213 const auto& disk = field(manifest, "moonDisk");
214 fields(disk, {"color", "shape", "lighting", "glow"});
215 d.moonDiskColor = vector<4>(field(disk, "color"));
216 d.moonDiskShape = vector<4>(field(disk, "shape"));
217 d.moonDiskLighting = vector<4>(field(disk, "lighting"));
218 d.moonDiskGlow = vector<4>(field(disk, "glow"));
219 }
220 auto checked = d.validate();
221 if (!checked) throw std::runtime_error(checked.status().describe());
222 const std::array names{"stars", "starsNoise", "moonColorTexture", "moonNormalTexture"};
223 for (size_t i = 0; i < (lunar ? 4u : 2u); ++i) {
224 const auto& descriptor = field(manifest, names[i]);
225 if (packed)
226 fields(descriptor, {"path", "width", "height", "encoding", "mipLevels"});
227 else
228 fields(descriptor, {"path", "width", "height", "encoding"});
229 auto& star = result->celestialImages[i];
230 const auto extent = [&](const char* key) {
231 const auto& v = field(descriptor, key);
232 if (!v.isInt64() || v.asInt() < 1 || v.asInt() > 4096)
233 throw std::runtime_error("Invalid star extent");
234 return static_cast<uint32_t>(v.asInt());
235 };
236 star.width = extent("width");
237 star.height = extent("height");
238 const auto format = text(descriptor, "encoding");
239 if (format != "rgba8-srgb" && format != "rgba8-linear" && !(packed && format == "rgba16-float"))
240 throw std::runtime_error("Invalid star encoding");
241 star.format = format == "rgba8-srgb" ? ShaderImageFormat::RGBA8Srgb : ShaderImageFormat::RGBA8;
242 if (format == "rgba16-float") star.format = ShaderImageFormat::RGBA16Float;
243 if (packed) star.mipLevels = extent("mipLevels");
244 unsigned maximumMips = 1;
245 for (unsigned size = std::max(star.width, star.height); size > 1; size >>= 1) ++maximumMips;
246 if (star.mipLevels > maximumMips) throw std::runtime_error("Invalid star mip count");
247 size_t expected = 0;
248 for (unsigned mip = 0; mip < star.mipLevels; ++mip)
249 expected += size_t(std::max(1u, star.width >> mip)) * std::max(1u, star.height >> mip) *
250 (format == "rgba16-float" ? 8u : 4u);
251 auto data = bytes(resourcePath(manifestPath, text(descriptor, "path")), 64 * 1024 * 1024);
252 if (data->getSize() != expected) throw std::runtime_error("Truncated star texture");
253 const auto* begin = static_cast<const std::byte*>(data->getData());
254 result->celestialPixels[i].assign(begin, begin + data->getSize());
255 star.bytes = result->celestialPixels[i];
256 star.sampler = star.mipLevels > 1 ? TextureSampler::linearMipmap() : TextureSampler::linear();
257 star.sampler.repeatU = star.sampler.repeatV = i < 2;
258 }
259 w.starsTexture = &result->celestialImages[0];
260 w.starsNoiseTexture = &result->celestialImages[1];
261 if (lunar) {
262 w.moonColorTexture = &result->celestialImages[2];
263 w.moonNormalTexture = &result->celestialImages[3];
264 if (w.moonColorTexture->format != ShaderImageFormat::RGBA16Float ||
265 w.moonNormalTexture->format != ShaderImageFormat::RGBA16Float)
266 throw std::runtime_error("Lunar runtime samples require linear RGBA16F");
267 }
268 w.daylightEnabled = true;
269 }
270 w.corners = result->corners;
271 w.densityTexture = &image;
272 w.color = vector<3>(field(material, "color"));
273 w.gradient = vector<4>(field(material, "gradient"));
274 w.movement = vector<2>(field(material, "movement"));
275 w.moonForward = vector<3>(field(material, "moonForward"));
276 w.domeScale = number(field(material, "domeScale"));
277 w.morphRate = number(field(material, "morphRate"));
278 w.morphPeriod = number(field(material, "morphPeriod"));
279 w.morphAmount = number(field(material, "morphAmount"));
280 w.opacity = number(field(material, "opacity"));
281 w.litIntensity = number(field(material, "litIntensity"));
282 w.cloudTime = number(field(material, "cloudTime"));
283 w.moonGradient = boolean(field(material, "moonGradient"));
284 w.staticClouds = boolean(field(material, "staticClouds"));
285 return Result<SkyWispsAsset>::success(SkyWispsAsset(std::move(result)));
286 } catch (const std::exception& e) {
288 Diagnostic::error(DiagnosticCode::InvalidArgument, e.what(), "sky.wisps-asset"));
289 }
290}
291Result<void> SkyDaylightLayer::validate() const {
292 const auto failure = [] {
293 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,
294 "Invalid daylight material coefficients", "sky.daylight"));
295 };
296 for (const auto* v : {&sun, &moon, &dayTint, &duskTint, &nightTint, &glow, &controls, &stars, &twinkle})
297 for (float x : *v)
298 if (!std::isfinite(x) || x < 0 || x > 1e6f) return failure();
299 for (const auto* v : {&moonOrbit, &starUv})
300 for (float x : *v)
301 if (!std::isfinite(x) || std::abs(x) > 1e6f) return failure();
302 if (std::abs(moonOrbit[0]) > 90 || std::abs(moonOrbit[1]) > 360 || std::abs(moonOrbit[2]) >= 1 ||
303 std::abs(moonOrbit[3]) > 1 || controls[2] > 1 || starUv[0] <= 0 || twinkle[3] != 0)
304 return failure();
305 for (size_t i = 0; i < scatteringCurve.size(); ++i) {
306 const auto& k = scatteringCurve[i];
307 for (float x : k)
308 if (!std::isfinite(x) || x < 0 || x > 1e6f) return failure();
309 if (k[0] > 180 || (i && k[0] <= scatteringCurve[i - 1][0])) return failure();
310 }
311 for (const auto* v : {&rayDay, &rayDusk, &rayNight, &absorptionDay, &absorptionNight})
312 for (float x : *v)
313 if (!std::isfinite(x) || x < 0 || x > 1) return failure();
314 if (rayDusk[3] != 0 || rayNight[3] != 0) return failure();
315 for (const auto* v : {&moonDiskColor, &moonDiskShape, &moonDiskLighting, &moonDiskGlow})
316 for (float x : *v)
317 if (!std::isfinite(x) || std::abs(x) > 1e6f) return failure();
318 for (size_t i = 0; i < 3; ++i)
319 if (moonDiskColor[i] < 0) return failure();
320 if ((moonDiskColor[3] != 0 && moonDiskColor[3] != 1) || moonDiskShape[0] <= 0 || moonDiskShape[0] > 1 ||
321 moonDiskShape[1] <= 0 || moonDiskShape[1] > 10 || std::abs(moonDiskShape[2]) > 360 || moonDiskShape[3] < 0 ||
322 moonDiskShape[3] > 1 || moonDiskLighting[0] < 0 || moonDiskLighting[1] < 0 || moonDiskLighting[2] < 0 ||
323 moonDiskLighting[2] > 29.53f || moonDiskLighting[3] < 0 || moonDiskLighting[3] > 10 || moonDiskGlow[0] < 0 ||
324 moonDiskGlow[1] <= 0 || moonDiskGlow[2] != 0 || moonDiskGlow[3] != 0)
325 return failure();
326 return Result<void>::success();
327}
328} // namespace eve::graphics
double value
float w
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
Vec3 relative
Definition AnimSmr.cpp:164
std::string descriptor
glm::vec4 p[6]
filesystem::File * file
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
std::string layout
std::uint32_t key
vk::UniqueImage image
glm::vec3 n
Definition Grass.cpp:63
float v
std::string encoding
std::string text
std::uint64_t bytes
std::string name
std::vector< Point > vertices
std::string path
Definition PlayHost.cpp:110
std::vector< std::string > fields
Definition PlayHost.cpp:111
float begin
float d
uint8_t * pixels
Mesh * mesh
Material * material
double number
bool boolean
Heightmap curve
int limit
Definition TreeMesh.cpp:164
float size
Definition TreeMesh.cpp:156
const UnitySourceAsset & source
Owning, renderer-independent dynamic values.
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
Definition Value.cpp:57
Immutable owned sky mesh and textures, loaded from a pack or generated procedurally.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
Result< std::shared_ptr< const FileData > > readPreparedFile(const std::string &path, size_t limit)
Join a queued read or synchronously read an immutable file snapshot.
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Definition Animation.h:25
const EditorValue * field(const EditorValue &value, const char *name)
Borrowed authored thin-cloud layer, consumed synchronously by sky preparation.