载入中...
搜索中...
未找到
PrototypeTextures.cpp
浏览该文件的文档.
2
3#include "image/ImageData.h"
6
7#include <algorithm>
8#include <array>
9#include <cmath>
10#include <cstdint>
11#include <limits>
12#include <string>
13
14namespace eve::procgen {
15namespace {
16
17constexpr std::array<PrototypeTextureDescriptor, 13> kPatterns{{
18 {"labeled-grid", "Labeled Grid"},
19 {"quadrant-grid", "Quadrant Grid"},
20 {"fine-grid", "Fine Grid"},
21 {"panel-grid", "Panel Grid"},
22 {"diagonal-grid", "Diagonal Grid"},
23 {"diagonal-fine", "Fine Diagonal Grid"},
24 {"checker-fine", "Fine Checker"},
25 {"checker-coarse", "Coarse Checker"},
26 {"subtle-grid", "Subtle Grid"},
27 {"stairs-guide", "Stairs Guide"},
28 {"doorway-guide", "Doorway Guide"},
29 {"window-guide", "Window Guide"},
30 {"cross-markers", "Cross Markers"},
31}};
32
33struct Color {
34 uint8_t r;
35 uint8_t g;
36 uint8_t b;
37 uint8_t a = 255;
38};
39
40struct Canvas {
41 int width;
42 int height;
43 uint8_t* pixels;
44
45 void blend(int x, int y, Color color, float opacity) {
46 if (x < 0 || y < 0 || x >= width || y >= height) return;
47 opacity = std::clamp(opacity * (float(color.a) / 255.0f), 0.0f, 1.0f);
48 const size_t index = (size_t(y) * size_t(width) + size_t(x)) * 4u;
49 pixels[index + 0] =
50 uint8_t(std::lround(float(pixels[index + 0]) * (1.0f - opacity) + float(color.r) * opacity));
51 pixels[index + 1] =
52 uint8_t(std::lround(float(pixels[index + 1]) * (1.0f - opacity) + float(color.g) * opacity));
53 pixels[index + 2] =
54 uint8_t(std::lround(float(pixels[index + 2]) * (1.0f - opacity) + float(color.b) * opacity));
55 pixels[index + 3] = 255;
56 }
57
58 void fill(Color color) {
59 for (int y = 0; y < height; ++y)
60 for (int x = 0; x < width; ++x) {
61 const size_t index = (size_t(y) * size_t(width) + size_t(x)) * 4u;
62 pixels[index + 0] = color.r;
63 pixels[index + 1] = color.g;
64 pixels[index + 2] = color.b;
65 pixels[index + 3] = 255;
66 }
67 }
68};
69
70bool hasPattern(std::string_view id) {
71 return std::any_of(kPatterns.begin(), kPatterns.end(), [id](const auto& pattern) { return pattern.id == id; });
72}
73
74bool hasPalette(std::string_view palette) {
75 constexpr std::array<std::string_view, 7> kPalettes{{"dark", "light", "purple", "orange", "green", "red",
76 "custom"}};
77 return std::find(kPalettes.begin(), kPalettes.end(), palette) != kPalettes.end();
78}
79
80Color paletteColor(const Params& params, std::string_view palette) {
81 if (palette == "light") return {207, 214, 222};
82 if (palette == "purple") return {140, 100, 190};
83 if (palette == "orange") return {215, 135, 65};
84 if (palette == "green") return {70, 170, 115};
85 if (palette == "red") return {205, 82, 90};
86 if (palette == "custom") {
87 return {uint8_t(std::clamp(params.getInt("backgroundR", 51), 0, 255)),
88 uint8_t(std::clamp(params.getInt("backgroundG", 51), 0, 255)),
89 uint8_t(std::clamp(params.getInt("backgroundB", 53), 0, 255))};
90 }
91 return {82, 92, 106};
92}
93
94Color lineColor(const Params& params, std::string_view palette) {
95 if (palette == "light") return {71, 82, 95};
96 if (palette == "purple") return {220, 201, 239};
97 if (palette == "orange") return {247, 215, 174};
98 if (palette == "green") return {190, 231, 207};
99 if (palette == "red") return {241, 198, 201};
100 if (palette == "dark") return {166, 180, 197};
101 return {uint8_t(std::clamp(params.getInt("lineR", 255), 0, 255)),
102 uint8_t(std::clamp(params.getInt("lineG", 255), 0, 255)),
103 uint8_t(std::clamp(params.getInt("lineB", 255), 0, 255))};
104}
105
106void line(Canvas& canvas, int x0, int y0, int x1, int y1, Color color, float alpha, int width) {
107 const int dx = std::abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
108 const int dy = -std::abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
109 int error = dx + dy;
110 for (;;) {
111 const int radius = std::max(0, width - 1) / 2;
112 for (int oy = -radius; oy <= radius; ++oy)
113 for (int ox = -radius; ox <= radius; ++ox) canvas.blend(x0 + ox, y0 + oy, color, alpha);
114 if (x0 == x1 && y0 == y1) break;
115 const int twice = 2 * error;
116 if (twice >= dy) {
117 error += dy;
118 x0 += sx;
119 }
120 if (twice <= dx) {
121 error += dx;
122 y0 += sy;
123 }
124 }
125}
126
127void rectangle(Canvas& canvas, int x0, int y0, int x1, int y1, Color color, float alpha, int width) {
128 line(canvas, x0, y0, x1, y0, color, alpha, width);
129 line(canvas, x1, y0, x1, y1, color, alpha, width);
130 line(canvas, x1, y1, x0, y1, color, alpha, width);
131 line(canvas, x0, y1, x0, y0, color, alpha, width);
132}
133
134void grid(Canvas& canvas, int cell, int lineWidth, Color color, float minorAlpha, float majorAlpha, int majorEvery,
135 bool diagonals) {
136 cell = std::max(2, cell);
137 for (int x = 0; x < canvas.width; x += cell) {
138 const bool major = (x / cell) % std::max(1, majorEvery) == 0;
139 line(canvas, x, 0, x, canvas.height - 1, color, major ? majorAlpha : minorAlpha, lineWidth);
140 }
141 for (int y = 0; y < canvas.height; y += cell) {
142 const bool major = (y / cell) % std::max(1, majorEvery) == 0;
143 line(canvas, 0, y, canvas.width - 1, y, color, major ? majorAlpha : minorAlpha, lineWidth);
144 }
145 if (diagonals) {
146 for (int y = -canvas.height; y < canvas.height * 2; y += cell)
147 line(canvas, 0, y, canvas.width - 1, y + canvas.width - 1, color, minorAlpha, lineWidth);
148 for (int y = 0; y < canvas.height * 3; y += cell)
149 line(canvas, 0, y, canvas.width - 1, y - canvas.width + 1, color, minorAlpha, lineWidth);
150 }
151}
152
153void checker(Canvas& canvas, int cell, Color lineColor, float alpha) {
154 for (int y = 0; y < canvas.height; ++y)
155 for (int x = 0; x < canvas.width; ++x) {
156 if (((x / cell) + (y / cell)) % 2 == 0) canvas.blend(x, y, lineColor, alpha);
157 }
158}
159
160void cross(Canvas& canvas, int cx, int cy, int radius, Color color, float alpha, int width) {
161 line(canvas, cx - radius, cy, cx + radius, cy, color, alpha, width);
162 line(canvas, cx, cy - radius, cx, cy + radius, color, alpha, width);
163}
164
165void paintPattern(std::string_view id, Canvas& canvas, const Params& params, Color foreground) {
166 const int cell = std::clamp(params.getInt("cellSize", std::max(8, canvas.width / 8)), 2, 4096);
167 const int lineWidth = std::clamp(params.getInt("lineWidth", std::max(1, canvas.width / 512)), 1, 64);
168 const float minorAlpha = std::clamp(params.getFloat("minorAlpha", 0.10f), 0.0f, 1.0f);
169 const float majorAlpha = std::clamp(params.getFloat("majorAlpha", 0.45f), 0.0f, 1.0f);
170 if (id == "labeled-grid" || id == "quadrant-grid") {
171 grid(canvas, std::max(2, cell / 2), lineWidth, foreground, minorAlpha, majorAlpha, 4, false);
172 if (id == "labeled-grid") {
173 const int pad = std::max(4, cell / 4);
174 for (int i = 0; i < 4; ++i)
175 line(canvas, pad, pad + i * lineWidth * 3, pad + cell / (i + 2), pad + i * lineWidth * 3, foreground,
176 0.95f, lineWidth);
177 }
178 } else if (id == "fine-grid") {
179 grid(canvas, std::max(2, cell / 2), lineWidth, foreground, minorAlpha, majorAlpha * 0.5f, 4, false);
180 } else if (id == "panel-grid") {
181 grid(canvas, cell * 2, lineWidth, foreground, minorAlpha, majorAlpha, 1, false);
182 } else if (id == "diagonal-grid" || id == "diagonal-fine") {
183 const int scale = id == "diagonal-fine" ? std::max(2, cell / 2) : cell;
184 grid(canvas, scale, lineWidth, foreground, minorAlpha, majorAlpha, 4, true);
185 } else if (id == "checker-fine" || id == "checker-coarse") {
186 checker(canvas, id == "checker-fine" ? std::max(2, cell / 2) : cell * 2, foreground, minorAlpha);
187 } else if (id == "subtle-grid") {
188 grid(canvas, cell * 2, lineWidth, foreground, minorAlpha * 0.55f, majorAlpha * 0.25f, 4, false);
189 } else if (id == "stairs-guide") {
190 grid(canvas, cell, lineWidth, foreground, minorAlpha, majorAlpha * 0.4f, 4, false);
191 const int steps = std::clamp(params.getInt("guideSteps", 6), 2, 32);
192 int x = canvas.width / 4, y = canvas.height * 3 / 4;
193 const int dx = canvas.width / 2 / steps, dy = canvas.height / 2 / steps;
194 for (int i = 0; i < steps; ++i) {
195 line(canvas, x, y, x + dx, y, foreground, 0.95f, lineWidth * 2);
196 x += dx;
197 line(canvas, x, y, x, y - dy, foreground, 0.95f, lineWidth * 2);
198 y -= dy;
199 }
200 } else if (id == "doorway-guide") {
201 grid(canvas, cell, lineWidth, foreground, minorAlpha, majorAlpha * 0.35f, 4, false);
202 rectangle(canvas, canvas.width / 4, canvas.height / 4, canvas.width * 3 / 4, canvas.height, foreground, 0.95f,
203 lineWidth * 2);
204 rectangle(canvas, canvas.width / 4 + cell / 3, canvas.height / 4 + cell / 3, canvas.width * 3 / 4 - cell / 3,
205 canvas.height, foreground, 0.55f, lineWidth);
206 } else if (id == "window-guide") {
207 grid(canvas, cell, lineWidth, foreground, minorAlpha, majorAlpha * 0.35f, 4, false);
208 rectangle(canvas, canvas.width / 4, canvas.height / 4, canvas.width * 3 / 4, canvas.height * 3 / 4, foreground,
209 0.95f, lineWidth * 2);
210 rectangle(canvas, canvas.width / 4 + cell / 3, canvas.height / 4 + cell / 3, canvas.width * 3 / 4 - cell / 3,
211 canvas.height * 3 / 4 - cell / 3, foreground, 0.55f, lineWidth);
212 } else if (id == "cross-markers") {
213 const int radius = std::max(2, cell / 5);
214 for (int y = cell / 2; y < canvas.height; y += cell * 2)
215 for (int x = cell / 2; x < canvas.width; x += cell * 2)
216 cross(canvas, x, y, radius, foreground, majorAlpha, lineWidth);
217 }
218}
219
220RecipeDescriptor descriptorFor(const PrototypeTextureDescriptor& pattern) {
221 RecipeDescriptor schema =
222 RecipeDescriptor::grid("tex.prototype." + std::string(pattern.id), std::string(pattern.displayName),
223 "Prototype Texture", 8, 8, 4096, 4096);
224 schema.params.push_back(ParamDescriptor::choice("palette", "Palette", "dark",
225 {"dark", "light", "purple", "orange", "green", "red", "custom"}));
226 schema.params.push_back(ParamDescriptor::integer("cellSize", "Cell Size", 128, 2, 4096));
227 schema.params.push_back(ParamDescriptor::integer("lineWidth", "Line Width", 2, 1, 64));
228 schema.params.push_back(ParamDescriptor::floating("minorAlpha", "Minor Line Opacity", 0.10f, 0.0f, 1.0f, 0.01f));
229 schema.params.push_back(ParamDescriptor::floating("majorAlpha", "Major Line Opacity", 0.45f, 0.0f, 1.0f, 0.01f));
230 schema.params.push_back(ParamDescriptor::integer("guideSteps", "Guide Steps", 6, 2, 32));
231 schema.params.push_back(ParamDescriptor::integer("backgroundR", "Custom Background R", 51, 0, 255));
232 schema.params.push_back(ParamDescriptor::integer("backgroundG", "Custom Background G", 51, 0, 255));
233 schema.params.push_back(ParamDescriptor::integer("backgroundB", "Custom Background B", 53, 0, 255));
234 schema.params.push_back(ParamDescriptor::integer("lineR", "Custom Line R", 255, 0, 255));
235 schema.params.push_back(ParamDescriptor::integer("lineG", "Custom Line G", 255, 0, 255));
236 schema.params.push_back(ParamDescriptor::integer("lineB", "Custom Line B", 255, 0, 255));
237 return schema;
238}
239
240} // namespace
241
242std::span<const PrototypeTextureDescriptor> prototypeTextureDescriptors() noexcept { return kPatterns; }
243
245 const Params& params) {
246 if (!hasPattern(patternId)) {
248 eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "unknown prototype texture pattern", "patternId"));
249 }
250 const int width = params.getWidth();
251 const int height = params.getHeight();
252 if (width < 8 || height < 8 || width > 4096 || height > 4096) {
255 "prototype texture dimensions must be between 8 and 4096", "params.size"));
256 }
257 const std::string palette = params.getString("palette", "dark");
258 if (!hasPalette(palette)) {
260 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "unknown prototype texture palette",
261 "params.palette"));
262 }
263 const float invalidFloat = std::numeric_limits<float>::quiet_NaN();
264 const float minorAlpha =
265 params.has("minorAlpha") ? params.getFloat("minorAlpha", invalidFloat) : 0.10f;
266 const float majorAlpha =
267 params.has("majorAlpha") ? params.getFloat("majorAlpha", invalidFloat) : 0.45f;
268 if (!std::isfinite(minorAlpha) || !std::isfinite(majorAlpha)) {
271 "prototype texture opacities must be finite", "params.opacity"));
272 }
273 auto image = std::make_unique<image::ImageData>(width, height, "RGBA8");
274 auto* pixels = static_cast<uint8_t*>(image->getData());
275 if (!pixels) {
277 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "prototype texture allocation failed", "image"));
278 }
279 Canvas canvas{width, height, pixels};
280 canvas.fill(paletteColor(params, palette));
281 paintPattern(patternId, canvas, params, lineColor(params, palette));
282 return eve::Result<std::unique_ptr<image::ImageData>>::success(std::move(image),
284}
285
287 for (const auto& pattern : kPatterns) {
288 const std::string id(pattern.id);
289 registry.registerRecipe(descriptorFor(pattern), [id](const Params& params, std::string& error) {
290 auto generated = generatePrototypeTexture(id, params);
291 if (!generated.ok()) {
292 error = generated.status().describe();
293 return std::unique_ptr<image::ImageData>{};
294 }
295 return std::move(generated).takeValue();
296 });
297 }
298}
299
300} // namespace eve::procgen
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
std::string pattern
std::string palette
tensor::Graph g
Definition GpuGraph.cpp:7
vk::UniqueImage image
double r
float blend
std::uint32_t height
std::uint32_t width
std::array< float, 3 > scale
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string error
Definition Package.cpp:60
float radius
std::string id
Definition PlayHost.cpp:108
int steps
uint8_t * pixels
float dy
float dx
Cell cell
float opacity
uint32_t index
double oy
double ox
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 Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
Owning, typed generation parameters.
Definition Params.h:27
EVENGINE_API_DOMAINS public API.
void registerRecipe(const std::string &id, TextureRecipeFn fn)
Register a recipe without metadata.
std::vector< ParamSpec > params
double cross(const Vec2 &a, const Vec2 &b)
Cross.
Definition UrbanTypes.h:36
void registerPrototypeTextureRecipes(TextureRecipeRegistry &registry)
Register all patterns as tex.prototype.* texture recipes.
std::span< const PrototypeTextureDescriptor > prototypeTextureDescriptors() noexcept
Return the stable catalogue of 13 procedural prototype patterns.
eve::Result< std::unique_ptr< image::ImageData > > generatePrototypeTexture(std::string_view patternId, const Params &params)
Generate a parameterized RGBA8 prototype texture without loading an image asset. @ownership On succes...
WidgetDesc grid(int columns, std::vector< WidgetDesc > children, std::string id)
Fixed-column grid; children are placed in source order.
Definition Widget.cpp:687
glm::vec4 Color
Render-neutral RGBA color shared by graphics-facing modules.
Definition RenderTypes.h:8
static ParamDescriptor choice(std::string key, std::string label, std::string defaultValue, std::vector< std::string > choices)
Construct a finite-choice string descriptor.
static ParamDescriptor floating(std::string key, std::string label, float defaultValue, float minimum, float maximum, float step)
Construct a bounded floating-point descriptor.
static ParamDescriptor integer(std::string key, std::string label, int defaultValue, int minimum, int maximum, int step=1)
Construct a bounded integer descriptor.
static RecipeDescriptor grid(std::string id, std::string displayName, std::string category, int minimumWidth, int minimumHeight, int maximumWidth=4096, int maximumHeight=4096)
Create a schema with common seed, width and height parameters.
uint32_t pad[2]
glm::vec4 color