载入中...
搜索中...
未找到
VegetationField.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <glm/common.hpp>
6#include <glm/geometric.hpp>
7#include <limits>
8#include <new>
9
10namespace eve::graphics {
11namespace {
12bool finite(glm::vec3 v) { return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z); }
13bool finite(glm::vec4 v) { return finite(glm::vec3(v)) && std::isfinite(v.w); }
14bool unit(float v) { return std::isfinite(v) && v >= 0.f && v <= 1.f; }
15bool layersValid(std::array<uint8_t, 4> layers) {
16 return std::all_of(layers.begin(), layers.end(), [](uint8_t layer) { return layer <= 8; });
17}
18Diagnostic invalid(std::string message) {
19 return Diagnostic::error(DiagnosticCode::InvalidArgument, message, {}, {}, "graphics.vegetation");
20}
21Diagnostic exhausted() {
22 return Diagnostic::error(DiagnosticCode::Failed, "vegetation allocation failed", {}, {}, "graphics.vegetation");
23}
24glm::vec4 maskAt(const VegetationMask& mask, float u, float v) {
25 if (mask.pixels.empty()) return glm::vec4(1.f);
26 const float x = std::clamp(u * mask.width - 0.5f, 0.f, float(mask.width - 1));
27 const float y = std::clamp(v * mask.height - 0.5f, 0.f, float(mask.height - 1));
28 const auto x0 = uint32_t(x), y0 = uint32_t(y);
29 const auto x1 = std::min(x0 + 1, mask.width - 1), y1 = std::min(y0 + 1, mask.height - 1);
30 return glm::mix(
31 glm::mix(mask.pixels[size_t(y0) * mask.width + x0], mask.pixels[size_t(y0) * mask.width + x1], x - x0),
32 glm::mix(mask.pixels[size_t(y1) * mask.width + x0], mask.pixels[size_t(y1) * mask.width + x1], x - x0), y - y0);
33}
34glm::vec4 seasonalValue(const VegetationElement& e, float season) {
35 if (!e.seasonal) return e.value;
36 const float wrapped = season == 4.f ? 0.f : season;
37 const auto i = unsigned(wrapped);
38 const float f = wrapped - i;
39 return glm::mix(e.seasons[i], e.seasons[(i + 1) % 4], f * f * (3.f - 2.f * f));
40}
41glm::vec4 blend(glm::vec4 previous, glm::vec4 target, float weight, VegetationBlend mode) {
42 switch (mode) {
43 case VegetationBlend::Replace: return glm::mix(previous, target, weight);
45 case VegetationBlend::Multiply: return previous * glm::mix(glm::vec4(1.f), target, weight);
46 case VegetationBlend::Minimum: return glm::mix(previous, glm::min(previous, target), weight);
47 case VegetationBlend::Maximum: return glm::mix(previous, glm::max(previous, target), weight);
48 }
49 return previous;
50}
51} // namespace
52
54 try {
55 return Result<std::vector<VegetationElement>>::success(elements_);
56 } catch (const std::bad_alloc&) {
58 Diagnostic::error(DiagnosticCode::Failed, "vegetation element snapshot allocation failed", {},
59 {}, "graphics.vegetation"));
60 }
61}
62
63Result<void> VegetationField::replace(const VegetationGlobals& globals, std::span<const VegetationElement> elements) {
64 if (revision_ == std::numeric_limits<uint64_t>::max())
65 return Result<void>::failure(invalid("field publication revision exhausted"));
66 if (!finite(globals.color) || !finite(globals.extras) || !finite(globals.motion) || !finite(globals.vertex) ||
67 !std::isfinite(globals.season) || globals.season < 0.f || globals.season > 4.f || elements.size() > 4096)
68 return Result<void>::failure(invalid("invalid globals or element count exceeds 4096"));
69 size_t totalPixels = 0;
70 for (const auto& e : elements) {
71 if (unsigned(e.channel) > unsigned(VegetationChannel::Vertex) ||
72 unsigned(e.blend) > unsigned(VegetationBlend::Maximum) ||
73 unsigned(e.shape) > unsigned(VegetationShape::Box) || !finite(e.center) || !finite(e.extents) ||
74 e.extents.x < 1e-4f || e.extents.y < 1e-4f || e.extents.z < 1e-4f || !std::isfinite(e.yaw) ||
75 !unit(e.opacity) || !unit(e.edgeFade) || !finite(e.value) || e.layers == 0 || (e.layers & ~0x1ffu))
76 return Result<void>::failure(invalid("invalid element shape, channel, blend, layers or parameters"));
77 for (const auto& value : e.seasons)
78 if (!finite(value)) return Result<void>::failure(invalid("non-finite season value"));
79 const auto& m = e.mask;
80 if ((m.pixels.empty() && (m.width != 0 || m.height != 0)) ||
81 (!m.pixels.empty() && (m.width == 0 || m.height == 0 || m.width > 2048 || m.height > 2048 ||
82 m.pixels.size() != size_t(m.width) * m.height)))
83 return Result<void>::failure(invalid("invalid mask dimensions or pixel count"));
84 totalPixels += m.pixels.size();
85 if (totalPixels > 4 * 1024 * 1024) return Result<void>::failure(invalid("field mask pixel budget exceeded"));
86 for (auto pixel : m.pixels)
87 if (!finite(pixel) || !unit(pixel.a)) return Result<void>::failure(invalid("invalid mask pixel"));
88 }
89 try {
90 std::vector<VegetationElement> candidate(elements.begin(), elements.end());
91 std::stable_sort(candidate.begin(), candidate.end(),
92 [](const auto& a, const auto& b) { return a.priority < b.priority; });
93 elements_.swap(candidate);
94 globals_ = globals;
95 ++revision_;
96 return Result<void>::success();
97 } catch (const std::bad_alloc&) {
98 return Result<void>::failure(exhausted());
99 }
100}
101
102VegetationSample VegetationField::evaluate(glm::vec3 position, std::array<uint8_t, 4> layers) const {
103 std::array<glm::vec4, 4> values{globals_.color, globals_.extras, globals_.motion, globals_.vertex};
104 for (const auto& e : elements_) {
105 const size_t channel = size_t(e.channel);
106 if (!(e.layers & (1u << layers[channel]))) continue;
107 const auto d = position - e.center;
108 const float c = std::cos(e.yaw), s = std::sin(e.yaw);
109 const glm::vec3 q = glm::vec3(c * d.x - s * d.z, d.y, s * d.x + c * d.z) / e.extents;
110 if (!finite(q)) {
111 const glm::vec4 invalidValue(std::numeric_limits<float>::quiet_NaN());
112 return {invalidValue, invalidValue, invalidValue, invalidValue};
113 }
114 const float distance = e.shape == VegetationShape::Ellipsoid
115 ? glm::length(q)
116 : std::max({std::abs(q.x), std::abs(q.y), std::abs(q.z)});
117 if (distance >= 1.f) continue;
118 const float t = e.edgeFade > 0.f ? std::clamp((1.f - distance) / e.edgeFade, 0.f, 1.f) : 1.f;
119 const auto mask = maskAt(e.mask, q.x * 0.5f + 0.5f, q.z * 0.5f + 0.5f);
120 auto target = seasonalValue(e, globals_.season);
121 target *= glm::vec4(glm::vec3(mask), 1.f);
122 values[channel] = blend(values[channel], target, t * t * (3.f - 2.f * t) * e.opacity * mask.a, e.blend);
123 }
124 return {values[0], values[1], values[2], values[3]};
125}
126
127Result<VegetationSample> VegetationField::sample(glm::vec3 position, std::array<uint8_t, 4> layers) const {
128 if (!finite(position) || !layersValid(layers))
129 return Result<VegetationSample>::failure(invalid("invalid sample position or layer"));
130 auto result = evaluate(position, layers);
131 if (!finite(result.color) || !finite(result.extras) || !finite(result.motion) || !finite(result.vertex))
132 return Result<VegetationSample>::failure(invalid("field composition overflow"));
134}
135
136Result<VegetationAtlas> VegetationField::bake(glm::vec3 center, glm::vec3 extent, uint32_t width, uint32_t height,
137 std::array<uint8_t, 4> layers, bool snapToTexel) const {
138 if (!finite(center) || !finite(extent) || extent.x < 1e-4f || extent.y < 1e-4f || extent.z < 1e-4f || width == 0 ||
139 height == 0 || width > 2048 || height > 2048 || !layersValid(layers))
140 return Result<VegetationAtlas>::failure(invalid("invalid atlas geometry, dimensions or layer"));
141 const double dx = 2.0 * extent.x / width, dz = 2.0 * extent.z / height;
142 if (snapToTexel) {
143 center.x = float(std::round(double(center.x) / dx) * dx);
144 center.z = float(std::round(double(center.z) / dz) * dz);
145 }
146 if (!finite(center) || !finite(center + extent) || !finite(center - extent))
147 return Result<VegetationAtlas>::failure(invalid("atlas bounds overflow"));
148 try {
149 VegetationAtlas atlas;
150 atlas.width = width;
151 atlas.height = height;
152 atlas.center = center;
153 atlas.extent = extent;
154 for (auto& channel : atlas.channels) channel.resize(size_t(width) * height);
155 for (uint32_t y = 0; y < height; ++y) {
156 for (uint32_t x = 0; x < width; ++x) {
157 const glm::vec3 p(float(double(center.x) - extent.x + (x + 0.5) * dx), center.y,
158 float(double(center.z) - extent.z + (y + 0.5) * dz));
159 auto result = sample(p, layers);
160 if (!result.ok()) return Result<VegetationAtlas>::failure(result.status());
161 const auto& v = result.value();
162 const size_t index = size_t(y) * width + x;
163 atlas.channels[0][index] = v.color;
164 atlas.channels[1][index] = v.extras;
165 atlas.channels[2][index] = v.motion;
166 atlas.channels[3][index] = v.vertex;
167 }
168 }
169 return Result<VegetationAtlas>::success(std::move(atlas));
170 } catch (const std::bad_alloc&) {
171 return Result<VegetationAtlas>::failure(exhausted());
172 }
173}
174} // namespace eve::graphics
LogicalId target
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
const std::string & s
int mask
glm::vec4 p[6]
std::map< std::string, Var > values
std::string message
float u
Definition Grass.cpp:233
std::array< double, 10 > q
float v
std::int32_t c
float blend
std::uint32_t height
std::uint32_t width
std::array< float, 3 > position
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
graphics::Canvas * previous
bool finite
TileLayer * layer
float f
float d
float t
float dz
float dx
std::size_t elements
TacticalUnit * unit
uint32_t index
float m[16]
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
Result< std::vector< VegetationElement > > snapshotElements() const
Return an owning, priority-ordered element snapshot for revision-safe editor previews.
Result< void > replace(const VegetationGlobals &globals, std::span< const VegetationElement > elements)
Validate and atomically replace the entire field; failure preserves prior state.
eve::Diagnostic Diagnostic
卡牌游戏 UI 工具模块:工厂 + 脚本绑定入口。 功能参考 ycarowr/UiCard:扇形手牌布局、抽牌/洗牌、悬浮放大、拖拽到落牌区、 敌方手牌(背面/偷看)、费用不足置灰,以及可实时调节的布局...
Definition Animation.h:25
VegetationBlend
Ordered field composition operation.
Result< int > invalid(std::string message)
Invalid.
Four owning arrays in row-major X/Z order at texel centers.
std::array< std::vector< glm::vec4 >, 4 > channels
Owning global defaults. Extras = emission, wetness, overlay, alpha. Motion = signed world X/Z directi...
Owning sampled field channels; no lifetime dependency on the field.