载入中...
搜索中...
未找到
VegetationPresetTexture.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <new>
6
7namespace eve::asset_import {
8namespace {
9
10bool valid(const VegetationPresetImage& image, std::uint64_t maximumPixels) {
11 const auto count = std::uint64_t(image.width) * image.height;
12 return image.width && image.height && count <= maximumPixels && count <= SIZE_MAX / 4 &&
13 image.pixels.size() == std::size_t(count) * 4;
14}
15
16float channel(const VegetationPresetImage& image, float u, float v, unsigned component) {
17 const float x = std::clamp(u * image.width - 0.5f, 0.f, float(image.width - 1));
18 const float y = std::clamp(v * image.height - 0.5f, 0.f, float(image.height - 1));
19 const auto x0 = std::uint32_t(x), y0 = std::uint32_t(y);
20 const auto x1 = std::min(x0 + 1, image.width - 1), y1 = std::min(y0 + 1, image.height - 1);
21 const auto at = [&](std::uint32_t px, std::uint32_t py) {
22 return image.pixels[(std::size_t(py) * image.width + px) * 4 + component] / 255.f;
23 };
24 return std::lerp(std::lerp(at(x0, y0), at(x1, y0), x - x0), std::lerp(at(x0, y1), at(x1, y1), x - x0), y - y0);
25}
26
27Result<float> sample(const VegetationPresetImage& image, const VegetationTextureChannelRecipe& recipe, float u,
28 float v) {
29 float value = 0.f;
30 if (recipe.selector == "GET_RED")
31 value = channel(image, u, v, 0);
32 else if (recipe.selector == "GET_GREEN")
33 value = channel(image, u, v, 1);
34 else if (recipe.selector == "GET_BLUE")
35 value = channel(image, u, v, 2);
36 else if (recipe.selector == "GET_ALPHA")
37 value = channel(image, u, v, 3);
38 else if (recipe.selector == "GET_MAX")
39 value = std::max({channel(image, u, v, 0), channel(image, u, v, 1), channel(image, u, v, 2)});
40 else if (recipe.selector == "GET_GRAY" || recipe.selector == "GET_GREY")
41 value = (channel(image, u, v, 0) + channel(image, u, v, 1) + channel(image, u, v, 2)) / 3.f;
42 else
44 "unsupported texture channel selector: " + recipe.selector, {},
45 {}, "asset.import.vegetation-preset.texture"));
46 if (recipe.action == "ACTION_ONE_MINUS")
47 value = 1.f - value;
48 else if (!recipe.action.empty())
50 "unsupported texture channel action: " + recipe.action, {}, {},
51 "asset.import.vegetation-preset.texture"));
52 return Result<float>::success(std::clamp(value, 0.f, 1.f));
53}
54
55float gammaToLinear(float value) {
56 return value <= 0.04045f ? value / 12.92f : std::pow((value + 0.055f) / 1.055f, 2.4f);
57}
58float linearToGamma(float value) {
59 return value <= 0.0031308f ? value * 12.92f : 1.055f * std::pow(value, 1.f / 2.4f) - 0.055f;
60}
61
62Result<void> transformNormalMap(VegetationPresetImage& image, const asset::CanonicalMeshData& mesh,
63 bool objectToTangent) {
64 const auto count = mesh.positions.size() / 3;
65 const auto tangent = mesh.attributes.find("TANGENT");
66 const auto uv = mesh.texcoords.find(0);
67 if (!count || mesh.normals.size() != count * 3 || tangent == mesh.attributes.end() ||
68 tangent->second.components != 4 || tangent->second.values.size() != count * 4 || uv == mesh.texcoords.end() ||
69 uv->second.size() != count * 2 || mesh.indices.size() % 3)
72 "normal-space transform requires normals, tangent float4, UV0 and triangles", {}, {},
73 "asset.import.vegetation-preset.texture"));
74 auto norm = [](float& x, float& y, float& z) {
75 const float l = std::sqrt(x * x + y * y + z * z);
76 if (l < 1e-8f) return false;
77 x /= l;
78 y /= l;
79 z /= l;
80 return true;
81 };
82 struct Frame {
83 std::uint32_t a = 0, b = 0, c = 0;
84 float w0 = 0, w1 = 0, w2 = 0;
85 bool covered = false;
86 };
87 struct Triangle {
88 std::uint32_t a = 0, b = 0, c = 0;
89 float ax = 0, ay = 0, bx = 0, by = 0, cx = 0, cy = 0, determinant = 0;
90 int minX = 0, maxX = -1, minY = 0, maxY = -1;
91 };
92 constexpr std::uint32_t tileSize = 16;
93 const auto tilesX = (image.width + tileSize - 1) / tileSize;
94 const auto tilesY = (image.height + tileSize - 1) / tileSize;
95 std::vector<Frame> frames(std::size_t(image.width) * image.height);
96 std::vector<Triangle> triangles;
97 triangles.reserve(mesh.indices.size() / 3);
98 std::vector<std::vector<std::uint32_t>> bins(std::size_t(tilesX) * tilesY);
99 std::uint64_t rasterWork = 0, binReferences = 0;
100 for (std::size_t k = 0; k < mesh.indices.size(); k += 3) {
101 const auto a = mesh.indices[k], b = mesh.indices[k + 1], c = mesh.indices[k + 2];
102 if (a >= count || b >= count || c >= count)
104 "normal-space mesh index out of range", {}, {},
105 "asset.import.vegetation-preset.texture"));
106 const float ax = uv->second[a * 2], ay = uv->second[a * 2 + 1], bx = uv->second[b * 2],
107 by = uv->second[b * 2 + 1], cx = uv->second[c * 2], cy = uv->second[c * 2 + 1];
108 if (!std::isfinite(ax) || !std::isfinite(ay) || !std::isfinite(bx) || !std::isfinite(by) ||
109 !std::isfinite(cx) || !std::isfinite(cy))
111 "normal-space mesh contains nonfinite UVs", {}, {},
112 "asset.import.vegetation-preset.texture"));
113 const float determinant = (by - cy) * (ax - cx) + (cx - bx) * (ay - cy);
114 if (std::abs(determinant) < 1e-10f) continue;
115 const int minX = std::max(0, int(std::ceil(std::min({ax, bx, cx}) * image.width - .5f)));
116 const int maxX = std::min(int(image.width) - 1, int(std::floor(std::max({ax, bx, cx}) * image.width - .5f)));
117 const int minY = std::max(0, int(std::ceil(std::min({ay, by, cy}) * image.height - .5f)));
118 const int maxY = std::min(int(image.height) - 1, int(std::floor(std::max({ay, by, cy}) * image.height - .5f)));
119 if (minX > maxX || minY > maxY) continue;
120 rasterWork += std::uint64_t(maxX - minX + 1) * std::uint64_t(maxY - minY + 1);
121 if (rasterWork > 512ull * 1024ull * 1024ull)
123 "normal-space raster work budget exceeded", {}, {},
124 "asset.import.vegetation-preset.texture"));
125 const auto triangleIndex = std::uint32_t(triangles.size());
126 triangles.push_back({a, b, c, ax, ay, bx, by, cx, cy, determinant, minX, maxX, minY, maxY});
127 const auto firstTileX = std::uint32_t(minX) / tileSize, lastTileX = std::uint32_t(maxX) / tileSize;
128 const auto firstTileY = std::uint32_t(minY) / tileSize, lastTileY = std::uint32_t(maxY) / tileSize;
129 binReferences += std::uint64_t(lastTileX - firstTileX + 1) * (lastTileY - firstTileY + 1);
130 if (binReferences > 32ull * 1024ull * 1024ull)
132 "normal-space triangle-bin budget exceeded", {}, {},
133 "asset.import.vegetation-preset.texture"));
134 for (auto tileY = firstTileY; tileY <= lastTileY; ++tileY)
135 for (auto tileX = firstTileX; tileX <= lastTileX; ++tileX)
136 bins[std::size_t(tileY) * tilesX + tileX].push_back(triangleIndex);
137 }
138 for (std::uint32_t tileY = 0; tileY < tilesY; ++tileY)
139 for (std::uint32_t tileX = 0; tileX < tilesX; ++tileX) {
140 const int tileMinX = int(tileX * tileSize), tileMinY = int(tileY * tileSize);
141 const int tileMaxX = std::min(int(image.width) - 1, tileMinX + int(tileSize) - 1);
142 const int tileMaxY = std::min(int(image.height) - 1, tileMinY + int(tileSize) - 1);
143 for (const auto triangleIndex : bins[std::size_t(tileY) * tilesX + tileX]) {
144 const auto& triangle = triangles[triangleIndex];
145 for (int py = std::max(tileMinY, triangle.minY); py <= std::min(tileMaxY, triangle.maxY); ++py)
146 for (int px = std::max(tileMinX, triangle.minX); px <= std::min(tileMaxX, triangle.maxX); ++px) {
147 auto& frame = frames[std::size_t(py) * image.width + px];
148 const float u = (px + .5f) / image.width, v = (py + .5f) / image.height;
149 const float w0 = ((triangle.by - triangle.cy) * (u - triangle.cx) +
150 (triangle.cx - triangle.bx) * (v - triangle.cy)) /
151 triangle.determinant;
152 const float w1 = ((triangle.cy - triangle.ay) * (u - triangle.cx) +
153 (triangle.ax - triangle.cx) * (v - triangle.cy)) /
154 triangle.determinant;
155 const float w2 = 1 - w0 - w1;
156 if (w0 >= -1e-5f && w1 >= -1e-5f && w2 >= -1e-5f)
157 frame = {triangle.a, triangle.b, triangle.c, w0, w1, w2, true};
158 }
159 }
160 }
161 for (std::uint32_t py = 0; py < image.height; ++py)
162 for (std::uint32_t px = 0; px < image.width; ++px) {
163 const auto& frame = frames[std::size_t(py) * image.width + px];
164 if (!frame.covered) continue;
165 auto interp = [&](const std::vector<float>& values, unsigned stride, unsigned component) {
166 return values[frame.a * stride + component] * frame.w0 +
167 values[frame.b * stride + component] * frame.w1 +
168 values[frame.c * stride + component] * frame.w2;
169 };
170 float nx = interp(mesh.normals, 3, 0), ny = interp(mesh.normals, 3, 1), nz = interp(mesh.normals, 3, 2);
171 float tx = interp(tangent->second.values, 4, 0), ty = interp(tangent->second.values, 4, 1),
172 tz = interp(tangent->second.values, 4, 2);
173 if (!norm(nx, ny, nz) || !norm(tx, ty, tz))
175 "degenerate normal-space frame", {}, {},
176 "asset.import.vegetation-preset.texture"));
177 const float sign = interp(tangent->second.values, 4, 3) >= 0 ? 1.f : -1.f;
178 float bx = (ny * tz - nz * ty) * sign, by = (nz * tx - nx * tz) * sign, bz = (nx * ty - ny * tx) * sign;
179 norm(bx, by, bz);
180 const auto at = (std::size_t(py) * image.width + px) * 4;
181 float x = image.pixels[at] / 127.5f - 1, y = image.pixels[at + 1] / 127.5f - 1,
182 z = image.pixels[at + 2] / 127.5f - 1;
183 float ox, oy, oz;
184 if (objectToTangent) {
185 ox = x * tx + y * ty + z * tz;
186 oy = x * bx + y * by + z * bz;
187 oz = x * nx + y * ny + z * nz;
188 } else {
189 ox = tx * x + bx * y + nx * z;
190 oy = ty * x + by * y + ny * z;
191 oz = tz * x + bz * y + nz * z;
192 }
193 if (!norm(ox, oy, oz))
195 "zero normal-map direction", {}, {},
196 "asset.import.vegetation-preset.texture"));
197 image.pixels[at] = std::uint8_t(std::clamp(ox * .5f + .5f, 0.f, 1.f) * 255 + .5f);
198 image.pixels[at + 1] = std::uint8_t(std::clamp(oy * .5f + .5f, 0.f, 1.f) * 255 + .5f);
199 image.pixels[at + 2] = std::uint8_t(std::clamp(oz * .5f + .5f, 0.f, 1.f) * 255 + .5f);
200 }
201 return Result<void>::success();
202}
203} // namespace
204
206 const VegetationConversionCandidate& candidate, const std::map<std::string, VegetationPresetImage>& sources,
207 std::uint64_t maximumPixels) {
208 if (!maximumPixels)
210 Diagnostic::error(DiagnosticCode::InvalidArgument, "texture pixel budget must be positive", {}, {},
211 "asset.import.vegetation-preset.texture"));
212 try {
213 std::map<std::string, VegetationPresetImage> outputs;
214 for (const auto& recipe : candidate.texturePacks) {
215 if (recipe.targetProperty.empty())
216 return Result<std::map<std::string, VegetationPresetImage>>::failure(
217 Diagnostic::error(DiagnosticCode::ParseError, "texture recipe has no target property", {}, {},
218 "asset.import.vegetation-preset.texture"));
219 if (recipe.transformSpace == "OBJECT_TO_TANGENT" || recipe.transformSpace == "TANGENT_TO_OBJECT")
221 DiagnosticCode::Unsupported, "texture tangent-space conversion requires a mesh execution context",
222 {}, {}, "asset.import.vegetation-preset.texture"));
223 std::uint32_t width = 0, height = 0;
224 for (const auto& instruction : recipe.channels) {
225 if (instruction.selector.empty() || instruction.selector == "NONE") continue;
226 const auto found = sources.find(instruction.sourceProperty);
227 if (found == sources.end()) continue;
228 if (!valid(found->second, maximumPixels))
229 return Result<std::map<std::string, VegetationPresetImage>>::failure(
230 Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid source texture image", {}, {},
231 "asset.import.vegetation-preset.texture"));
232 width = std::max(width, found->second.width);
233 height = std::max(height, found->second.height);
234 }
235 const auto count = std::uint64_t(width) * height;
236 if (!width || !height) continue;
237 if (count > maximumPixels || count > SIZE_MAX / 4)
239 Diagnostic::error(DiagnosticCode::InvalidArgument, "texture recipe exceeds pixel budget", {}, {},
240 "asset.import.vegetation-preset.texture"));
241 VegetationPresetImage output{width, height, std::vector<std::uint8_t>(std::size_t(count) * 4, 0)};
242 for (std::uint32_t y = 0; y < height; ++y) {
243 for (std::uint32_t x = 0; x < width; ++x) {
244 for (unsigned c = 0; c < 4; ++c) {
245 const auto& instruction = recipe.channels[c];
246 if (instruction.selector.empty() || instruction.selector == "NONE") continue;
247 const auto found = sources.find(instruction.sourceProperty);
248 if (found == sources.end()) continue;
249 auto value = sample(found->second, instruction, (x + 0.5f) / width, (y + 0.5f) / height);
250 if (!value)
252 float component = value.value();
253 if (recipe.transformSpace == "GAMMA_TO_LINEAR")
254 component = gammaToLinear(component);
255 else if (recipe.transformSpace == "LINEAR_TO_GAMMA")
256 component = linearToGamma(component);
257 else if (!recipe.transformSpace.empty() && recipe.transformSpace != "NONE")
260 "unsupported texture transform space: " + recipe.transformSpace, {},
261 {}, "asset.import.vegetation-preset.texture"));
262 output.pixels[(std::size_t(y) * width + x) * 4 + c] =
263 std::uint8_t(std::clamp(component, 0.f, 1.f) * 255.f + 0.5f);
264 }
265 }
266 }
267 outputs[recipe.targetProperty] = std::move(output);
268 }
269 return Result<std::map<std::string, VegetationPresetImage>>::success(std::move(outputs));
270 } catch (const std::bad_alloc&) {
272 Diagnostic::error(DiagnosticCode::Failed, "texture packing allocation failed", {}, {},
273 "asset.import.vegetation-preset.texture"));
274 }
275}
276
278 const VegetationConversionCandidate& candidate, const std::map<std::string, VegetationPresetImage>& sources,
279 const asset::CanonicalMeshData& mesh, std::uint64_t maximumPixels) {
280 try {
281 auto plain = candidate;
282 for (auto& recipe : plain.texturePacks)
283 if (recipe.transformSpace == "OBJECT_TO_TANGENT" || recipe.transformSpace == "TANGENT_TO_OBJECT")
284 recipe.transformSpace = "NONE";
285 auto outputs = executeVegetationTexturePacks(plain, sources, maximumPixels);
286 if (!outputs) return outputs;
287 for (const auto& recipe : candidate.texturePacks) {
288 if (recipe.transformSpace != "OBJECT_TO_TANGENT" && recipe.transformSpace != "TANGENT_TO_OBJECT") continue;
289 auto found = outputs.value().find(recipe.targetProperty);
290 if (found == outputs.value().end()) continue;
291 auto changed = transformNormalMap(found->second, mesh, recipe.transformSpace == "OBJECT_TO_TANGENT");
292 if (!changed) return Result<std::map<std::string, VegetationPresetImage>>::failure(changed.status());
293 }
294 return outputs;
295 } catch (const std::bad_alloc&) {
297 Diagnostic::error(DiagnosticCode::Failed, "normal-space texture conversion allocation failed", {}, {},
298 "asset.import.vegetation-preset.texture"));
299 }
300}
301} // namespace eve::asset_import
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
float uv
int bz
Definition CaveMesh.cpp:114
Vec3 tangent
Definition CaveMesh.cpp:80
int ax
Definition CaveMesh.cpp:113
int ay
Definition CaveMesh.cpp:113
int bx
Definition CaveMesh.cpp:114
int by
Definition CaveMesh.cpp:114
float py
float nx
float nz
float ny
std::map< std::string, Var > values
int tilesX
int tilesY
int triangle
vk::UniqueImage image
float u
Definition Grass.cpp:233
float v
std::int32_t c
std::vector< Colorf > px
std::uint32_t height
std::uint32_t width
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::vector< TriangleRef > triangles
Mesh * mesh
bool found
std::uint32_t count
std::size_t at
double oy
double ox
int covered
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
EVENGINE_API_PLATFORM Result< std::map< std::string, VegetationPresetImage > > executeVegetationTexturePacks(const VegetationConversionCandidate &candidate, const std::map< std::string, VegetationPresetImage > &sources, std::uint64_t maximumPixels=16ull *1024ull *1024ull)
Execute all texture recipes against detached RGBA8 source images.
double sample(const Heightmap &map, double u, double v)
Sample.
Owning CPU mesh snapshot; UV arrays are packed ST pairs indexed by source set number....
Owning unpublished material, mesh and texture candidate mutated transactionally by preset commands.
std::vector< VegetationTexturePackRecipe > texturePacks
Owning linear RGBA8 source or packed texture used by preset conversion.