MayaFlux 0.2.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
VertexSampler.cpp
Go to the documentation of this file.
1#include "VertexSampler.hpp"
2
3#include "MotionCurves.hpp"
4
5#include <glm/gtc/constants.hpp>
6
7namespace MayaFlux::Kinesis {
8
9namespace {
10
11 SampleResult sample_random_volume(const SamplerBounds& b, Stochastic::Stochastic& rng)
12 {
13 glm::vec3 pos {
14 rng(b.min.x, b.max.x),
15 rng(b.min.y, b.max.y),
16 rng(b.min.z, b.max.z)
17 };
18 const glm::vec3 ext = b.extent();
19 return { .position = pos, .color = (pos - b.min) / ext, .scalar = 0.5F };
20 }
21
22 SampleResult sample_random_surface(const SamplerBounds& b, Stochastic::Stochastic& rng)
23 {
24 static constexpr glm::vec3 k_face_colors[6] {
25 { 0.8F, 0.3F, 0.3F }, { 1.0F, 0.4F, 0.4F },
26 { 0.3F, 0.8F, 0.3F }, { 0.4F, 1.0F, 0.4F },
27 { 0.3F, 0.3F, 0.8F }, { 0.4F, 0.4F, 1.0F }
28 };
29
30 const int face = static_cast<int>(rng(0, 6));
31 glm::vec3 pos;
32
33 switch (face) {
34 case 0:
35 pos = { b.min.x, rng(b.min.y, b.max.y), rng(b.min.z, b.max.z) };
36 break;
37 case 1:
38 pos = { b.max.x, rng(b.min.y, b.max.y), rng(b.min.z, b.max.z) };
39 break;
40 case 2:
41 pos = { rng(b.min.x, b.max.x), b.min.y, rng(b.min.z, b.max.z) };
42 break;
43 case 3:
44 pos = { rng(b.min.x, b.max.x), b.max.y, rng(b.min.z, b.max.z) };
45 break;
46 case 4:
47 pos = { rng(b.min.x, b.max.x), rng(b.min.y, b.max.y), b.min.z };
48 break;
49 default:
50 pos = { rng(b.min.x, b.max.x), rng(b.min.y, b.max.y), b.max.z };
51 break;
52 }
53
54 return { .position = pos, .color = k_face_colors[face], .scalar = 1.0F };
55 }
56
57 SampleResult sample_grid(const SamplerBounds& b, size_t idx, size_t total)
58 {
59 const size_t gs = static_cast<size_t>(std::cbrt(static_cast<double>(total))) + 1;
60 const glm::vec3 spacing = b.extent() / static_cast<float>(gs);
61 const size_t x = idx % gs;
62 const size_t y = (idx / gs) % gs;
63 const size_t z = idx / (gs * gs);
64
65 const glm::vec3 pos = b.min + glm::vec3(static_cast<float>(x) * spacing.x, static_cast<float>(y) * spacing.y, static_cast<float>(z) * spacing.z);
66
67 const glm::vec3 color {
68 static_cast<float>(x) / static_cast<float>(gs),
69 static_cast<float>(y) / static_cast<float>(gs),
70 static_cast<float>(z) / static_cast<float>(gs)
71 };
72
73 return { .position = pos, .color = color, .scalar = 0.5F };
74 }
75
76 SampleResult sample_sphere_volume(const SamplerBounds& b, Stochastic::Stochastic& rng)
77 {
78 const float mr = b.max_radius();
79 const float radius = mr * std::cbrt(static_cast<float>(rng(0.0F, 1.0F)));
80 const auto theta = static_cast<float>(rng(0.0F, glm::two_pi<double>()));
81 const float phi = std::acos(static_cast<float>(rng(-1.0F, 1.0F)));
82
83 const glm::vec3 pos = b.center() + glm::vec3(radius * std::sin(phi) * std::cos(theta), radius * std::sin(phi) * std::sin(theta), radius * std::cos(phi));
84
85 const float norm = radius / mr;
86 return {
87 .position = pos,
88 .color = glm::mix(glm::vec3(1.0F, 0.8F, 0.2F), glm::vec3(0.2F, 0.4F, 1.0F), norm),
89 .scalar = 1.0F - norm
90 };
91 }
92
93 SampleResult sample_sphere_surface(const SamplerBounds& b, Stochastic::Stochastic& rng)
94 {
95 const float radius = b.max_radius();
96 const auto theta = static_cast<float>(rng(0.0F, glm::two_pi<double>()));
97 const float phi = std::acos(static_cast<float>(rng(-1.0F, 1.0F)));
98
99 const glm::vec3 pos = b.center() + glm::vec3(radius * std::sin(phi) * std::cos(theta), radius * std::sin(phi) * std::sin(theta), radius * std::cos(phi));
100
101 const float lat = std::sin(phi);
102 return {
103 .position = pos,
104 .color = { (std::sin(theta) + 1.0F) * 0.5F, phi / glm::pi<float>(), (std::cos(theta) + 1.0F) * 0.5F },
105 .scalar = lat
106 };
107 }
108
109 SampleResult sample_uniform_grid(const SamplerBounds& b, size_t idx, size_t total)
110 {
111 const auto ppa = static_cast<size_t>(std::cbrt(static_cast<double>(total)));
112 const glm::vec3 step = b.extent() / static_cast<float>(ppa - 1 > 0 ? ppa - 1 : 1);
113 const size_t x = idx % ppa;
114 const size_t y = (idx / ppa) % ppa;
115 const size_t z = idx / (ppa * ppa);
116
117 const glm::vec3 pos = b.min + glm::vec3(static_cast<float>(x) * step.x, static_cast<float>(y) * step.y, static_cast<float>(z) * step.z);
118
119 const glm::vec3 color {
120 static_cast<float>(x) / static_cast<float>(ppa - 1 > 0 ? ppa - 1 : 1),
121 static_cast<float>(y) / static_cast<float>(ppa - 1 > 0 ? ppa - 1 : 1),
122 static_cast<float>(z) / static_cast<float>(ppa - 1 > 0 ? ppa - 1 : 1)
123 };
124
125 const float t = glm::length(pos - b.center()) / b.max_radius();
126 return { .position = pos, .color = color, .scalar = t };
127 }
128
129 SampleResult sample_random_sphere(const SamplerBounds& b, Stochastic::Stochastic& rng)
130 {
131 const auto theta = static_cast<float>(rng(0.0F, glm::two_pi<double>()));
132 const float phi = std::acos(static_cast<float>(rng(-1.0F, 1.0F)));
133 const float radius = b.max_radius() * static_cast<float>(std::cbrt(rng(0.0F, 1.0F)));
134
135 const glm::vec3 pos = b.center() + radius * glm::vec3(std::sin(phi) * std::cos(theta), std::sin(phi) * std::sin(theta), std::cos(phi));
136
137 return {
138 .position = pos,
139 .color = { radius / b.max_radius(), theta / glm::two_pi<float>(), phi / glm::pi<float>() },
140 .scalar = radius / b.max_radius()
141 };
142 }
143
144 SampleResult sample_random_cube(const SamplerBounds& b, Stochastic::Stochastic& rng)
145 {
146 const glm::vec3 pos {
147 rng(b.min.x, b.max.x),
148 rng(b.min.y, b.max.y),
149 rng(b.min.z, b.max.z)
150 };
151 return { .position = pos, .color = (pos - b.min) / b.extent(), .scalar = 0.5F };
152 }
153
154 std::vector<SampleResult> sample_perlin_field(
155 const SamplerBounds& b,
156 size_t count,
157 Stochastic::Stochastic& rng)
158 {
159 auto perlin = Stochastic::perlin(4, 0.5);
160 std::vector<SampleResult> out;
161 out.reserve(count);
162
163 while (out.size() < count) {
164 const glm::vec3 p {
165 rng(b.min.x, b.max.x),
166 rng(b.min.y, b.max.y),
167 rng(b.min.z, b.max.z)
168 };
169 if (perlin.at(p.x, p.y, p.z) > rng(0.0, 1.0)) {
170 out.push_back({ .position = p, .color = (p - b.min) / b.extent(), .scalar = 0.5F });
171 }
172 }
173
174 return out;
175 }
176
177 std::vector<SampleResult> sample_brownian_path(
178 const SamplerBounds& b,
179 size_t count,
180 Stochastic::Stochastic& rng)
181 {
182 auto alg_backup = rng.get_algorithm();
183 rng.set_algorithm(Stochastic::Algorithm::BROWNIAN);
184
185 std::vector<SampleResult> out;
186 out.reserve(count);
187
188 glm::vec3 pos = b.center();
189 for (size_t i = 0; i < count; ++i) {
190 pos += glm::vec3(rng(-1.0, 1.0), rng(-1.0, 1.0), rng(-1.0, 1.0)) * 0.1F;
191 pos = glm::clamp(pos, b.min, b.max);
192 out.push_back({ .position = pos,
193 .color = glm::vec3(static_cast<float>(i) / static_cast<float>(count)),
194 .scalar = static_cast<float>(i) / static_cast<float>(count) });
195 }
196 rng.set_algorithm(alg_backup);
197
198 return out;
199 }
200
201 std::vector<SampleResult> sample_stratified_cube(
202 const SamplerBounds& b,
203 size_t count,
204 Stochastic::Stochastic& rng)
205 {
206 const auto ppa = static_cast<size_t>(std::cbrt(count));
207 const glm::vec3 step = b.extent() / static_cast<float>(ppa);
208 std::vector<SampleResult> out;
209 out.reserve(ppa * ppa * ppa);
210
211 for (size_t x = 0; x < ppa; ++x) {
212 for (size_t y = 0; y < ppa; ++y) {
213 for (size_t z = 0; z < ppa; ++z) {
214 const glm::vec3 jitter { rng(-0.5F, 0.5F), rng(-0.5F, 0.5F), rng(-0.5F, 0.5F) };
215 const glm::vec3 pos = b.min + (glm::vec3(x, y, z) + 0.5F + jitter) * step;
216 out.push_back({ .position = pos, .color = (pos - b.min) / b.extent(), .scalar = 0.6F });
217 }
218 }
219 }
220
221 return out;
222 }
223
224 std::vector<SampleResult> sample_spline_path(
225 const SamplerBounds& b,
226 size_t count,
227 Stochastic::Stochastic& rng)
228 {
229 Eigen::MatrixXd ctrl(3, 6);
230 for (int i = 0; i < 6; ++i) {
231 ctrl.col(i) = Eigen::Vector3d(
232 rng(b.min.x, b.max.x),
233 rng(b.min.y, b.max.y),
234 rng(b.min.z, b.max.z));
235 }
236
237 Eigen::MatrixXd path = generate_interpolated_points(ctrl, static_cast<Eigen::Index>(count), InterpolationMode::CATMULL_ROM);
238
239 std::vector<SampleResult> out;
240 out.reserve(path.cols());
241 for (Eigen::Index i = 0; i < path.cols(); ++i) {
242 const glm::vec3 pos(path(0, i), path(1, i), path(2, i));
243 out.push_back({ .position = pos, .color = glm::vec3(0.1F, 0.8F, 0.4F), .scalar = 0.5F });
244 }
245
246 return out;
247 }
248
249 std::vector<SampleResult> sample_fibonacci_sphere(const SamplerBounds& b, size_t count)
250 {
251 const float phi = glm::pi<float>() * (3.0F - std::sqrt(5.0F));
252 const float mr = b.max_radius();
253 const glm::vec3 ext = b.extent();
254 std::vector<SampleResult> out;
255 out.reserve(count);
256
257 for (size_t i = 0; i < count; ++i) {
258 const float y = 1.0F - (static_cast<float>(i) / static_cast<float>(count - 1)) * 2.0F;
259 const float radius = std::sqrt(1.0F - y * y);
260 const float theta = phi * static_cast<float>(i);
261 const glm::vec3 pos = b.center() + mr * glm::vec3(std::cos(theta) * radius, y, std::sin(theta) * radius);
262 out.push_back({ .position = pos, .color = (pos - b.min) / ext, .scalar = 1.0F });
263 }
264
265 return out;
266 }
267
268 std::vector<SampleResult> sample_fibonacci_spiral(const SamplerBounds& b, size_t count)
269 {
270 const float golden_angle = glm::pi<float>() * (3.0F - std::sqrt(5.0F));
271 const float mr = b.max_radius();
272 std::vector<SampleResult> out;
273 out.reserve(count);
274
275 for (size_t i = 0; i < count; ++i) {
276 const float r = mr * std::sqrt(static_cast<float>(i) / static_cast<float>(count));
277 const float theta = static_cast<float>(i) * golden_angle;
278 const glm::vec3 pos = b.center() + glm::vec3(r * std::cos(theta), r * std::sin(theta), 0.0F);
279 out.push_back({ .position = pos,
280 .color = { r / mr, 0.5F, 1.0F - r / mr },
281 .scalar = r / mr });
282 }
283
284 return out;
285 }
286
287 std::vector<SampleResult> sample_lissajous(const SamplerBounds& b, size_t count)
288 {
289 static constexpr float a = 3.0F, bv = 2.0F, c = 5.0F;
290 const float mr = b.max_radius();
291 std::vector<SampleResult> out;
292 out.reserve(count);
293
294 for (size_t i = 0; i < count; ++i) {
295 const float t = (static_cast<float>(i) / static_cast<float>(count)) * glm::two_pi<float>() * 2.0F;
296 const glm::vec3 pos = b.center() + mr * glm::vec3(std::sin(a * t), std::sin(bv * t), std::sin(c * t));
297 out.push_back({ .position = pos,
298 .color = { 0.5F + pos.x, 0.5F, 0.8F },
299 .scalar = 1.0F });
300 }
301
302 return out;
303 }
304
305 std::vector<SampleResult> sample_torus(const SamplerBounds& b, size_t count)
306 {
307 const float mr = b.max_radius();
308 const float main_r = mr * 0.7F;
309 const float tube_r = mr * 0.3F;
310 std::vector<SampleResult> out;
311 out.reserve(count);
312
313 for (size_t i = 0; i < count; ++i) {
314 const float u = (static_cast<float>(i) / static_cast<float>(count)) * glm::two_pi<float>();
315 const float v = (static_cast<float>(i * 7 % count) / static_cast<float>(count)) * glm::two_pi<float>();
316 const glm::vec3 pos = b.center() + glm::vec3((main_r + tube_r * std::cos(v)) * std::cos(u), (main_r + tube_r * std::cos(v)) * std::sin(u), tube_r * std::sin(v));
317 const glm::vec3 color { (std::cos(u) + 1.0F) * 0.5F, (std::cos(v) + 1.0F) * 0.5F, (std::sin(u) + 1.0F) * 0.5F };
318 out.push_back({ .position = pos, .color = color, .scalar = (std::cos(v) + 1.0F) * 0.5F });
319 }
320
321 return out;
322 }
323
324} // anonymous namespace
325
326//-----------------------------------------------------------------------------
327
328std::vector<SampleResult> generate_samples(
330 size_t count,
331 const SamplerBounds& bounds,
333{
334 if (count == 0 || dist == SpatialDistribution::EMPTY) {
335 return {};
336 }
337
338 switch (dist) {
340 return sample_perlin_field(bounds, count, rng);
342 return sample_brownian_path(bounds, count, rng);
344 return sample_stratified_cube(bounds, count, rng);
346 return sample_spline_path(bounds, count, rng);
348 return sample_fibonacci_sphere(bounds, count);
350 return sample_fibonacci_spiral(bounds, count);
352 return sample_torus(bounds, count);
354 return sample_lissajous(bounds, count);
355
356 default: {
357 std::vector<SampleResult> out;
358 out.reserve(count);
359 for (size_t i = 0; i < count; ++i) {
360 out.push_back(generate_sample_at(dist, i, count, bounds, rng));
361 }
362 return out;
363 }
364 }
365}
366
369 size_t index,
370 size_t total,
371 const SamplerBounds& bounds,
373{
374 switch (dist) {
376 return sample_random_volume(bounds, rng);
378 return sample_random_surface(bounds, rng);
380 return sample_grid(bounds, index, total);
382 return sample_sphere_volume(bounds, rng);
384 return sample_sphere_surface(bounds, rng);
386 return sample_uniform_grid(bounds, index, total);
388 return sample_random_sphere(bounds, rng);
390 return sample_random_cube(bounds, rng);
391 default:
392 return { .position = glm::vec3(0.0F), .color = glm::vec3(0.5F), .scalar = 0.5F };
393 }
394}
395
396std::vector<Nodes::PointVertex> to_point_vertices(
397 std::span<const SampleResult> samples,
398 glm::vec2 size_range)
399{
400 std::vector<Nodes::PointVertex> out;
401 out.reserve(samples.size());
402 for (const auto& s : samples) {
403 out.push_back(to_point_vertex(s, size_range));
404 }
405 return out;
406}
407
408std::vector<Nodes::LineVertex> to_line_vertices(
409 std::span<const SampleResult> samples,
410 glm::vec2 thickness_range)
411{
412 std::vector<Nodes::LineVertex> out;
413 out.reserve(samples.size());
414 for (const auto& s : samples) {
415 out.push_back(to_line_vertex(s, thickness_range));
416 }
417 return out;
418}
419
420} // namespace MayaFlux::Kinesis
Eigen::Index count
size_t a
size_t b
double at(double x, double y=0.0, double z=0.0)
Multi-dimensional generation (Perlin, spatial noise)
Unified generative infrastructure for stochastic and procedural algorithms.
Stochastic perlin(int octaves=4, double persistence=0.5)
Creates Perlin noise generator.
SampleResult generate_sample_at(SpatialDistribution dist, size_t index, size_t total, const SamplerBounds &bounds, Stochastic::Stochastic &rng)
Generate a single sample at a specific index (for indexed/sequential modes).
Nodes::PointVertex to_point_vertex(const SampleResult &s, glm::vec2 size_range={ 8.0F, 12.0F }) noexcept
Project SampleResult to PointVertex.
Eigen::MatrixXd generate_interpolated_points(const Eigen::MatrixXd &control_points, Eigen::Index num_samples, InterpolationMode mode, double tension)
Generate interpolated points from control points.
std::vector< Nodes::PointVertex > to_point_vertices(std::span< const SampleResult > samples, glm::vec2 size_range)
Batch-project SampleResult vector to PointVertex.
std::vector< Nodes::LineVertex > to_line_vertices(std::span< const SampleResult > samples, glm::vec2 thickness_range)
Batch-project SampleResult vector to LineVertex.
SpatialDistribution
Spatial distribution mode for point cloud and particle generation.
Nodes::LineVertex to_line_vertex(const SampleResult &s, glm::vec2 thickness_range={ 1.0F, 2.0F }) noexcept
Project SampleResult to LineVertex.
std::vector< SampleResult > generate_samples(SpatialDistribution dist, size_t count, const SamplerBounds &bounds, Stochastic::Stochastic &rng)
Generate a batch of spatially distributed samples.
Position and normalised color derived from spatial sampling.
Spatial domain for vertex generation.