载入中...
搜索中...
未找到
VegetationPresetConversion.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <new>
5
6namespace eve::asset_import {
7namespace {
8Result<void> applyOutputPlan(VegetationConversionResult& result) {
9 const auto value = [&](const char* domain) -> const std::string* {
10 const auto found = result.candidate.outputDirectives.find(domain);
11 return found == result.candidate.outputDirectives.end() || found->second.size() != 1 ? nullptr
12 : &found->second.front();
13 };
14 const auto invalid = [](std::string message) {
16 "asset.import.vegetation-preset.conversion"));
17 };
18 if (result.candidate.outputDirectives.contains("OutputMeshes")) {
19 const auto* mode = value("OutputMeshes");
20 if (!mode) return invalid("OutputMeshes requires one mode");
21 if (*mode == "OFF" || *mode == "NONE")
22 result.meshOutput = VegetationMeshOutputMode::Off;
23 else if (*mode == "DEFAULT")
24 result.meshOutput = VegetationMeshOutputMode::Default;
25 else if (*mode == "CUSTOM")
26 result.meshOutput = VegetationMeshOutputMode::Custom;
27 else
28 return invalid("unsupported OutputMeshes mode");
29 }
30 if (result.candidate.outputDirectives.contains("OutputMaterials")) {
31 const auto* mode = value("OutputMaterials");
32 if (!mode) return invalid("OutputMaterials requires one mode");
33 if (*mode == "OFF" || *mode == "NONE")
34 result.materialOutput = VegetationMaterialOutputMode::Off;
35 else if (*mode == "DEFAULT")
36 result.materialOutput = VegetationMaterialOutputMode::Default;
37 else
38 return invalid("unsupported OutputMaterials mode");
39 }
40 if (result.candidate.outputDirectives.contains("OutputTextures")) {
41 const auto* mode = value("OutputTextures");
42 if (!mode) return invalid("OutputTextures requires one mode");
43 if (*mode == "SAVE_TEXTURES_AS_PNG")
44 result.textureOutput = VegetationTextureOutputEncoding::Png;
45 else if (*mode == "SAVE_TEXTURES_AS_TGA")
46 result.textureOutput = VegetationTextureOutputEncoding::Tga;
47 else if (*mode == "SAVE_TEXTURES_AS_EXR")
48 result.textureOutput = VegetationTextureOutputEncoding::Exr;
49 else if (*mode == "SAVE_TEXTURES_AS_ASSET")
51 else
52 return invalid("unsupported OutputTextures mode");
53 }
54 if (result.candidate.outputDirectives.contains("OutputTransforms")) {
55 const auto* mode = value("OutputTransforms");
56 if (!mode) return invalid("OutputTransforms requires one mode");
57 if (*mode == "KEEP_ORIGINAL_TRANSFORMS" || *mode == "USE_ORIGINAL_TRANSFORMS")
58 result.transformOutput = VegetationTransformOutputMode::KeepOriginal;
59 else if (*mode == "TRANSFORM_TO_WORLD_SPACE")
61 else
62 return invalid("unsupported OutputTransforms mode");
63 }
64 return Result<void>::success();
65}
66
67Result<void> transformToWorld(asset::CanonicalMeshData& mesh, const std::array<float, 16>& point,
68 const std::array<float, 9>& direction) {
69 for (const float value : point)
70 if (!std::isfinite(value))
72 "source-to-world point matrix contains nonfinite values", {},
73 {}, "asset.import.vegetation-preset.conversion"));
74 for (const float value : direction)
75 if (!std::isfinite(value))
77 "source-to-world direction matrix contains nonfinite values",
78 {}, {}, "asset.import.vegetation-preset.conversion"));
79 const float determinant = direction[0] * (direction[4] * direction[8] - direction[7] * direction[5]) -
80 direction[3] * (direction[1] * direction[8] - direction[7] * direction[2]) +
81 direction[6] * (direction[1] * direction[5] - direction[4] * direction[2]);
82 const auto dot = [&](unsigned a, unsigned b) {
83 return direction[a] * direction[b] + direction[a + 1] * direction[b + 1] + direction[a + 2] * direction[b + 2];
84 };
85 if (std::abs(determinant - 1.f) > 1e-4f || std::abs(dot(0, 0) - 1.f) > 1e-4f || std::abs(dot(3, 3) - 1.f) > 1e-4f ||
86 std::abs(dot(6, 6) - 1.f) > 1e-4f || std::abs(dot(0, 3)) > 1e-4f || std::abs(dot(0, 6)) > 1e-4f ||
87 std::abs(dot(3, 6)) > 1e-4f)
89 "source-to-world direction matrix is not a proper rotation", {},
90 {}, "asset.import.vegetation-preset.conversion"));
91 const auto transformDirection = [&](float& x, float& y, float& z) {
92 const float ox = direction[0] * x + direction[3] * y + direction[6] * z;
93 const float oy = direction[1] * x + direction[4] * y + direction[7] * z;
94 const float oz = direction[2] * x + direction[5] * y + direction[8] * z;
95 const float length = std::sqrt(ox * ox + oy * oy + oz * oz);
96 if (length < 1e-8f) return false;
97 x = ox / length;
98 y = oy / length;
99 z = oz / length;
100 return true;
101 };
102 for (std::size_t i = 0; i < mesh.positions.size(); i += 3) {
103 const float x = mesh.positions[i], y = mesh.positions[i + 1], z = mesh.positions[i + 2];
104 mesh.positions[i] = point[0] * x + point[4] * y + point[8] * z + point[12];
105 mesh.positions[i + 1] = point[1] * x + point[5] * y + point[9] * z + point[13];
106 mesh.positions[i + 2] = point[2] * x + point[6] * y + point[10] * z + point[14];
107 if (!transformDirection(mesh.normals[i], mesh.normals[i + 1], mesh.normals[i + 2]))
109 "world transform produced a zero mesh normal", {}, {},
110 "asset.import.vegetation-preset.conversion"));
111 }
112 if (auto tangent = mesh.attributes.find("TANGENT"); tangent != mesh.attributes.end()) {
113 if (tangent->second.components != 4 || tangent->second.values.size() / 4 != mesh.positions.size() / 3)
115 "world transform tangent stream is malformed", {}, {},
116 "asset.import.vegetation-preset.conversion"));
117 for (std::size_t i = 0; i < tangent->second.values.size(); i += 4)
118 if (!transformDirection(tangent->second.values[i], tangent->second.values[i + 1],
119 tangent->second.values[i + 2]))
121 "world transform produced a zero mesh tangent", {}, {},
122 "asset.import.vegetation-preset.conversion"));
123 }
124 return Result<void>::success();
125}
126} // namespace
127
129 VegetationConversionCandidate source, const std::vector<VegetationPresetCommand>& commands,
130 std::optional<asset::CanonicalMeshData> mesh, const std::map<std::string, VegetationPresetImage>& textures,
131 float variationSeed, const std::optional<std::array<float, 16>>& sourceToWorld,
132 const std::optional<std::array<float, 9>>& sourceDirectionToWorld) {
133 try {
134 auto applied = applyVegetationPreset(std::move(source), commands);
135 if (!applied) return Result<VegetationConversionResult>::failure(applied.status());
137 result.candidate = std::move(applied).takeValue();
138 auto outputPlan = applyOutputPlan(result);
139 if (!outputPlan) return Result<VegetationConversionResult>::failure(outputPlan.status());
142 if (sourceToWorld.has_value() != sourceDirectionToWorld.has_value())
144 DiagnosticCode::InvalidArgument, "world conversion requires both point and direction matrices", {},
145 {}, "asset.import.vegetation-preset.conversion"));
146 if (sourceToWorld) {
147 auto transformed = transformToWorld(*mesh, *sourceToWorld, *sourceDirectionToWorld);
148 if (!transformed) return Result<VegetationConversionResult>::failure(transformed.status());
149 }
150 }
152 auto converted = executeVegetationMeshRules(result.candidate, std::move(*mesh), textures, variationSeed);
153 if (!converted) return Result<VegetationConversionResult>::failure(converted.status());
154 result.mesh = std::move(converted).takeValue();
155 const auto count = result.mesh->positions.size() / 3;
156 float radius = 0, minimumY = result.mesh->positions[1], maximumY = minimumY;
157 for (std::size_t i = 0; i < count; ++i) {
158 radius = std::max(radius, std::max(std::abs(result.mesh->positions[i * 3]),
159 std::abs(result.mesh->positions[i * 3 + 2])));
160 minimumY = std::min(minimumY, result.mesh->positions[i * 3 + 1]);
161 maximumY = std::max(maximumY, result.mesh->positions[i * 3 + 1]);
162 }
163 if (auto bounds = result.candidate.meshRules.find("SetBounds");
164 bounds != result.candidate.meshRules.end()) {
165 if (bounds->second.size() != 2 || bounds->second[0] != "GET_BOUNDS_PROCEDURAL")
167 Diagnostic::error(DiagnosticCode::Unsupported, "unsupported vegetation bounds rule", {}, {},
168 "asset.import.vegetation-preset.conversion"));
169 const auto& option = bounds->second[1];
170 const float expand = option == "0" ? 1.2f
171 : option == "1" ? 1.4f
172 : option == "2" ? 1.6f
173 : option == "3" ? 2.f
174 : 0.f;
175 if (expand == 0)
177 Diagnostic::error(DiagnosticCode::ParseError, "invalid vegetation bounds option", {}, {},
178 "asset.import.vegetation-preset.conversion"));
179 result.meshBoundsMinimum = std::array{-radius * expand, minimumY, -radius * expand};
180 result.meshBoundsMaximum = std::array{radius * expand, maximumY * 1.25f, radius * expand};
181 }
182 if (auto readable = result.candidate.meshRules.find("SetReadWrite");
183 readable != result.candidate.meshRules.end()) {
184 if (readable->second.size() != 1 || (readable->second[0] != "MARK_MESHES_AS_READABLE" &&
185 readable->second[0] != "MARK_MESHES_AS_NON_READABLE"))
187 Diagnostic::error(DiagnosticCode::Unsupported, "unsupported vegetation mesh readability rule",
188 {}, {}, "asset.import.vegetation-preset.conversion"));
189 result.meshCpuReadable = readable->second[0] == "MARK_MESHES_AS_READABLE";
190 }
191 } else if (!mesh && !result.candidate.meshRules.empty() && result.meshOutput != VegetationMeshOutputMode::Off) {
193 Diagnostic::error(DiagnosticCode::NotFound, "vegetation preset requires a source mesh", {}, {},
194 "asset.import.vegetation-preset.conversion"));
195 }
197 auto packed = result.mesh ? executeVegetationTexturePacks(result.candidate, textures, *result.mesh)
198 : executeVegetationTexturePacks(result.candidate, textures);
199 if (!packed) return Result<VegetationConversionResult>::failure(packed.status());
200 result.textures = std::move(packed).takeValue();
201 }
202 return Result<VegetationConversionResult>::success(std::move(result));
203 } catch (const std::bad_alloc&) {
205 Diagnostic::error(DiagnosticCode::Failed, "vegetation conversion allocation failed", {}, {},
206 "asset.import.vegetation-preset.conversion"));
207 }
208}
209
211 if (request.sourcePath.empty() || request.shaderName.empty() || request.materialName.empty() ||
212 request.rootPreset.empty())
215 "Unity vegetation conversion requires source, shader, material, and root preset names",
216 request.sourcePath, {}, "asset.import.vegetation-preset.conversion"));
217 try {
218 auto source = decodeUnityVegetationConversionCandidate(request.materialYaml, request.sourcePath,
219 request.maximumMaterialBytes);
221 auto context = makeVegetationPresetContext(source.value(), request.shaderName, request.materialName,
222 request.shaderPipeline, request.outputOptions);
223 auto commands = evaluateVegetationPreset(request.presetDefinitions, request.rootPreset, context);
225 return executeVegetationConversion(std::move(source).takeValue(), commands.value(), request.mesh,
226 request.textures, request.variationSeed, request.sourceToWorld,
227 request.sourceDirectionToWorld);
228 } catch (const std::bad_alloc&) {
230 Diagnostic::error(DiagnosticCode::Failed, "Unity vegetation transaction allocation failed",
231 request.sourcePath, {}, "asset.import.vegetation-preset.conversion"));
232 }
233}
234} // namespace eve::asset_import
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
Vec3 tangent
Definition CaveMesh.cpp:80
float length
Definition CaveMesh.cpp:94
std::string message
const GltfImportRequest & request
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string error
Definition Package.cpp:60
float radius
Mesh * mesh
RoadLaneDirection direction
bool found
std::uint32_t count
const UnitySourceAsset & source
double oy
double ox
std::vector< VegetationPresetCommand > commands
const VegetationPresetContext & context
glm::vec3 point
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< VegetationConversionCandidate > decodeUnityVegetationConversionCandidate(std::span< const std::uint8_t > yaml, std::string sourcePath, std::uint64_t maximumBytes=16ull *1024ull *1024ull)
Decode Unity text Material saved properties into an owning vegetation conversion baseline.
EVENGINE_API_PLATFORM Result< VegetationConversionCandidate > applyVegetationPreset(VegetationConversionCandidate source, const std::vector< VegetationPresetCommand > &commands)
Type-check and atomically apply an evaluated TVE command sequence.
EVENGINE_API_PLATFORM Result< VegetationConversionResult > executeUnityVegetationConversion(const UnityVegetationConversionRequest &request)
Decode a Unity Material, evaluate one TVE preset graph, and execute every output atomically.
EVENGINE_API_PLATFORM Result< asset::CanonicalMeshData > executeVegetationMeshRules(const VegetationConversionCandidate &candidate, asset::CanonicalMeshData source, float variationSeed=1.f)
Execute preset mask and coordinate rules into canonical TVE authoring streams.
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.
EVENGINE_API_PLATFORM Result< VegetationConversionResult > executeVegetationConversion(VegetationConversionCandidate source, const std::vector< VegetationPresetCommand > &commands, std::optional< asset::CanonicalMeshData > mesh, const std::map< std::string, VegetationPresetImage > &textures, float variationSeed=1.f, const std::optional< std::array< float, 16 > > &sourceToWorld={}, const std::optional< std::array< float, 9 > > &sourceDirectionToWorld={})
Apply commands and execute all available mesh and texture outputs as one transaction.
Result< std::vector< VegetationPresetCommand > > evaluateVegetationPreset(const std::map< std::string, Value > &definitions, std::string_view root, const VegetationPresetContext &context)
Decode, resolve and evaluate a library of canonical TVE preset definitions.
EVENGINE_API_PLATFORM VegetationPresetContext makeVegetationPresetContext(const VegetationConversionCandidate &candidate, std::string shaderName, std::string materialName, std::string shaderPipeline, std::set< std::string > outputOptions={})
Project one conversion baseline into immutable preset predicate facts.
eve::Diagnostic Diagnostic
eve::Result< T > Result
Result< int > invalid(std::string message)
Invalid.
double dot(const Vec2 &a, const Vec2 &b)
Dot.
Definition UrbanTypes.h:38
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
Owning inputs for one complete Unity Material and TVE preset conversion transaction.
Owning unpublished material, mesh and texture candidate mutated transactionally by preset commands.
std::map< std::string, std::vector< std::string > > meshRules
Complete detached output of one vegetation preset conversion transaction.
VegetationTransformOutputMode transformOutput
std::map< std::string, VegetationPresetImage > textures
std::optional< std::array< float, 3 > > meshBoundsMinimum
std::optional< asset::CanonicalMeshData > mesh
std::optional< std::array< float, 3 > > meshBoundsMaximum
glm::vec4 bounds