9bool finite(glm::vec3
v) {
return std::isfinite(
v.x) && std::isfinite(
v.y) && std::isfinite(
v.z); }
10bool finite(glm::vec4
v) {
return finite(glm::vec3(
v)) && std::isfinite(
v.w); }
11bool inRange(
float v,
float lo,
float hi) {
return std::isfinite(
v) &&
v >= lo &&
v <= hi; }
12bool validFilter(
unsigned filter) {
return (
filter & 0xffffu) != 0 && (
filter >> 16u) != 0; }
13bool filtersMatch(
unsigned a,
unsigned b) {
14 return ((
a & 0xffffu) & (
b >> 16u)) != 0 && ((
b & 0xffffu) & (
a >> 16u)) != 0;
18 glm::vec4
bary{1.f, 0.f, 0.f, 0.f};
21SimplexPoint closestEdge(glm::vec3
a, glm::vec3
b, glm::vec3
p) {
23 const float denominator = glm::dot(
edge,
edge);
24 const float t = denominator > 1e-12f ? std::clamp(glm::dot(
p -
a,
edge) / denominator, 0.f, 1.f) : 0.f;
25 return {
a +
edge *
t, {1.f -
t,
t, 0.f, 0.f}};
27SimplexPoint closestTriangle(glm::vec3
a, glm::vec3
b, glm::vec3
c, glm::vec3
p) {
29 const float d1 = glm::dot(
ab, ap), d2 = glm::dot(
ac, ap);
30 if (d1 <= 0.f && d2 <= 0.f)
return {
a, {1, 0, 0, 0}};
31 const auto bp =
p -
b;
32 const float d3 = glm::dot(
ab, bp), d4 = glm::dot(
ac, bp);
33 if (d3 >= 0.f && d4 <= d3)
return {
b, {0, 1, 0, 0}};
34 const float vc = d1 * d4 - d3 * d2;
35 if (vc <= 0.f && d1 >= 0.f && d3 <= 0.f) {
36 const float v = d1 / (d1 - d3);
37 return {
a +
ab *
v, {1 -
v,
v, 0, 0}};
39 const auto cp =
p -
c;
40 const float d5 = glm::dot(
ab, cp), d6 = glm::dot(
ac, cp);
41 if (d6 >= 0.f && d5 <= d6)
return {
c, {0, 0, 1, 0}};
42 const float vb = d5 * d2 - d1 * d6;
43 if (vb <= 0.f && d2 >= 0.f && d6 <= 0.f) {
44 const float w = d2 / (d2 - d6);
45 return {
a +
ac *
w, {1 -
w, 0,
w, 0}};
47 const float va = d3 * d6 - d5 * d4;
48 if (va <= 0.f && (d4 - d3) >= 0.f && (d5 - d6) >= 0.f) {
49 const float w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
50 return {
b + (
c -
b) *
w, {0, 1 -
w,
w, 0}};
52 const float sum = va + vb + vc;
53 if (std::abs(sum) <= 1e-12f) {
57 if (da <= db && da <= dc)
return first;
61 const float inverse = 1.f / sum,
v = vb * inverse,
w = vc * inverse;
62 return {
a +
ab *
v +
ac *
w, {1.f -
v -
w,
v,
w, 0}};
64SimplexPoint closestSimplex(
const VolumeFluidSimplex& simplex, std::span<const VolumeFluidParticle> particles,
66 const auto a = particles[simplex.particleIndices[0]].position;
67 if (simplex.size == 1)
return {
a, {1, 0, 0, 0}};
68 const auto b = particles[simplex.particleIndices[1]].position;
69 if (simplex.size == 2)
return closestEdge(
a,
b,
point);
70 return closestTriangle(
a,
b, particles[simplex.particleIndices[2]].position,
point);
72float radiusAlong(
const VolumeFluidSimplex& simplex, std::span<const VolumeFluidParticle> particles, glm::vec4
bary,
75 for (
unsigned i = 0; i < simplex.size; ++i) {
76 const auto& particle = particles[simplex.particleIndices[i]];
78 glm::quat(particle.orientation.w, particle.orientation.x, particle.orientation.y, particle.orientation.z);
83glm::vec3 boxPoint(
const VolumeFluidQueryShape& query, glm::vec3
world, glm::vec3&
normal) {
84 const auto q = glm::normalize(glm::quat(
query.rotation.w,
query.rotation.x,
query.rotation.y,
query.rotation.z));
85 const auto half =
query.size * .5f + glm::vec3(
query.contactOffset),
88 const auto outside = glm::abs(
local) - half;
89 if (glm::all(glm::lessThanEqual(outside, glm::vec3(0.f)))) {
90 const auto gap = half - glm::abs(
local);
97 const float length = glm::length(delta);
101VolumeFluidSimplex implicitPoint(
unsigned index) {
return {{
index, 0, 0}, 1}; }
102bool selected(
const VolumeFluidSimplex& simplex, std::span<const VolumeFluidParticle> particles,
103 const VolumeFluidQueryShape& query) {
104 for (
unsigned i = 0; i < simplex.size; ++i) {
105 const auto&
p = particles[simplex.particleIndices[i]];
106 if ((
query.phaseMask & (1u <<
unsigned(
p.material.phase))) != 0 &&
107 filtersMatch(
query.collisionFilter,
p.collisionFilter))
112VolumeFluidSimplexHit
evaluate(
const VolumeFluidSimplex& simplex,
unsigned simplexIndex,
unsigned queryIndex,
113 std::span<const VolumeFluidParticle> particles,
const VolumeFluidQueryShape& query) {
115 glm::vec3 queryPoint,
normal;
117 convex = closestSimplex(simplex, particles,
query.center);
118 const auto delta = convex.point -
query.center;
119 const float length = glm::length(delta);
123 convex.bary = glm::vec4(1.f /
float(simplex.size));
124 convex.point = glm::vec3(0.f);
125 for (
unsigned i = 0; i < simplex.size; ++i)
126 convex.point += particles[simplex.particleIndices[i]].position * convex.bary[i];
127 for (
int iteration = 0; iteration < 8; ++iteration) {
128 queryPoint = boxPoint(query, convex.point,
normal);
129 convex = closestSimplex(simplex, particles, queryPoint);
131 queryPoint = boxPoint(query, convex.point,
normal);
133 const auto segment =
query.size -
query.center;
134 const float length = glm::length(segment);
136 float along = .5f *
length;
138 for (
int iteration = 0; iteration < 8; ++iteration) {
139 convex = closestSimplex(simplex, particles, queryPoint);
143 const auto delta = convex.point - queryPoint;
148 const float centerDistance = glm::dot(convex.point - queryPoint,
normal);
149 return {convex.bary, queryPoint,
normal, centerDistance - radiusAlong(simplex, particles, convex.bary,
normal),
150 simplexIndex, queryIndex};
155 std::span<const VolumeFluidSimplex> simplexes,
156 std::span<const VolumeFluidQueryShape> queries,
157 unsigned maxHitsPerQuery) {
159 const auto fail = [](
const char*
message) {
160 return Output::failure(
163 const size_t simplexCount = simplexes.empty() ? particles.size() : simplexes.size();
164 if (queries.size() > 256 || maxHitsPerQuery == 0 || maxHitsPerQuery > 4096)
165 return fail(
"Invalid query count or hit limit");
166 if (simplexCount > 65536 || (!queries.empty() && simplexCount > 4000000u / queries.size()))
167 return fail(
"Simplex query exceeds candidate budget");
168 for (
const auto& query : queries) {
170 !
finite(query.size) || !inRange(query.contactOffset, 0.f, 10000.f) ||
171 !inRange(query.maxDistance, 0.f, 10000.f) || query.phaseMask == 0 || (query.phaseMask & ~0x0fu) != 0 ||
172 !validFilter(query.collisionFilter))
173 return fail(
"Invalid simplex query");
175 (!inRange(query.size.x, 0.f, 10000.f) || query.size.y != 0 || query.size.z != 0))
176 return fail(
"Invalid sphere query");
178 (glm::any(glm::lessThan(query.size, glm::vec3(0))) ||
179 glm::any(glm::greaterThan(query.size, glm::vec3(20000))) || !
finite(query.rotation) ||
180 std::abs(glm::dot(query.rotation, query.rotation) - 1.f) > .001f))
181 return fail(
"Invalid box query");
183 return fail(
"Invalid ray query");
187 return a.distance !=
b.distance ?
a.distance <
b.distance :
a.simplexIndex <
b.simplexIndex;
190 std::vector<VolumeFluidSimplexHit>
output;
191 output.reserve(std::min<size_t>(queries.size() *
size_t(maxHitsPerQuery), 4000000));
192 std::vector<VolumeFluidSimplexHit> heap;
193 heap.reserve(maxHitsPerQuery);
194 const Farther farther;
195 for (
size_t qi = 0; qi < queries.size(); ++qi) {
197 for (
size_t si = 0; si < simplexCount; ++si) {
198 const auto implicit = implicitPoint(
unsigned(si));
199 const auto& simplex = simplexes.empty() ? implicit : simplexes[si];
200 if (!selected(simplex, particles, queries[qi]))
continue;
201 auto hit = evaluate(simplex,
unsigned(si),
unsigned(qi), particles, queries[qi]);
202 if (
hit.distance > queries[qi].maxDistance)
continue;
203 if (heap.size() < maxHitsPerQuery) {
205 std::push_heap(heap.begin(), heap.end(), farther);
206 }
else if (
hit.distance < heap.front().distance ||
207 (
hit.distance == heap.front().distance && si < heap.front().simplexIndex)) {
208 std::pop_heap(heap.begin(), heap.end(), farther);
210 std::push_heap(heap.begin(), heap.end(), farther);
213 std::sort_heap(heap.begin(), heap.end(), farther);
216 return Output::success(std::move(
output));