3#include <glm/geometric.hpp>
4#include <glm/gtc/constants.hpp>
14 return std::isfinite(
value.x) && std::isfinite(
value.y) && std::isfinite(
value.z);
24 return static_cast<float>(
state >> 8u) * (2.f / 16777216.f) - 1.f;
28glm::vec3 safeNormal(glm::vec3
value, glm::vec3 fallback) {
33void appendRibbon(TrailMeshSnapshot&
mesh,
const std::vector<glm::vec3>& centers,
34 const glm::vec3& cameraForward,
float widthStart,
float widthEnd,
35 float alphaStart,
float alphaEnd) {
36 if (centers.size() < 2)
return;
37 const std::uint32_t base =
static_cast<std::uint32_t
>(
mesh.vertices.size());
38 for (std::size_t i = 0; i < centers.size(); ++i) {
39 const float t =
static_cast<float>(i) /
static_cast<float>(centers.size() - 1u);
40 const glm::vec3
previous = centers[i == 0 ? 0 : i - 1u];
41 const glm::vec3
next = centers[std::min(i + 1u, centers.size() - 1u)];
42 const glm::vec3
tangent = safeNormal(next -
previous, glm::vec3(0.f, 1.f, 0.f));
43 glm::vec3
side = glm::cross(cameraForward,
tangent);
44 if (glm::length(
side) <= 1e-6f)
side = glm::cross(glm::vec3(0.f, 1.f, 0.f),
tangent);
45 if (glm::length(
side) <= 1e-6f)
side = glm::vec3(1.f, 0.f, 0.f);
47 const float halfWidth = 0.5f * (widthStart + (widthEnd - widthStart) *
t);
48 const float alpha = alphaStart + (alphaEnd - alphaStart) *
t;
52 const std::uint32_t left0 = base +
static_cast<std::uint32_t
>((i - 1u) * 2u);
53 const std::uint32_t right0 = left0 + 1u;
54 const std::uint32_t left1 = base +
static_cast<std::uint32_t
>(i * 2u);
55 const std::uint32_t right1 = left1 + 1u;
56 mesh.indices.insert(
mesh.indices.end(), {left0, right0, left1, left1, right0, right1});
60std::vector<glm::vec3> makeCenters(
const glm::vec3&
start,
const glm::vec3&
end,
61 std::uint32_t segments,
float amplitude,
float cycles,
62 const glm::vec3& cameraForward, RandomStream&
random) {
63 std::vector<glm::vec3> centers;
64 centers.reserve(
static_cast<std::size_t
>(segments) + 1u);
66 glm::vec3
side = safeNormal(glm::cross(cameraForward,
direction), glm::vec3(1.f, 0.f, 0.f));
67 glm::vec3
up = safeNormal(glm::cross(
direction,
side), glm::vec3(0.f, 1.f, 0.f));
68 for (std::uint32_t i = 0; i <= segments; ++i) {
69 const float t =
static_cast<float>(i) /
static_cast<float>(segments);
70 const float envelope = std::sin(glm::pi<float>() *
t);
71 const float wave = std::sin(glm::two_pi<float>() * cycles *
t +
random.signedUnit());
72 const glm::vec3 noise =
74 (amplitude * envelope);
77 centers.front() =
start;
85 const glm::vec3& cameraForward,
97 const float length = glm::length(delta);
104 const glm::vec3
view = safeNormal(cameraForward, glm::vec3(0.f, 0.f, -1.f));
111 glm::vec3
side = safeNormal(glm::cross(
view,
direction), glm::vec3(1.f, 0.f, 0.f));
112 glm::vec3
up = safeNormal(glm::cross(
direction,
side), glm::vec3(0.f, 1.f, 0.f));
113 for (std::uint32_t branch = 0; branch < config.
branchCount; ++branch) {
114 const float t =
static_cast<float>(branch + 1u) /
116 const std::size_t parentIndex = std::min(
117 static_cast<std::size_t
>(std::round(
t * config.
segments)), centers.size() - 1u);
118 const glm::vec3 branchDirection = safeNormal(
120 const glm::vec3 branchStart = centers[parentIndex];
121 const glm::vec3 branchEnd =
123 auto branchCenters = makeCenters(branchStart, branchEnd, config.
branchSegments,
126 appendRibbon(result.
mesh, branchCenters,
view,
graphics::Canvas * previous
RoadLaneDirection direction
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
BeamBuildResult buildBeamEffect(const glm::vec3 &start, const glm::vec3 &end, const glm::vec3 &cameraForward, const BeamEffectConfig &config)
Generate a camera-facing beam or branched lightning ribbon.
Owning beam generation result suitable for MeshEffectRenderer::submitTrail.
Shape and deterministic noise controls for one 3D beam or lightning arc.
std::uint32_t branchCount
std::uint32_t branchSegments