3#include <glm/gtx/norm.hpp>
8#include <unordered_map>
17 bool operator==(
const EdgeKey& rhs)
const {
return a == rhs.a &&
b == rhs.b; }
21 size_t operator()(
const EdgeKey&
edge)
const {
22 const size_t first = std::hash<uint32_t>{}(
edge.a);
23 const size_t second = std::hash<uint32_t>{}(
edge.b);
33EdgeKey edgeKey(uint32_t
a, uint32_t
b) {
return {std::min(
a,
b), std::max(
a,
b)}; }
35glm::vec3 closestPointBarycentric(
const glm::vec3&
p,
const glm::vec3&
a,
const glm::vec3&
b,
37 const glm::vec3
ab =
b -
a;
38 const glm::vec3
ac =
c -
a;
39 const glm::vec3 ap =
p -
a;
40 const float d1 = glm::dot(
ab, ap);
41 const float d2 = glm::dot(
ac, ap);
42 if (d1 <= 0.f && d2 <= 0.f)
return {1.f, 0.f, 0.f};
44 const glm::vec3 bp =
p -
b;
45 const float d3 = glm::dot(
ab, bp);
46 const float d4 = glm::dot(
ac, bp);
47 if (d3 >= 0.f && d4 <= d3)
return {0.f, 1.f, 0.f};
49 const float vc = d1 * d4 - d3 * d2;
50 if (vc <= 0.f && d1 >= 0.f && d3 <= 0.f) {
51 const float v = d1 / (d1 - d3);
52 return {1.f -
v,
v, 0.f};
55 const glm::vec3 cp =
p -
c;
56 const float d5 = glm::dot(
ab, cp);
57 const float d6 = glm::dot(
ac, cp);
58 if (d6 >= 0.f && d5 <= d6)
return {0.f, 0.f, 1.f};
60 const float vb = d5 * d2 - d1 * d6;
61 if (vb <= 0.f && d2 >= 0.f && d6 <= 0.f) {
62 const float w = d2 / (d2 - d6);
63 return {1.f -
w, 0.f,
w};
66 const float va = d3 * d6 - d5 * d4;
67 if (va <= 0.f && d4 - d3 >= 0.f && d5 - d6 >= 0.f) {
68 const float w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
69 return {0.f, 1.f -
w,
w};
72 const float inv = 1.f / (va + vb + vc);
73 const float v = vb * inv;
74 const float w = vc * inv;
75 return {1.f -
v -
w,
v,
w};
81 const std::vector<uint32_t>&
indices,
82 const std::vector<glm::vec2>& uvs) {
83 restPositions_.clear();
84 currentPositions_.clear();
85 previousPositions_.clear();
89 const auto fail = [&]() {
90 restPositions_.clear();
91 currentPositions_.clear();
92 previousPositions_.clear();
99 if (!uvs.empty() && uvs.size() !=
positions.size())
return false;
107 adjacency_.assign(
indices.size() / 3u, glm::ivec3(-1));
109 std::unordered_map<EdgeKey, std::vector<EdgeOwner>, EdgeHash>
edges;
111 const uint32_t base = uint32_t(tri) * 3u;
114 if (glm::length2(glm::cross(
ab,
ac)) < 1e-16f)
return fail();
115 for (
int opposite = 0; opposite < 3; ++opposite) {
116 const uint32_t
a = indices_[base + uint32_t((opposite + 1) % 3)];
117 const uint32_t
b = indices_[base + uint32_t((opposite + 2) % 3)];
118 const EdgeKey
key = edgeKey(
a,
b);
120 owners.push_back({tri, opposite});
121 if (owners.size() > 2u)
return fail();
124 for (
const auto& [
key, owners] :
edges) {
126 if (owners.size() != 2u)
continue;
127 adjacency_[size_t(owners[0].
triangle)][owners[0].oppositeVertex] = owners[1].triangle;
128 adjacency_[size_t(owners[1].
triangle)][owners[1].oppositeVertex] = owners[0].triangle;
135 previousPositions_ = currentPositions_;
136 for (
size_t i = 0; i < restPositions_.size(); ++i)
137 currentPositions_[i] = glm::vec3(transform * glm::vec4(restPositions_[i], 1.f));
141 if (!
isValid() || worldPositions.size() != currentPositions_.size())
return false;
142 previousPositions_ = currentPositions_;
143 currentPositions_ = worldPositions;
150 return !currentPositions_.empty() && currentPositions_.size() == previousPositions_.size() &&
151 indices_.size() >= 3u && indices_.size() % 3u == 0
u;
157 std::vector<glm::uvec3> result;
159 for (
size_t i = 0; i + 2u < indices_.size(); i += 3u)
160 result.emplace_back(indices_[i], indices_[i + 1u], indices_[i + 2u]);
164glm::vec3 FluidSurfaceBinding::triangleNormal(uint32_t
triangle)
const {
165 const uint32_t base =
triangle * 3u;
166 const glm::vec3&
a = currentPositions_[indices_[base]];
167 const glm::vec3&
b = currentPositions_[indices_[base + 1u]];
168 const glm::vec3&
c = currentPositions_[indices_[base + 2u]];
169 const glm::vec3
n = glm::cross(
b -
a,
c -
a);
170 const float n2 = glm::length2(
n);
171 return n2 > 1e-16f ?
n / std::sqrt(n2) : glm::vec3(0.f, 1.f, 0.f);
174glm::vec3 FluidSurfaceBinding::barycentric(uint32_t
triangle,
const glm::vec3&
point)
const {
175 const uint32_t base =
triangle * 3u;
176 const glm::vec3&
a = currentPositions_[indices_[base]];
177 const glm::vec3&
b = currentPositions_[indices_[base + 1u]];
178 const glm::vec3&
c = currentPositions_[indices_[base + 2u]];
179 const glm::vec3 v0 =
b -
a;
180 const glm::vec3 v1 =
c -
a;
181 const glm::vec3 v2 =
point -
a;
182 const float d00 = glm::dot(v0, v0);
183 const float d01 = glm::dot(v0, v1);
184 const float d11 = glm::dot(v1, v1);
185 const float d20 = glm::dot(v2, v0);
186 const float d21 = glm::dot(v2, v1);
187 const float denom = d00 * d11 - d01 * d01;
188 if (std::fabs(denom) < 1e-16f)
return {1.f, 0.f, 0.f};
189 const float v = (d11 * d20 - d01 * d21) / denom;
190 const float w = (d00 * d21 - d01 * d20) / denom;
191 return {1.f -
v -
w,
v,
w};
197 sample.location = location;
199 const uint32_t base = location.
triangle * 3u;
200 for (
int i = 0; i < 3; ++i) {
201 const uint32_t vertex = indices_[base + uint32_t(i)];
202 sample.position += currentPositions_[vertex] *
bary[i];
203 sample.previousPosition += previousPositions_[vertex] *
bary[i];
204 if (!uvs_.empty()) sample.uv += uvs_[vertex] *
bary[i];
206 sample.normal = triangleNormal(location.
triangle);
207 const glm::vec3
edge = currentPositions_[indices_[base + 1u]] - currentPositions_[indices_[base]];
208 const glm::vec3
tangent =
edge - sample.normal * glm::dot(
edge, sample.normal);
209 const float t2 = glm::length2(
tangent);
210 sample.tangent = t2 > 1e-16f ?
tangent / std::sqrt(t2) : glm::vec3(1.f, 0.f, 0.f);
211 sample.bitangent = glm::normalize(glm::cross(sample.normal, sample.tangent));
212 if (dt > 1e-8f) sample.velocity = (sample.position - sample.previousPosition) / dt;
219 float best = std::numeric_limits<float>::max();
222 const uint32_t base = uint32_t(tri) * 3u;
223 const glm::vec3
bary = closestPointBarycentric(worldPosition,
224 currentPositions_[indices_[base]], currentPositions_[indices_[base + 1u]],
225 currentPositions_[indices_[base + 2u]]);
226 const glm::vec3
point = currentPositions_[indices_[base]] *
bary.x +
227 currentPositions_[indices_[base + 1u]] *
bary.y +
228 currentPositions_[indices_[base + 2u]] *
bary.z;
229 const float distance2 = glm::distance2(worldPosition,
point);
230 if (distance2 <
best) {
232 location = {uint32_t(tri),
bary};
235 if (maxDistance >= 0.f &&
best > maxDistance * maxDistance)
return false;
236 outLocation = location;
241 const glm::vec3& worldDisplacement,
242 int maxCrossings)
const {
247 glm::vec3 remaining = worldDisplacement;
248 constexpr float epsilon = 1e-5f;
250 for (
int crossing = 0; crossing <= maxCrossings; ++crossing) {
252 remaining -= sample.normal * glm::dot(remaining, sample.normal);
253 if (glm::length2(remaining) < epsilon * epsilon) {
257 const glm::vec3 targetBary = barycentric(result.
location.
triangle, sample.position + remaining);
259 float mostNegative = -epsilon;
260 for (
int i = 0; i < 3; ++i) {
261 if (targetBary[i] < mostNegative) {
262 mostNegative = targetBary[i];
275 const float to = targetBary[outside];
276 const float t = std::clamp(
from / (
from -
to), 0.f, 1.f);
278 edgeBary[outside] = 0.f;
279 edgeBary = glm::max(edgeBary, glm::vec3(0.f));
280 edgeBary /= edgeBary.x + edgeBary.y + edgeBary.z;
281 remaining *= 1.f -
t;
297 if (crossing == maxCrossings) {
306 if (
triangle >= adjacency_.size() || oppositeVertex < 0 || oppositeVertex > 2)
return -1;
std::vector< std::uint32_t > indices
std::vector< float > positions
HexCoordinates to
Cell the unit walks towards on this segment.
std::map< Cell, int > best
bool build(const std::vector< glm::vec3 > &positions, const std::vector< uint32_t > &indices, const std::vector< glm::vec2 > &uvs={})
Build topology and initialize the current and previous poses.
void setTransform(const glm::mat4 &transform)
Apply a new rigid pose and preserve the old pose for velocity queries.
SurfaceSample evaluate(const SurfaceLocation &location, float dt) const
Evaluate a surface address in the current and previous poses.
bool isValid() const
True when valid.
bool setDeformedPositions(const std::vector< glm::vec3 > &worldPositions)
Supply a new deformed world-space pose and preserve the previous pose.
int adjacentTriangle(uint32_t triangle, int oppositeVertex) const
Adjacent triangle.
int triangleCount() const
Triangle count.
SurfaceWalkResult walkAcrossSurface(const SurfaceLocation &start, const glm::vec3 &worldDisplacement, int maxCrossings=16) const
Move a location by a world-space displacement, crossing triangle edges.
std::vector< glm::uvec3 > triangles() const
Triangles.
void commitPose()
Make the current pose the previous pose, yielding zero surface velocity.
bool project(const glm::vec3 &worldPosition, float maxDistance, SurfaceLocation &outLocation) const
Find the closest material point on the current surface.
GLSL compute kernels for the GPU surface-flow solver.
Stable material-space address of a point on a triangle surface.
Evaluated world-space frame and motion at a surface location.
Result of walking a material point across triangle adjacency.
glm::vec3 remainingDisplacement