载入中...
搜索中...
未找到
FoliageCluster.cpp
浏览该文件的文档.
2
3#include "procgen/PointSet.h"
4
5#include <algorithm>
6#include <cmath>
7#include <cstdint>
8#include <random>
9#include <vector>
10
11namespace eve::procgen {
12namespace {
13
14constexpr float kPi = 3.14159265358979323846f;
15
16struct V3 {
17 float x = 0.f, y = 0.f, z = 0.f;
18};
19
20V3 add(V3 a, V3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
21V3 sub(V3 a, V3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
22V3 mul(V3 a, float s) { return {a.x * s, a.y * s, a.z * s}; }
23float dot(V3 a, V3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
24V3 cross(V3 a, V3 b) { return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; }
25
26V3 norm(V3 a) {
27 const float n = std::sqrt(std::max(1e-12f, dot(a, a)));
28 return mul(a, 1.f / n);
29}
30
31float randomRange(std::mt19937 &rng, float lo, float hi) { return std::uniform_real_distribution<float>(lo, hi)(rng); }
32
34void basisFor(V3 axis, V3 &right, V3 &forward) {
35 axis = norm(axis);
36 const V3 helper = std::fabs(axis.y) < 0.92f ? V3{0.f, 1.f, 0.f} : V3{1.f, 0.f, 0.f};
37 right = norm(cross(helper, axis));
38 forward = norm(cross(axis, right));
39}
40
41struct V2 {
42 float x = 0.f, y = 0.f;
43};
44
60std::vector<V2> samplePlaneCentres(std::uint32_t seed, float radius, float minDistance, int maxLeaves) {
61 std::vector<V2> centres;
62 if (radius <= 0.f || minDistance <= 0.f || maxLeaves <= 0) return centres;
63
64 // Hexagonal packing fits about 2A / (sqrt(3) d^2) centres in area A. Solving
65 // for the disk makes the *disk* land on the leaf cap; the enclosing square
66 // then saturates above it, so the budget below is only a safety cap and the
67 // fill is never truncated (a truncated Bridson fill is spatially biased).
68 const float capSpacing = std::sqrt(2.f * kPi * radius * radius / (std::sqrt(3.f) * float(maxLeaves)));
69 const float spacing = std::max(minDistance, capSpacing);
70 const int squareSide = std::max(2, int(std::ceil(2.f * radius)));
71 const float squareArea = float(squareSide) * float(squareSide);
72 const int squareBudget = int(1.3f * float(maxLeaves) * squareArea / std::max(kPi * radius * radius, 1e-6f)) + 8;
73
74 const PointSet square = poissonDiskPoints(squareSide, squareSide, spacing, seed, squareBudget);
75 const float half = 0.5f * float(squareSide);
76 // `poissonDiskPoints` samples the XZ plane, so the second area axis is z.
77 for (const ProcgenPoint &point : square.points()) {
78 const float dx = point.x - half;
79 const float dz = point.z - half;
80 if (dx * dx + dz * dz > radius * radius) continue;
81 centres.push_back({dx, dz});
82 if (int(centres.size()) >= maxLeaves) break;
83 }
84 return centres;
85}
94template <typename NormalAt>
95void addPetal(MeshBuild &out, V3 center, V3 right, V3 forward, float size, float roll, float u0, float u1,
96 float v0, float v1, bool doubleSided, const NormalAt &normalAt) {
97 const float c = std::cos(roll), s = std::sin(roll);
98 const V3 axisX = add(mul(right, c), mul(forward, s));
99 const V3 axisY = add(mul(right, -s), mul(forward, c));
100
101 // Ovate outline: rounded body, soft tip — matches bush/tree leaf cards.
102 constexpr float kOutlineX[6] = {0.f, -0.40f, -0.46f, 0.f, 0.46f, 0.40f};
103 constexpr float kOutlineY[6] = {-0.48f, -0.18f, 0.16f, 0.50f, 0.16f, -0.18f};
104
105 const uint32_t base = uint32_t(out.getVertexCount());
106 for (int i = 0; i < 6; ++i) {
107 const V3 p = add(center, add(mul(axisX, kOutlineX[i] * size), mul(axisY, kOutlineY[i] * size)));
108 const V3 n = normalAt(p);
109 const float u = u0 + (0.5f + kOutlineX[i]) * (u1 - u0);
110 const float v = v0 + (0.5f + kOutlineY[i]) * (v1 - v0);
111 out.addVertex(p.x, p.y, p.z, n.x, n.y, n.z, u, v);
112 }
113 for (int i = 1; i < 5; ++i) {
114 out.addTriangle(base, base + uint32_t(i), base + uint32_t(i + 1));
115 }
116 if (doubleSided) {
117 for (int i = 1; i < 5; ++i) {
118 out.addTriangle(base, base + uint32_t(i + 1), base + uint32_t(i));
119 }
120 }
121}
122
123} // namespace
124
126 const float radius = std::max(1e-3f, desc.radius);
127 const float leafSize = std::max(1e-3f, desc.leafSize);
128 const float leafSpacing = std::max(0.05f, desc.leafSpacing);
129 const int planes = std::clamp(desc.planes, 1, 24);
130 const int capPlanes = std::clamp(desc.capPlanes, 0, 8);
131 const float tiltMax = std::clamp(desc.tiltDegrees, 0.f, 80.f) * kPi / 180.f;
132 const float planeOffset = std::clamp(desc.planeOffset, 0.f, 0.6f) * radius;
133 const float scaleVar = std::clamp(desc.planeScaleVariation, 0.f, 0.6f);
134 const float leafJitter = std::clamp(desc.leafJitter, 0.f, 0.5f) * radius;
135 const float sizeVar = std::clamp(desc.leafScaleVariation, 0.f, 0.6f);
136 const int maxLeaves = std::clamp(desc.maxLeavesPerPlane, 1, 256);
137 const float rounding = std::clamp(desc.normalRounding, 0.f, 1.f);
138 const float uvMin = std::min(desc.uvMin, desc.uvMax);
139 const float uvMax = std::max(desc.uvMin, desc.uvMax);
140
141 const V3 center{desc.centerX, desc.centerY, desc.centerZ};
142 std::mt19937 rng(desc.seed);
143
144 // Requested blue-noise spacing. `samplePlaneCentres` widens it further when
145 // it would otherwise overshoot the per-plane leaf cap.
146 const float minDistance = leafSize * leafSpacing;
147
148 // Vertex normals come from the cluster sphere so a bundle of flat cards
149 // still shades as one rounded mass. Near the centre the sphere direction is
150 // ill-conditioned, so the blend falls back to the plane normal there.
151 const float roundingDistance = std::max(1e-4f, radius * 0.35f);
152 const auto normalAt = [&](V3 p, V3 planeNormal) {
153 const V3 radial = sub(p, center);
154 const float length = std::sqrt(dot(radial, radial));
155 const float weight = rounding * std::clamp(length / roundingDistance, 0.f, 1.f);
156 if (length < 1e-5f || weight <= 0.f) return planeNormal;
157 const V3 blended = add(mul(mul(radial, 1.f / length), weight), mul(planeNormal, 1.f - weight));
158 if (dot(blended, blended) < 1e-6f) return planeNormal;
159 return norm(blended);
160 };
161
162 const int planeCount = planes + capPlanes;
163 const float yawPhase = randomRange(rng, 0.f, 2.f * kPi);
164 const float yawStep = 2.f * kPi / float(planeCount);
165 int leaves = 0;
166 for (int index = 0; index < planeCount; ++index) {
167 const bool cap = index >= planes;
168
169 // Ring planes take an even share of the circle plus a jitter, so the
170 // bundle never reads as a regular polygon from any angle.
171 const float yaw =
172 cap ? randomRange(rng, 0.f, 2.f * kPi) : yawPhase + (float(index) + randomRange(rng, 0.2f, 0.8f)) * yawStep;
173 // Ring planes lean a little off vertical; the caps lean near-horizontal
174 // so the cluster's poles are not left hollow.
175 const float tilt = cap ? ((index & 1) ? 1.f : -1.f) * randomRange(rng, 62.f, 86.f) * kPi / 180.f
176 : randomRange(rng, -tiltMax, tiltMax);
177 const V3 planeNormal{std::sin(yaw) * std::cos(tilt), std::sin(tilt), std::cos(yaw) * std::cos(tilt)};
178
179 V3 right, forward;
180 basisFor(planeNormal, right, forward);
181
182 const float radialScale = 1.f - scaleVar * randomRange(rng, 0.f, 1.f) - (cap ? 0.35f : 0.f);
183 const float planeRadius = radius * std::max(0.2f, radialScale);
184 const float offsetAngle = randomRange(rng, 0.f, 2.f * kPi);
185 const float offsetR = planeOffset * std::sqrt(randomRange(rng, 0.f, 1.f));
186 const V3 origin = add(
187 center, add(mul(right, std::cos(offsetAngle) * offsetR), mul(forward, std::sin(offsetAngle) * offsetR)));
188
189 const std::vector<V2> centers = samplePlaneCentres(desc.seed ^ (std::uint32_t(index + 1) * 2654435761u),
190 planeRadius, minDistance, maxLeaves);
191 for (const V2 &point : centers) {
192 const float jitter = randomRange(rng, -1.f, 1.f) * leafJitter;
193 const V3 position =
194 add(origin, add(add(mul(right, point.x), mul(forward, point.y)), mul(planeNormal, jitter)));
195
196 // Point the petals outward from the cluster centre so the bundle
197 // reads as growth radiating from the branch rather than confetti.
198 float roll = randomRange(rng, 0.f, 2.f * kPi);
199 const V3 radial = sub(position, center);
200 const V3 tangent = sub(radial, mul(planeNormal, dot(radial, planeNormal)));
201 if (dot(tangent, tangent) > 1e-8f) {
202 const V3 outward = norm(tangent);
203 roll = std::atan2(dot(outward, forward), dot(outward, right)) - 0.5f * kPi +
204 randomRange(rng, -0.85f, 0.85f);
205 }
206
207 const float size = leafSize * (1.f + randomRange(rng, -sizeVar, sizeVar));
208 // One of six shared leaf-card panels (tex.tree_atlas / tex.foliage).
209 constexpr int kCols = 2;
210 constexpr int kRows = 3;
211 const int col = int(randomRange(rng, 0.f, float(kCols))) % kCols;
212 const int row = int(randomRange(rng, 0.f, float(kRows))) % kRows;
213 const float inset = 0.04f;
214 const float spanU = (uvMax - uvMin) / float(kCols);
215 const float spanV = 1.f / float(kRows);
216 const float u0 = uvMin + (float(col) + inset) * spanU;
217 const float u1 = uvMin + (float(col) + 1.f - inset) * spanU;
218 const float v0 = (float(row) + inset) * spanV;
219 const float v1 = (float(row) + 1.f - inset) * spanV;
220 const auto shadedNormal = [&](V3 p) { return normalAt(p, planeNormal); };
221 addPetal(out, position, right, forward, size, roll, u0, u1, v0, v1, desc.doubleSided, shadedNormal);
222 ++leaves;
223 }
224 }
225 return leaves;
226}
227
228} // namespace eve::procgen
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
const std::string & s
Vec3 tangent
Definition CaveMesh.cpp:80
float length
Definition CaveMesh.cpp:94
float planes[6][4]
glm::vec4 p[6]
scene::NodeDesc desc
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
float v
HexVec3 right
std::int32_t c
std::array< float, 3 > position
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
Vec3 planeNormal
bool doubleSided
float radius
std::uint32_t seed
Definition PointSet.cpp:807
std::shared_ptr< const std::vector< glm::vec2 > > points
V3 origin
Definition RoadBake.cpp:138
float axisY
Definition RockMesh.cpp:22
float axisX
Definition RockMesh.cpp:22
float dz
float dx
int spacing
float tilt
Definition TreeMesh.cpp:160
float size
Definition TreeMesh.cpp:156
uint32_t index
glm::vec3 point
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
Definition MeshBuild.h:19
std::vector< double > forward(const Policy &p, const Observation &o)
Forward.
Definition Learning.h:65
double dot(const Vec2 &a, const Vec2 &b)
Dot.
Definition UrbanTypes.h:38
double cross(const Vec2 &a, const Vec2 &b)
Cross.
Definition UrbanTypes.h:36
int addFoliageCluster(MeshBuild &out, const FoliageClusterDesc &desc)
Append one blue-noise leaf cluster to a mesh.
PointSet poissonDiskPoints(int width, int depth, float radius, uint32_t seed, int maxPoints)
Bridson blue-noise (Poisson disk) samples in a width x depth area (XZ, y=0).
Definition PointSet.cpp:502
int axis(int64_t a, size_t rank)
Axis.
Recipe for one stylized leaf cluster ("foliage card bundle").