13float clamp01(
float value) {
return std::clamp(
value, 0.f, 1.f); }
15std::vector<std::size_t> elementIds(
const asset::CanonicalMeshData&
mesh, std::size_t
count) {
17 for (std::size_t i = 0; i <
count; ++i)
parent[i] = i;
25 for (std::size_t i = 0; i + 2 <
mesh.indices.size(); i += 3) {
26 const auto a =
mesh.indices[i],
b =
mesh.indices[i + 1],
c =
mesh.indices[i + 2];
32 std::map<std::size_t, std::size_t>
ids;
33 std::vector<std::size_t> result(
count);
34 for (std::size_t i = 0; i <
count; ++i) {
35 const auto r =
root(i);
37 result[i] =
found->second;
42std::vector<float> four(
const asset::CanonicalMeshData&
mesh,
const char*
name, std::size_t
count,
43 std::array<float, 4> fallback) {
46 return found->second.values;
47 std::vector<float> result(
count * 4);
48 for (std::size_t i = 0; i <
count; ++i)
49 std::copy(fallback.begin(), fallback.end(), result.begin() + std::ptrdiff_t(i * 4));
58 {}, {},
"asset.import.vegetation-preset.mesh"));
62Result<std::vector<float>>
mask(
const asset::CanonicalMeshData&
mesh,
const std::vector<std::string>& rule,
63 const std::map<std::string, VegetationPresetImage>& textures, std::size_t
count,
65 std::vector<float> result(
count, 1.f);
66 if (rule.empty() || rule[0] ==
"NONE")
return Result<std::vector<float>>::success(std::move(result));
71 if (!option)
return Result<std::vector<float>>::failure(option.status());
72 if (rule[0] ==
"GET_MASK_FROM_CHANNEL") {
74 const int channel = option.value();
75 for (std::size_t i = 0; i <
count; ++i) {
77 result[i] =
color[i * 4 + channel];
79 const int uv = (channel - 4) / 4, component = (channel - 4) % 4;
80 auto values = four(
mesh, (
"_UNITY_UV" + std::to_string(
uv)).c_str(),
count, {0, 0, 0, 0});
81 result[i] =
values[i * 4 + component];
84 }
else if (rule[0] ==
"GET_MASK_PROCEDURAL") {
85 const int mode = option.value();
86 const auto elements = (mode == 2 || mode == 3) ? elementIds(
mesh,
count) : std::vector<std::size_t>{};
87 const auto elementCount =
89 for (std::size_t i = 0; i <
count; ++i) {
90 const float x =
mesh.positions[i * 3],
y =
mesh.positions[i * 3 + 1],
z =
mesh.positions[i * 3 + 2];
93 case 0: result[i] = 0;
break;
94 case 1: result[i] = 1;
break;
96 std::uint32_t
hash = std::uint32_t(
elements[i]) ^ std::bit_cast<std::uint32_t>(
seed);
102 result[i] = float(
hash & 0x00ffffffu) / float(0x01000000u);
106 result[i] = std::fmod(
float(
elements[i]) / std::max<std::size_t>(elementCount, 1) *
seed, 1.f);
108 case 4: result[i] = clamp01(
h);
break;
109 case 5: result[i] = clamp01(std::sqrt(
x *
x +
y *
y +
z *
z) / std::max(
radius, 1e-6f));
break;
110 case 6: result[i] = clamp01((radial - .1f) / .9f);
break;
112 const float cap = clamp01((
h - .8f) / .2f), base = clamp01(
h / .1f);
113 result[i] = clamp01(clamp01((radial - .1f) / .9f) + cap) * base;
116 case 8: result[i] = 1.f - clamp01(
h);
break;
117 case 9: result[i] = clamp01(-
mesh.normals[i * 3 + 1] * .5f + .5f);
break;
118 case 10: result[i] = clamp01(
mesh.normals[i * 3 + 1] * .5f + .5f);
break;
119 case 11: result[i] = clamp01((clamp01(
h) - .2f) / .8f);
break;
120 case 12: result[i] = clamp01((clamp01(
h) - .4f) / .6f);
break;
121 case 13: result[i] = clamp01((clamp01(
h) - .6f) / .4f);
break;
122 case 14: result[i] = 1.f - std::pow(1.f - clamp01(
h), 4.f);
break;
124 result[i] = std::pow(clamp01(std::sqrt(
x *
x +
y *
y +
z *
z) / std::max(
radius, 1e-6f)), 2.f);
126 case 16: result[i] = std::pow(clamp01((radial - .1f) / .9f), 2.f);
break;
128 const float cap = clamp01((
h - .8f) / .2f), base = clamp01(
h / .1f);
129 const float capsule = clamp01(clamp01((radial - .1f) / .9f) + cap) * base;
130 result[i] = capsule * capsule;
133 case 18: result[i] =
x / std::max(
radius, 1e-6f);
break;
134 case 19: result[i] =
h;
break;
135 case 20: result[i] =
z / std::max(
radius, 1e-6f);
break;
137 return Result<std::vector<float>>::failure(
139 "asset.import.vegetation-preset.mesh"));
142 }
else if (rule[0] ==
"GET_MASK_FROM_TEXTURE") {
145 "texture mask lacks property", {}, {},
146 "asset.import.vegetation-preset.mesh"));
147 const auto image = textures.find(rule[2]);
148 if (image == textures.end())
return Result<std::vector<float>>::success(std::move(result));
149 const auto pixels = std::uint64_t(
image->second.width) *
image->second.height;
150 if (!
image->second.width || !
image->second.height ||
pixels > 16ull * 1024ull * 1024ull ||
151 image->second.pixels.size() != std::size_t(
pixels) * 4 || option.value() < 0 || option.value() > 3)
153 "invalid texture mask input", {}, {},
154 "asset.import.vegetation-preset.mesh"));
156 for (std::size_t i = 3; i + 1 < rule.size(); ++i)
157 if (rule[i] ==
"GET_COORD") {
158 auto parsed =
integer(rule[i + 1]);
159 if (!parsed)
return Result<std::vector<float>>::failure(parsed.status());
160 coord = parsed.value();
162 auto uv = four(
mesh, (
"_UNITY_UV" + std::to_string(coord)).c_str(),
count, {0, 0, 0, 0});
163 const auto texel = [&](std::uint32_t
x, std::uint32_t
y) {
164 return image->second.pixels[(std::size_t(
y) *
image->second.width +
x) * 4 + option.value()] / 255.f;
166 for (std::size_t i = 0; i <
count; ++i) {
167 const float px = std::clamp(
uv[i * 4] *
image->second.width - .5f, 0.f,
float(
image->second.width - 1));
169 std::clamp(
uv[i * 4 + 1] *
image->second.height - .5f, 0.f,
float(
image->second.height - 1));
170 const auto x0 = std::uint32_t(
px), y0 = std::uint32_t(
py), x1 = std::min(x0 + 1,
image->second.width - 1),
171 y1 = std::min(y0 + 1,
image->second.height - 1);
172 result[i] = std::lerp(std::lerp(texel(x0, y0), texel(x1, y0),
px - x0),
173 std::lerp(texel(x0, y1), texel(x1, y1),
px - x0),
py - y0);
175 }
else if (rule[0] ==
"GET_MASK_3RD_PARTY") {
176 const auto uv2 = four(
mesh,
"_UNITY_UV2",
count, {0, 0, 0, 0});
177 const auto uv1 = four(
mesh,
"_UNITY_UV1",
count, {0, 0, 0, 0});
178 const auto uv3 = four(
mesh,
"_UNITY_UV3",
count, {0, 0, 0, 0});
179 for (std::size_t i = 0; i <
count; ++i) {
180 const float x =
mesh.positions[i * 3],
y =
mesh.positions[i * 3 + 1],
z =
mesh.positions[i * 3 + 2];
181 if (option.value() == 0 || option.value() == 1) {
182 const float packed = uv2[i * 4],
scale = uv2[i * 4 + 1];
183 const float pivotX = std::fmod(packed, 1.f) * 2.f - 1.f;
184 const float pivotZ = std::fmod(32768.f * packed, 1.f) * 2.f - 1.f;
185 const float pivotY = std::sqrt(1.f - clamp01(pivotX * pivotX + pivotZ * pivotZ));
186 if (option.value() == 0) {
187 result[i] = std::sqrt(std::pow(
x - pivotX *
scale, 2.f) + std::pow(
y - pivotY *
scale, 2.f) +
188 std::pow(
z - pivotZ *
scale, 2.f)) /
192 result[i] = packed < .01f ? 0.f
193 : repeat(pivotX *
scale * 33.3f) + repeat(pivotY *
scale * 33.3f) +
194 repeat(pivotZ *
scale * 33.3f);
196 }
else if (option.value() == 2) {
197 const float ax = uv1[i * 4 + 2] -
x,
ay = uv1[i * 4 + 3] -
y,
az = uv2[i * 4 + 3] -
z;
198 result[i] = uv3[i * 4 + 3] == 0.f ? clamp01(
y / std::max(
height, 1e-6f))
200 (uv1[i * 4 + 3] * uv3[i * 4 + 3]) /
std::
max(
radius, 1e-6f);
202 return Result<std::vector<float>>::failure(
204 {},
"asset.import.vegetation-preset.mesh"));
208 return Result<std::vector<float>>::failure(
210 {},
"asset.import.vegetation-preset.mesh"));
212 if (rule.size() >= 3 && rule.back().starts_with(
"ACTION_")) {
213 const auto&
action = rule.back();
214 if (action ==
"ACTION_ONE_MINUS")
215 for (
auto&
v : result)
v = 1.f -
v;
216 else if (action ==
"ACTION_NEGATIVE")
217 for (
auto&
v : result)
v = -
v;
218 else if (action ==
"ACTION_POWER_2")
219 for (
auto&
v : result)
v *=
v;
220 else if (action ==
"ACTION_MULTIPLY_BY_HEIGHT")
221 for (std::size_t i = 0; i <
count; ++i)
222 result[i] *= clamp01(
mesh.positions[i * 3 + 1] / std::max(
height, 1e-6f));
223 else if (action ==
"ACTION_CLAMP_NEGATIVE_VALUES") {
224 for (std::size_t i = 0; i <
count; ++i)
225 if (
mesh.positions[i * 3 + 1] < 0) result[i] = 0;
226 }
else if (action ==
"ACTION_FRACTIONAL_VALUES") {
227 for (
auto&
v : result)
v -=
std::floor(
v);
228 }
else if (action ==
"ACTION_REMAP_01") {
229 auto [lo, hi] = std::minmax_element(result.begin(), result.end());
230 const float range = *hi - *lo;
231 for (
auto&
v : result)
v =
range == 0 ? 0 : (
v - *lo) /
range;
234 "unsupported mesh mask action", {}, {},
235 "asset.import.vegetation-preset.mesh"));
237 return Result<std::vector<float>>::success(std::move(result));
240float packPair(
float x,
float y) {
return std::floor(clamp01(
x) * 2047.f) * 2048.f + std::floor(clamp01(
y) * 2047.f); }
242void normalize3(
float&
x,
float&
y,
float&
z) {
251Result<void> applyNormals(asset::CanonicalMeshData&
mesh,
const std::vector<std::string>& rule,
float height) {
252 if (rule.size() != 2 || rule[0] !=
"GET_NORMALS_PROCEDURAL")
254 "asset.import.vegetation-preset.mesh"));
256 if (!mode || mode.value() < 0 || mode.value() > 6)
258 "asset.import.vegetation-preset.mesh"));
259 const std::size_t
count =
mesh.positions.size() / 3;
260 if (mode.value() == 0) {
261 std::fill(
mesh.normals.begin(),
mesh.normals.end(), 0.f);
262 for (std::size_t i = 0; i + 2 <
mesh.indices.size(); i += 3) {
263 const auto a =
mesh.indices[i],
b =
mesh.indices[i + 1],
c =
mesh.indices[i + 2];
266 "mesh index out of range", {}, {},
267 "asset.import.vegetation-preset.mesh"));
268 const float ax =
mesh.positions[
b * 3] -
mesh.positions[
a * 3],
269 ay =
mesh.positions[
b * 3 + 1] -
mesh.positions[
a * 3 + 1],
270 az =
mesh.positions[
b * 3 + 2] -
mesh.positions[
a * 3 + 2];
271 const float bx =
mesh.positions[
c * 3] -
mesh.positions[
a * 3],
272 by =
mesh.positions[
c * 3 + 1] -
mesh.positions[
a * 3 + 1],
273 bz =
mesh.positions[
c * 3 + 2] -
mesh.positions[
a * 3 + 2];
275 for (
auto v : {
a,
b,
c}) {
281 for (std::size_t i = 0; i <
count; ++i)
282 normalize3(
mesh.normals[i * 3],
mesh.normals[i * 3 + 1],
mesh.normals[i * 3 + 2]);
285 for (std::size_t i = 0; i <
count; ++i) {
286 float tx = 0, ty = 1, tz = 0,
blend = 1;
287 if (mode.value() <= 3) {
288 blend = mode.value() == 1 ? clamp01(
mesh.positions[i * 3 + 1] / std::max(
height, 1e-6f))
292 tx =
mesh.positions[i * 3];
293 ty =
mesh.positions[i * 3 + 1];
294 tz =
mesh.positions[i * 3 + 2];
295 normalize3(tx, ty, tz);
296 blend = mode.value() == 4 ? .5f : mode.value() == 5 ? .75f : 1.f;
298 mesh.normals[i * 3] = std::lerp(
mesh.normals[i * 3], tx,
blend);
299 mesh.normals[i * 3 + 1] = std::lerp(
mesh.normals[i * 3 + 1], ty,
blend);
300 mesh.normals[i * 3 + 2] = std::lerp(
mesh.normals[i * 3 + 2], tz,
blend);
305Result<void> recalculateTangents(asset::CanonicalMeshData&
mesh) {
306 const auto count =
mesh.positions.size() / 3;
307 const auto uv =
mesh.texcoords.find(0);
308 if (
uv ==
mesh.texcoords.end()) {
309 mesh.attributes.erase(
"TANGENT");
312 if (
uv->second.size() !=
count * 2)
314 "canonical UV0 has an invalid tangent input size", {}, {},
315 "asset.import.vegetation-preset.mesh"));
316 std::vector<float> tan1(
count * 3), tan2(
count * 3);
317 for (std::size_t k = 0; k + 2 <
mesh.indices.size(); k += 3) {
318 const auto a =
mesh.indices[k],
b =
mesh.indices[k + 1],
c =
mesh.indices[k + 2];
321 "mesh index out of range while rebuilding tangents", {}, {},
322 "asset.import.vegetation-preset.mesh"));
323 const float x1 =
mesh.positions[
b * 3] -
mesh.positions[
a * 3],
324 x2 =
mesh.positions[
c * 3] -
mesh.positions[
a * 3],
325 y1 =
mesh.positions[
b * 3 + 1] -
mesh.positions[
a * 3 + 1],
326 y2 =
mesh.positions[
c * 3 + 1] -
mesh.positions[
a * 3 + 1],
327 z1 =
mesh.positions[
b * 3 + 2] -
mesh.positions[
a * 3 + 2],
328 z2 =
mesh.positions[
c * 3 + 2] -
mesh.positions[
a * 3 + 2],
329 s1 =
uv->second[
b * 2] -
uv->second[
a * 2], s2 =
uv->second[
c * 2] -
uv->second[
a * 2],
330 t1 =
uv->second[
b * 2 + 1] -
uv->second[
a * 2 + 1],
331 t2 =
uv->second[
c * 2 + 1] -
uv->second[
a * 2 + 1], det = s1 * t2 - s2 * t1;
332 if (std::abs(det) < 1e-12f)
continue;
333 const float r = 1.f / det,
sx = (x1 * t2 - x2 * t1) *
r,
sy = (y1 * t2 - y2 * t1) *
r,
334 sz = (z1 * t2 - z2 * t1) *
r, tx = (x2 * s1 - x1 * s2) *
r, ty = (y2 * s1 - y1 * s2) *
r,
335 tz = (z2 * s1 - z1 * s2) *
r;
336 for (
const auto vertex : {
a,
b,
c}) {
337 tan1[vertex * 3] +=
sx;
338 tan1[vertex * 3 + 1] +=
sy;
339 tan1[vertex * 3 + 2] +=
sz;
340 tan2[vertex * 3] += tx;
341 tan2[vertex * 3 + 1] += ty;
342 tan2[vertex * 3 + 2] += tz;
345 asset::CanonicalMeshAttribute tangents{4, std::vector<float>(
count * 4)};
346 for (std::size_t i = 0; i <
count; ++i) {
347 const float nx =
mesh.normals[i * 3],
ny =
mesh.normals[i * 3 + 1],
nz =
mesh.normals[i * 3 + 2];
348 float tx = tan1[i * 3], ty = tan1[i * 3 + 1], tz = tan1[i * 3 + 2];
349 const float projection =
nx * tx +
ny * ty +
nz * tz;
350 tx -=
nx * projection;
351 ty -=
ny * projection;
352 tz -=
nz * projection;
353 if (std::sqrt(tx * tx + ty * ty + tz * tz) <= 1e-8f) {
354 if (std::abs(
ny) < .999f) {
364 normalize3(tx, ty, tz);
366 tangents.values[i * 4] = tx;
367 tangents.values[i * 4 + 1] = ty;
368 tangents.values[i * 4 + 2] = tz;
369 tangents.values[i * 4 + 3] =
bx * tan2[i * 3] +
by * tan2[i * 3 + 1] +
bz * tan2[i * 3 + 2] < 0 ? -1.f : 1.f;
371 mesh.attributes[
"TANGENT"] = std::move(tangents);
378 const std::map<std::string, VegetationPresetImage>& textures,
float variationSeed) {
379 const std::size_t
count =
source.positions.size() / 3;
381 !std::isfinite(variationSeed))
383 "invalid mesh geometry or seed", {}, {},
384 "asset.import.vegetation-preset.mesh"));
387 for (std::size_t i = 0; i <
count; ++i) {
389 std::max(
radius, std::max(std::abs(
source.positions[i * 3]), std::abs(
source.positions[i * 3 + 2])));
393 auto uv0 = four(
source,
"_UNITY_UV0",
count, {0, 0, 0, 0});
394 auto uv1 = four(
source,
"_UNITY_UV1",
count, {0, 0, 0, 0});
395 auto apply = [&](
const char* ruleName,
unsigned component) ->
Result<void> {
400 for (std::size_t i = 0; i <
count; ++i)
color[i * 4 + component] =
values.value()[i];
403 for (
auto [
name, component] : {std::pair<const char*, unsigned>{
"SetVariation", 0
u},
404 {
"SetOcclusion", 1u},
405 {
"SetDetailMask", 2u},
406 {
"SetHeight", 3u}}) {
407 auto result = apply(
name, component);
411 candidate.
meshRules.contains(
"SetMotion2")
415 candidate.
meshRules.contains(
"SetMotion3")
420 const float packedBounds = packPair(
height / 100.f,
radius / 100.f);
421 for (std::size_t i = 0; i <
count; ++i) {
422 uv0[i * 4 + 2] = packPair(motion2.value()[i], motion3.value()[i]);
423 uv0[i * 4 + 3] = packedBounds;
426 found->second.size() >= 2 &&
427 found->second[0] ==
"GET_COORD_FROM_CHANNEL") {
428 auto option = integer(
found->second[1]);
430 auto coord = four(
source, (
"_UNITY_UV" + std::to_string(option.value())).c_str(),
count, {0, 0, 0, 0});
431 for (std::size_t i = 0; i <
count; ++i) {
432 uv1[i * 4 + 2] = coord[i * 4];
433 uv1[i * 4 + 3] = coord[i * 4 + 1];
436 source.attributes[
"COLOR_0"] = {4, std::move(
color)};
437 source.attributes[
"_UNITY_UV0"] = {4, std::move(uv0)};
438 source.attributes[
"_UNITY_UV1"] = {4, std::move(uv1)};
439 auto uv3 = four(
source,
"_UNITY_UV3",
count, {0, 0, 0, 0});
441 if (
found->second.size() == 1 &&
found->second[0] ==
"NONE") {
442 source.attributes[
"_UNITY_UV3"] = {4, std::move(uv3)};
444 if (
found->second.size() != 2 ||
found->second[0] !=
"GET_PIVOTS_PROCEDURAL" ||
found->second[1] !=
"0")
447 "asset.import.vegetation-preset.mesh"));
449 const auto groups = *std::max_element(
ids.begin(),
ids.end()) + 1;
451 std::vector<std::size_t> sizes(
groups);
452 for (std::size_t i = 0; i <
count; ++i) {
454 sumZ[
ids[i]] +=
source.positions[i * 3 + 2];
457 for (std::size_t i = 0; i <
count; ++i) {
458 uv3[i * 4] = float(sumX[
ids[i]] / sizes[
ids[i]]);
459 uv3[i * 4 + 1] = -float(sumZ[
ids[i]] / sizes[
ids[i]]);
463 source.attributes[
"_UNITY_UV3"] = {4, std::move(uv3)};
466 source.attributes[
"_UNITY_UV3"] = {4, std::move(uv3)};
471 changed = recalculateTangents(
source);
475 }
catch (
const std::bad_alloc&) {
477 "mesh conversion allocation failed", {}, {},
478 "asset.import.vegetation-preset.mesh"));
484 static const std::map<std::string, VegetationPresetImage> noTextures;
std::map< std::string, Var > values
std::array< std::uint8_t, 32 > hash
std::vector< float > positions
std::array< float, 3 > scale
const UnitySourceAsset & source
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.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
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.
int64_t integer(const RuntimeTensor &v, size_t i=0)
Integer.
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::map< std::string, std::vector< std::string > > meshRules