MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
UVProjection.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "Tendency.hpp"
4
5namespace MayaFlux::Kinesis {
6
7/**
8 * @brief cartesian projection along a chosen axis
9 * @param axis Normal of the projection plane (normalised by caller)
10 * @param origin World-space origin of the UV tile
11 * @param scale UV scale: larger values tile more tightly
12 * @return UVField: glm::vec3 -> glm::vec2
13 *
14 * Projects each position onto the plane perpendicular to @p axis and
15 * returns the two remaining components as (u, v). The active axes are
16 * chosen to avoid the dominant axis so the result is never degenerate.
17 *
18 * Axis selection:
19 * |axis.y| <= |axis.x| and |axis.y| <= |axis.z| -> tangent = world Y
20 * otherwise -> tangent = world X
21 * The bitangent is cross(axis, tangent), giving an orthonormal frame.
22 */
24 const glm::vec3& axis = glm::vec3(0.0F, 0.0F, 1.0F),
25 const glm::vec3& origin = glm::vec3(0.0F),
26 float scale = 1.0F)
27{
28 const glm::vec3 n = glm::normalize(axis);
29
30 const glm::vec3 tangent = (std::abs(n.y) <= std::abs(n.x) && std::abs(n.y) <= std::abs(n.z))
31 ? glm::normalize(glm::cross(n, glm::vec3(0.0F, 1.0F, 0.0F)))
32 : glm::normalize(glm::cross(n, glm::vec3(1.0F, 0.0F, 0.0F)));
33
34 const glm::vec3 bitangent = glm::cross(n, tangent);
35
36 return { .fn = [tangent, bitangent, origin, scale](const glm::vec3& p) -> glm::vec2 {
37 const glm::vec3 d = p - origin;
38 return glm::vec2(glm::dot(d, tangent), glm::dot(d, bitangent)) * scale;
39 } };
40}
41
42/**
43 * @brief Cylindrical projection around a world axis
44 * @param axis Cylinder axis direction (normalised by caller)
45 * @param origin Point on the cylinder axis closest to world origin
46 * @param radius Cylinder radius used to normalise the radial component
47 * @param height Full height of one UV tile along the axis
48 * @return UVField: glm::vec3 -> glm::vec2
49 *
50 * u = azimuth angle in [0, 1], wraps at the seam behind the axis.
51 * v = axial distance / height, unbounded — tiles vertically.
52 *
53 * Vertices straddling the seam (angle wraps π to -π) will show a UV
54 * discontinuity. Orient geometry or rotate @p axis to push the seam
55 * to a non-visible region.
56 */
58 const glm::vec3& axis = glm::vec3(0.0F, 1.0F, 0.0F),
59 const glm::vec3& origin = glm::vec3(0.0F),
60 float radius = 1.0F,
61 float height = 1.0F)
62{
63 const glm::vec3 n = glm::normalize(axis);
64
65 const glm::vec3 tangent = (std::abs(n.y) <= std::abs(n.x) && std::abs(n.y) <= std::abs(n.z))
66 ? glm::normalize(glm::cross(n, glm::vec3(0.0F, 1.0F, 0.0F)))
67 : glm::normalize(glm::cross(n, glm::vec3(1.0F, 0.0F, 0.0F)));
68
69 const glm::vec3 bitangent = glm::cross(n, tangent);
70
71 const float inv_radius = (radius > 1e-6F) ? (1.0F / radius) : 1.0F;
72 const float inv_height = (height > 1e-6F) ? (1.0F / height) : 1.0F;
73
74 return { .fn = [n, tangent, bitangent, origin, inv_radius, inv_height](const glm::vec3& p) -> glm::vec2 {
75 const glm::vec3 d = p - origin;
76 const float axial = glm::dot(d, n);
77 const float radial_x = glm::dot(d, tangent) * inv_radius;
78 const float radial_y = glm::dot(d, bitangent) * inv_radius;
79 const float u = (glm::atan(radial_y, radial_x) / static_cast<float>(std::numbers::pi) + 1.0F) * 0.5F;
80 const float v = axial * inv_height;
81 return { u, v };
82 } };
83}
84
85/**
86 * @brief Spherical projection from a centre point
87 * @param centre World-space centre of the sphere
88 * @return UVField: glm::vec3 -> glm::vec2
89 *
90 * u = longitude in [0, 1], zero at +X, increasing counter-clockwise.
91 * v = latitude in [0, 1], zero at south pole, one at north pole.
92 *
93 * Degenerate at the poles (sin(phi) -> 0) and at @p centre.
94 * Vertices straddling the longitude seam will show a UV discontinuity.
95 */
96inline UVField spherical(const glm::vec3& centre = glm::vec3(0.0F))
97{
98 return { .fn = [centre](const glm::vec3& p) -> glm::vec2 {
99 const glm::vec3 d = p - centre;
100 const float len = glm::length(d);
101 if (len < 1e-6F)
102 return glm::vec2(0.0F);
103
104 const glm::vec3 n = d / len;
105 const float u = (glm::atan(n.z, n.x) / static_cast<float>(std::numbers::pi) + 1.0F) * 0.5F;
106 const float v = n.y * 0.5F + 0.5F;
107 return { u, v };
108 } };
109}
110
111/**
112 * @brief axial_blend projection: blend of three axis-aligned planar projections
113 * @param origin World-space tile origin
114 * @param scale UV scale applied uniformly to all three planes
115 * @param blend Sharpness of the blend between planes (higher = sharper)
116 * @return UVField: glm::vec3 -> glm::vec2
117 *
118 * Projects onto XZ, XY, and YZ planes and blends by weights derived
119 * from the absolute value of the offset position, raised to @p blend
120 * and normalised. Uses position as a proxy for surface normal, which
121 * works well for geometry radiating from the origin. Geometry that is
122 * flat or axially aligned may show visible blending artefacts.
123 *
124 * The blended UV is a weighted average:
125 * XZ plane contributes (x, z) weighted by the Y component.
126 * XY plane contributes (x, y) weighted by the Z component.
127 * YZ plane contributes (y, z) weighted by the X component.
128 */
130 const glm::vec3& origin = glm::vec3(0.0F),
131 float scale = 1.0F,
132 float blend = 4.0F)
133{
134 return { .fn = [origin, scale, blend](const glm::vec3& p) -> glm::vec2 {
135 const glm::vec3 d = (p - origin) * scale;
136
137 glm::vec3 w = glm::pow(glm::abs(d), glm::vec3(blend));
138 const float wsum = w.x + w.y + w.z;
139 if (wsum < 1e-6F)
140 return glm::vec2(0.0F);
141 w /= wsum;
142
143 const glm::vec2 uv_xz(d.x, d.z);
144 const glm::vec2 uv_xy(d.x, d.y);
145 const glm::vec2 uv_yz(d.y, d.z);
146
147 return uv_xz * w.y + uv_xy * w.z + uv_yz * w.x;
148 } };
149}
150
151} // namespace MayaFlux::Kinesis
Range radius
UVField spherical(const glm::vec3 &centre=glm::vec3(0.0F))
Spherical projection from a centre point.
UVField axial_blend(const glm::vec3 &origin=glm::vec3(0.0F), float scale=1.0F, float blend=4.0F)
axial_blend projection: blend of three axis-aligned planar projections
Tendency< D, float > scale(const Tendency< D, float > &t, float factor)
Uniform scaling of a scalar-output tendency.
Definition Tendency.hpp:97
UVField cartesian(const glm::vec3 &axis=glm::vec3(0.0F, 0.0F, 1.0F), const glm::vec3 &origin=glm::vec3(0.0F), float scale=1.0F)
cartesian projection along a chosen axis
UVField cylindrical(const glm::vec3 &axis=glm::vec3(0.0F, 1.0F, 0.0F), const glm::vec3 &origin=glm::vec3(0.0F), float radius=1.0F, float height=1.0F)
Cylindrical projection around a world axis.
std::function< R(const D &)> fn
Definition Tendency.hpp:23
Typed, composable, stateless callable from domain D to range R.
Definition Tendency.hpp:22