841 lines
40 KiB
C++
841 lines
40 KiB
C++
#include <cmath>
|
|
#include <glm/geometric.hpp>
|
|
#include <limits>
|
|
#include "fluids/VolumeFluid.h"
|
|
#include "fluids/VolumeFluidCodec.h"
|
|
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
|
|
using namespace eve::fluids;
|
|
|
|
static eve::Value withoutRecentMaterialFields(eve::Value particle) {
|
|
const auto* current = particle.find("material");
|
|
eve::Value material(eve::Value::Object{});
|
|
for (const auto& key : current->keys())
|
|
if (key != "atmosphericPressure" && key != "smoothing" && key != "rollingContacts" &&
|
|
key != "rollingFriction" && key != "dynamicFriction" && key != "staticFriction" && key != "stickiness" &&
|
|
key != "stickDistance" && key != "frictionCombine" && key != "stickinessCombine")
|
|
material.set(key, *current->find(key));
|
|
eve::Value legacy(eve::Value::Object{});
|
|
for (const auto& key : particle.keys())
|
|
if (key != "angularVelocity") legacy.set(key, *particle.find(key));
|
|
legacy.set("material", std::move(material));
|
|
return legacy;
|
|
}
|
|
static eve::Value withoutSdfColliders(const eve::Value& snapshot) {
|
|
eve::Value legacy(eve::Value::Object{});
|
|
for (const auto& key : snapshot.keys()) {
|
|
if (key == "sdfColliders" || key == "heightFieldColliders" || key == "stitches") continue;
|
|
if (key != "colliders") {
|
|
legacy.set(key, *snapshot.find(key));
|
|
continue;
|
|
}
|
|
eve::Value::Array colliders;
|
|
const auto* source = snapshot.find(key);
|
|
for (size_t i = 0; i < source->arraySize(); ++i) {
|
|
eve::Value collider(eve::Value::Object{});
|
|
for (const auto& field : source->at(i).keys())
|
|
if (field != "isTrigger" && field != "staticFriction" && field != "rollingFriction" &&
|
|
field != "stickiness" && field != "stickDistance" && field != "frictionCombine" &&
|
|
field != "stickinessCombine" && field != "rollingContacts")
|
|
collider.set(field, *source->at(i).find(field));
|
|
colliders.push_back(std::move(collider));
|
|
}
|
|
legacy.set(key, eve::Value(std::move(colliders)));
|
|
}
|
|
return legacy;
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.ownedSdfColliderObstacleContainerAndCodec") {
|
|
VolumeFluidSettings settings;
|
|
settings.gravity = {0, 0, 0};
|
|
settings.iterations = 1;
|
|
settings.minimum = {-1.f, .2f, -.8f};
|
|
settings.maximum = {1.4f, 1.8f, .8f};
|
|
auto created = VolumeFluid::create(settings);
|
|
REQUIRE(created.ok());
|
|
auto solver = std::move(created).takeValue();
|
|
VolumeFluidSdfCollider obstacle;
|
|
obstacle.label = 41;
|
|
obstacle.sdf = MeshSdf::makeSphere({0, 1, 0}, .5f, {24, 24, 24});
|
|
obstacle.position = {.2f, 0, 0};
|
|
obstacle.scale = 1.5f;
|
|
obstacle.friction = 0;
|
|
REQUIRE(solver->setSdfColliders(std::span(&obstacle, 1)).ok());
|
|
VolumeFluidParticle particle;
|
|
particle.position = {.9f, 1.5f, 0};
|
|
particle.material.cohesion = 0;
|
|
particle.material.viscosity = 0;
|
|
REQUIRE(solver->emit(std::span(&particle, 1)).ok());
|
|
REQUIRE(solver->step(1.f / 120.f, 1).ok());
|
|
REQUIRE(solver->particles()[0].position.x > .98f);
|
|
REQUIRE(!solver->contacts().empty());
|
|
REQUIRE(solver->contacts()[0].colliderLabel == 41);
|
|
auto encoded = encodeVolumeFluid(solver->snapshot());
|
|
auto decoded = decodeVolumeFluid(encoded);
|
|
REQUIRE(decoded.ok());
|
|
REQUIRE(decoded.value().version == 20);
|
|
REQUIRE(decoded.value().sdfColliders.size() == 1);
|
|
REQUIRE(decoded.value().sdfColliders[0].sdf.distances == obstacle.sdf.distances);
|
|
|
|
VolumeFluidSdfPose pose;
|
|
pose.label = 41;
|
|
pose.position = {.35f, .1f, 0};
|
|
pose.scale = 1.25f;
|
|
pose.velocity = {.5f, 0, 0};
|
|
pose.angularVelocity = {0, 1, 0};
|
|
const auto originalSamples = solver->snapshot().sdfColliders[0].sdf.distances;
|
|
REQUIRE(solver->updateSdfColliderPoses(std::span(&pose, 1)).ok());
|
|
const auto moved = solver->snapshot().sdfColliders[0];
|
|
REQUIRE(moved.position == pose.position);
|
|
REQUIRE(moved.scale == pose.scale);
|
|
REQUIRE(moved.velocity == pose.velocity);
|
|
REQUIRE(moved.angularVelocity == pose.angularVelocity);
|
|
REQUIRE(moved.sdf.distances == originalSamples);
|
|
auto poseValue = encodeVolumeFluidSdfPose(pose);
|
|
eve::Value::Array poseArray;
|
|
poseArray.push_back(poseValue);
|
|
auto decodedPoses = decodeVolumeFluidSdfPoses(eve::Value(std::move(poseArray)));
|
|
REQUIRE(decodedPoses.ok());
|
|
REQUIRE(decodedPoses.value().size() == 1);
|
|
REQUIRE(decodedPoses.value()[0].position == pose.position);
|
|
auto invalidPose = pose;
|
|
invalidPose.label = 999;
|
|
REQUIRE(!solver->updateSdfColliderPoses(std::span(&invalidPose, 1)).ok());
|
|
REQUIRE(solver->snapshot().sdfColliders[0].position == pose.position);
|
|
|
|
auto invalid = obstacle;
|
|
invalid.sdf.distances.pop_back();
|
|
REQUIRE(!solver->setSdfColliders(std::span(&invalid, 1)).ok());
|
|
REQUIRE(solver->snapshot().sdfColliders[0].label == 41);
|
|
|
|
VolumeFluidSettings enclosedSettings = settings;
|
|
enclosedSettings.minimum = {-.7f, .3f, -.7f};
|
|
enclosedSettings.maximum = {.7f, 1.7f, .7f};
|
|
auto enclosedCreated = VolumeFluid::create(enclosedSettings);
|
|
REQUIRE(enclosedCreated.ok());
|
|
auto enclosed = std::move(enclosedCreated).takeValue();
|
|
VolumeFluidSdfCollider container;
|
|
container.label = 42;
|
|
container.inverted = true;
|
|
container.sdf = MeshSdf::makeSphere({0, 1, 0}, .6f, {32, 32, 32});
|
|
container.friction = 0;
|
|
REQUIRE(enclosed->setSdfColliders(std::span(&container, 1)).ok());
|
|
particle.position = {.62f, 1, 0};
|
|
REQUIRE(enclosed->emit(std::span(&particle, 1)).ok());
|
|
REQUIRE(enclosed->step(1.f / 120.f, 1).ok());
|
|
REQUIRE(enclosed->particles()[0].position.x < .56f);
|
|
container.sdf.origin.x = .5f;
|
|
REQUIRE(!enclosed->setSdfColliders(std::span(&container, 1)).ok());
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.raycastSortedFilteredAndBounded") {
|
|
auto result = VolumeFluid::create({});
|
|
REQUIRE(result.ok());
|
|
auto solver = std::move(result).takeValue();
|
|
std::vector<VolumeFluidParticle> particles(4);
|
|
for (size_t i = 0; i < particles.size(); ++i) {
|
|
particles[i].position = {float(i) * .5f, 1.f, 0.f};
|
|
particles[i].material.phase = VolumeFluidPhase(i);
|
|
}
|
|
REQUIRE(solver->emit(particles).ok());
|
|
auto hits = solver->raycast({-1.f, 1.f, 0.f}, {5.f, 0.f, 0.f}, 2.1f, 4, 0x0f);
|
|
REQUIRE(hits.ok());
|
|
REQUIRE(hits.value().size() == 3);
|
|
REQUIRE(hits.value()[0].particleIndex == 0);
|
|
REQUIRE(std::abs(hits.value()[0].distance - .95f) < 1e-6f);
|
|
REQUIRE(std::abs(hits.value()[0].point.x + .05f) < 1e-6f);
|
|
REQUIRE(hits.value()[0].normal.x == -1.f);
|
|
REQUIRE(hits.value()[1].particleIndex == 1);
|
|
REQUIRE(hits.value()[2].particle.material.phase == VolumeFluidPhase::Granular);
|
|
auto nearest = solver->raycast({-1.f, 1.f, 0.f}, {1.f, 0.f, 0.f}, 10.f, 2, 0x0f);
|
|
REQUIRE(nearest.ok());
|
|
REQUIRE(nearest.value().size() == 2);
|
|
REQUIRE(nearest.value()[0].particleIndex == 0);
|
|
REQUIRE(nearest.value()[1].particleIndex == 1);
|
|
auto granular = solver->raycast({-1.f, 1.f, 0.f}, {1.f, 0.f, 0.f}, 10.f, 4, 1u << 2);
|
|
REQUIRE(granular.ok());
|
|
REQUIRE(granular.value().size() == 1);
|
|
REQUIRE(granular.value()[0].particleIndex == 2);
|
|
auto inside = solver->raycast(particles[0].position, {1.f, 0.f, 0.f}, 0.f, 1, 1);
|
|
REQUIRE(inside.ok());
|
|
REQUIRE(inside.value().size() == 1);
|
|
REQUIRE(inside.value()[0].distance == 0.f);
|
|
REQUIRE(inside.value()[0].normal.x == -1.f);
|
|
REQUIRE(!solver->raycast({}, {}, 1.f).ok());
|
|
REQUIRE(!solver->raycast({}, {0, 0, 1}, -1.f).ok());
|
|
REQUIRE(!solver->raycast({}, {0, 0, 1}, 1.f, 0).ok());
|
|
REQUIRE(!solver->raycast({}, {0, 0, 1}, 1.f, 1, 0x10).ok());
|
|
REQUIRE(solver->particleCount() == 4);
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.raycastRejectsUnboundedSource") {
|
|
VolumeFluidSettings settings;
|
|
settings.capacity = 65537;
|
|
auto result = VolumeFluid::create(settings);
|
|
REQUIRE(result.ok());
|
|
auto solver = std::move(result).takeValue();
|
|
std::vector<VolumeFluidParticle> particles(settings.capacity);
|
|
for (auto& particle : particles) particle.position = {0.f, 1.f, 0.f};
|
|
REQUIRE(solver->emit(particles).ok());
|
|
REQUIRE(!solver->raycast({0.f, 1.f, -1.f}, {0.f, 0.f, 1.f}, 2.f).ok());
|
|
REQUIRE(!solver->querySphere({0.f, 1.f, 0.f}, .1f, 0.f, 1.f).ok());
|
|
REQUIRE(!solver->queryBox({0.f, 1.f, 0.f}, {.1f, .1f, .1f}, {0, 0, 0, 1}, 0.f, 1.f).ok());
|
|
REQUIRE(solver->particleCount() == settings.capacity);
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.sphereDistanceQuerySignedOffsetAndFilter") {
|
|
auto result = VolumeFluid::create({});
|
|
REQUIRE(result.ok());
|
|
auto solver = std::move(result).takeValue();
|
|
std::vector<VolumeFluidParticle> particles(3);
|
|
particles[0].position = {0.f, 1.f, 0.f};
|
|
particles[1].position = {.2f, 1.f, 0.f};
|
|
particles[1].material.phase = VolumeFluidPhase::Gas;
|
|
particles[2].position = {.4f, 1.f, 0.f};
|
|
particles[2].material.phase = VolumeFluidPhase::Granular;
|
|
REQUIRE(solver->emit(particles).ok());
|
|
auto hits = solver->querySphere({0.f, 1.f, 0.f}, .1f, .02f, .1f, 4, 0x0f);
|
|
REQUIRE(hits.ok());
|
|
REQUIRE(hits.value().size() == 2);
|
|
REQUIRE(hits.value()[0].particleIndex == 0);
|
|
REQUIRE(std::abs(hits.value()[0].distance + .17f) < 1e-6f);
|
|
REQUIRE(hits.value()[0].normal.y == 1.f);
|
|
REQUIRE(std::abs(hits.value()[0].queryPoint.y - 1.12f) < 1e-6f);
|
|
REQUIRE(hits.value()[1].particleIndex == 1);
|
|
REQUIRE(std::abs(hits.value()[1].distance - .03f) < 1e-6f);
|
|
REQUIRE(std::abs(hits.value()[1].queryPoint.x - .12f) < 1e-6f);
|
|
REQUIRE(hits.value()[1].normal.x == 1.f);
|
|
auto gas = solver->querySphere({0.f, 1.f, 0.f}, .5f, 0.f, 0.f, 1, 1u << 1);
|
|
REQUIRE(gas.ok());
|
|
REQUIRE(gas.value().size() == 1);
|
|
REQUIRE(gas.value()[0].particle.material.phase == VolumeFluidPhase::Gas);
|
|
hits.value()[0].particle.position.x = 9.f;
|
|
REQUIRE(solver->particles()[0].position.x == 0.f);
|
|
REQUIRE(!solver->querySphere({}, -1.f, 0.f, 1.f).ok());
|
|
REQUIRE(!solver->querySphere({}, 1.f, -1.f, 1.f).ok());
|
|
REQUIRE(!solver->querySphere({}, 1.f, 0.f, -1.f).ok());
|
|
REQUIRE(!solver->querySphere({}, 1.f, 0.f, 1.f, 0, 15).ok());
|
|
REQUIRE(!solver->querySphere({}, 1.f, 0.f, 1.f, 1, 16).ok());
|
|
REQUIRE(solver->particleCount() == 3);
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.orientedBoxDistanceInsideOutsideAndOffset") {
|
|
auto result = VolumeFluid::create({});
|
|
REQUIRE(result.ok());
|
|
auto solver = std::move(result).takeValue();
|
|
std::vector<VolumeFluidParticle> particles(3);
|
|
particles[0].position = {0.f, 1.f, 0.f};
|
|
particles[1].position = {0.f, 1.3f, 0.f};
|
|
particles[1].material.phase = VolumeFluidPhase::Gas;
|
|
particles[2].position = {.3f, 1.f, 0.f};
|
|
particles[2].material.phase = VolumeFluidPhase::Granular;
|
|
REQUIRE(solver->emit(particles).ok());
|
|
const glm::vec4 quarterTurn{0.f, 0.f, .70710678f, .70710678f};
|
|
auto hits = solver->queryBox({0.f, 1.f, 0.f}, {.2f, .05f, .05f}, quarterTurn, .02f, .04f, 4, 15);
|
|
REQUIRE(hits.ok());
|
|
REQUIRE(hits.value().size() == 2);
|
|
REQUIRE(hits.value()[0].particleIndex == 0);
|
|
REQUIRE(std::abs(hits.value()[0].distance + .12f) < 1e-5f);
|
|
REQUIRE(hits.value()[0].normal.x > 0.999f);
|
|
REQUIRE(std::abs(hits.value()[0].queryPoint.x - .07f) < 1e-5f);
|
|
REQUIRE(hits.value()[1].particleIndex == 1);
|
|
REQUIRE(std::abs(hits.value()[1].distance - .03f) < 1e-5f);
|
|
REQUIRE(hits.value()[1].normal.y > 0.999f);
|
|
REQUIRE(std::abs(hits.value()[1].queryPoint.y - 1.22f) < 1e-5f);
|
|
auto gas = solver->queryBox({0.f, 1.f, 0.f}, {.2f, .05f, .05f}, quarterTurn, 0.f, 1.f, 1, 1u << 1);
|
|
REQUIRE(gas.ok());
|
|
REQUIRE(gas.value().size() == 1);
|
|
REQUIRE(gas.value()[0].particleIndex == 1);
|
|
hits.value()[0].particle.position.y = 9.f;
|
|
REQUIRE(solver->particles()[0].position.y == 1.f);
|
|
REQUIRE(!solver->queryBox({}, {-.1f, 1.f, 1.f}, {0, 0, 0, 1}, 0.f, 1.f).ok());
|
|
REQUIRE(!solver->queryBox({}, {1.f, 1.f, 1.f}, {0, 0, 0, 2}, 0.f, 1.f).ok());
|
|
REQUIRE(!solver->queryBox({}, {1.f, 1.f, 1.f}, {0, 0, 0, 1}, -1.f, 1.f).ok());
|
|
REQUIRE(!solver->queryBox({}, {1.f, 1.f, 1.f}, {0, 0, 0, 1}, 0.f, -1.f).ok());
|
|
REQUIRE(!solver->queryBox({}, {1.f, 1.f, 1.f}, {0, 0, 0, 1}, 0.f, 1.f, 0, 15).ok());
|
|
REQUIRE(!solver->queryBox({}, {1.f, 1.f, 1.f}, {0, 0, 0, 1}, 0.f, 1.f, 1, 16).ok());
|
|
REQUIRE(solver->particleCount() == 3);
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.mixedQueryBatchIsGroupedBoundedAndAtomic") {
|
|
auto created = VolumeFluid::create({});
|
|
REQUIRE(created.ok());
|
|
auto solver = std::move(created).takeValue();
|
|
std::vector<VolumeFluidParticle> particles(3);
|
|
particles[0].position = {0.f, 1.f, 0.f};
|
|
particles[1].position = {.2f, 1.f, 0.f};
|
|
particles[1].material.phase = VolumeFluidPhase::Gas;
|
|
particles[2].position = {.4f, 1.f, 0.f};
|
|
particles[2].material.phase = VolumeFluidPhase::Granular;
|
|
REQUIRE(solver->emit(particles).ok());
|
|
std::vector<VolumeFluidQueryShape> queries(3);
|
|
queries[0].center = {0.f, 1.f, 0.f};
|
|
queries[0].size = {.1f, 0.f, 0.f};
|
|
queries[0].maxDistance = .1f;
|
|
queries[1].type = VolumeFluidQueryType::Box;
|
|
queries[1].center = {.2f, 1.f, 0.f};
|
|
queries[1].size = {.1f, .1f, .1f};
|
|
queries[1].maxDistance = 0.f;
|
|
queries[1].phaseMask = 1u << 1;
|
|
queries[2].type = VolumeFluidQueryType::Ray;
|
|
queries[2].center = {-1.f, 1.f, 0.f};
|
|
queries[2].size = {1.f, 1.f, 0.f};
|
|
queries[2].contactOffset = .5f;
|
|
queries[2].maxDistance = 99.f;
|
|
auto hits = solver->queryBatch(queries, 2);
|
|
REQUIRE(hits.ok());
|
|
REQUIRE(hits.value().size() == 5);
|
|
REQUIRE(hits.value()[0].queryIndex == 0);
|
|
REQUIRE(hits.value()[0].particleIndex == 0);
|
|
REQUIRE(hits.value()[1].queryIndex == 0);
|
|
REQUIRE(hits.value()[1].particleIndex == 1);
|
|
REQUIRE(hits.value()[2].queryIndex == 1);
|
|
REQUIRE(hits.value()[2].particleIndex == 1);
|
|
REQUIRE(hits.value()[3].queryIndex == 2);
|
|
REQUIRE(hits.value()[3].particleIndex == 0);
|
|
REQUIRE(hits.value()[4].queryIndex == 2);
|
|
REQUIRE(hits.value()[4].particleIndex == 1);
|
|
REQUIRE(std::abs(hits.value()[3].distance + .5f) < 1e-6f);
|
|
REQUIRE(std::abs(hits.value()[3].queryPoint.x - .45f) < 1e-6f);
|
|
hits.value()[0].particle.position.x = 9.f;
|
|
REQUIRE(solver->particles()[0].position.x == 0.f);
|
|
queries[0].size.y = 1.f;
|
|
REQUIRE(!solver->queryBatch(queries, 2).ok());
|
|
REQUIRE(solver->particleCount() == 3);
|
|
REQUIRE(!solver->queryBatch({}, 0).ok());
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.mixedQueryBatchRejectsCandidateExplosion") {
|
|
VolumeFluidSettings settings;
|
|
settings.capacity = 20000;
|
|
auto created = VolumeFluid::create(settings);
|
|
REQUIRE(created.ok());
|
|
auto solver = std::move(created).takeValue();
|
|
std::vector<VolumeFluidParticle> particles(settings.capacity);
|
|
for (auto& particle : particles) particle.position = {0.f, 1.f, 0.f};
|
|
REQUIRE(solver->emit(particles).ok());
|
|
std::vector<VolumeFluidQueryShape> queries(201);
|
|
for (auto& query : queries) query.size = {0.f, 0.f, 0.f};
|
|
REQUIRE(!solver->queryBatch(queries, 1).ok());
|
|
REQUIRE(solver->particleCount() == settings.capacity);
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.queryColorsAreAtomicAndLaterOverlapsWin") {
|
|
auto created = VolumeFluid::create({});
|
|
REQUIRE(created.ok());
|
|
auto solver = std::move(created).takeValue();
|
|
VolumeFluidParticle particles[3];
|
|
particles[0].position = {-.2f, 1.f, 0.f};
|
|
particles[1].position = {0.f, 1.f, 0.f};
|
|
particles[2].position = {.2f, 1.f, 0.f};
|
|
REQUIRE(solver->emit(particles).ok());
|
|
VolumeFluidQueryShape queries[2];
|
|
queries[0].type = VolumeFluidQueryType::Box;
|
|
queries[0].center = {-.1f, 1.f, 0.f};
|
|
queries[0].size = {.3f, .2f, .2f};
|
|
queries[1].type = VolumeFluidQueryType::Box;
|
|
queries[1].center = {.1f, 1.f, 0.f};
|
|
queries[1].size = {.3f, .2f, .2f};
|
|
const glm::vec4 colors[] = {{1.f, 0.f, 0.f, 1.f}, {1.f, 1.f, 0.f, 1.f}};
|
|
auto counts = solver->applyQueryColors(queries, colors, {0.f, 1.f, 1.f, 1.f}, 3);
|
|
REQUIRE(counts.ok());
|
|
REQUIRE(counts.value().size() == 2);
|
|
REQUIRE(counts.value()[0] == 2);
|
|
REQUIRE(counts.value()[1] == 2);
|
|
const auto colored = solver->particles();
|
|
REQUIRE(colored[0].color == colors[0]);
|
|
REQUIRE(colored[1].color == colors[1]);
|
|
REQUIRE(colored[2].color == colors[1]);
|
|
queries[1].rotation = {0.f, 0.f, 0.f, 2.f};
|
|
REQUIRE(!solver->applyQueryColors(queries, colors, {0.f, 0.f, 1.f, 1.f}, 3).ok());
|
|
REQUIRE(solver->particles()[0].color == colors[0]);
|
|
const glm::vec4 invalid[] = {{2.f, 0.f, 0.f, 1.f}, {1.f, 1.f, 0.f, 1.f}};
|
|
queries[1].rotation = {0.f, 0.f, 0.f, 1.f};
|
|
REQUIRE(!solver->applyQueryColors(queries, invalid, {0.f, 1.f, 1.f, 1.f}, 3).ok());
|
|
REQUIRE(solver->particles()[1].color == colors[1]);
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.persistentQueryColorsPreserveOutsideAndAreAtomic") {
|
|
auto created = VolumeFluid::create({});
|
|
REQUIRE(created.ok());
|
|
auto solver = std::move(created).takeValue();
|
|
VolumeFluidParticle particles[3];
|
|
particles[0].position = {-.2f, 1.f, 0.f};
|
|
particles[1].position = {0.f, 1.f, 0.f};
|
|
particles[2].position = {.2f, 1.f, 0.f};
|
|
particles[0].color = {0.f, 0.f, 1.f, 1.f};
|
|
particles[1].color = {0.f, 1.f, 0.f, 1.f};
|
|
particles[2].color = {0.f, 1.f, 1.f, 1.f};
|
|
REQUIRE(solver->emit(particles).ok());
|
|
VolumeFluidQueryShape queries[2];
|
|
queries[0].type = VolumeFluidQueryType::Box;
|
|
queries[0].center = {-.2f, 1.f, 0.f};
|
|
queries[0].size = {.1f, .1f, .1f};
|
|
queries[1].type = VolumeFluidQueryType::Box;
|
|
queries[1].center = {0.f, 1.f, 0.f};
|
|
queries[1].size = {.1f, .1f, .1f};
|
|
const glm::vec4 colors[] = {{1.f, 0.f, 0.f, 1.f}, {1.f, 1.f, 0.f, 1.f}};
|
|
auto counts = solver->applyQueryColorsPreservingOutside(queries, colors, 3);
|
|
const std::vector<unsigned> expectedCounts{1, 1};
|
|
REQUIRE(counts.ok());
|
|
REQUIRE(counts.value() == expectedCounts);
|
|
REQUIRE(solver->particles()[0].color == colors[0]);
|
|
REQUIRE(solver->particles()[1].color == colors[1]);
|
|
REQUIRE(solver->particles()[2].color == particles[2].color);
|
|
queries[1].rotation = {0.f, 0.f, 0.f, 2.f};
|
|
REQUIRE(!solver->applyQueryColorsPreservingOutside(queries, colors, 3).ok());
|
|
REQUIRE(solver->particles()[0].color == colors[0]);
|
|
const glm::vec4 invalid[] = {{2.f, 0.f, 0.f, 1.f}, {1.f, 1.f, 0.f, 1.f}};
|
|
queries[1].rotation = {0.f, 0.f, 0.f, 1.f};
|
|
REQUIRE(!solver->applyQueryColorsPreservingOutside(queries, invalid, 3).ok());
|
|
REQUIRE(solver->particles()[1].color == colors[1]);
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.mixedQueryBatchCodecIsStrictAndOwning") {
|
|
eve::Value query(eve::Value::Object{});
|
|
query.set("type", 0);
|
|
query.set("center", eve::Value(eve::Value::Array{0.f, 1.f, 0.f}));
|
|
query.set("size", eve::Value(eve::Value::Array{.1f, 0.f, 0.f}));
|
|
query.set("rotation", eve::Value(eve::Value::Array{0.f, 0.f, 0.f, 1.f}));
|
|
query.set("contactOffset", 0.f);
|
|
query.set("maxDistance", 1.f);
|
|
query.set("phaseMask", 15);
|
|
query.set("collisionFilter", int64_t(0xffff0001u));
|
|
auto decoded = decodeVolumeFluidQueries(eve::Value(eve::Value::Array{query}));
|
|
REQUIRE(decoded.ok());
|
|
REQUIRE(decoded.value().size() == 1);
|
|
REQUIRE(decoded.value()[0].size.x == .1f);
|
|
query.set("unknown", 1);
|
|
REQUIRE(!decodeVolumeFluidQueries(eve::Value(eve::Value::Array{query})).ok());
|
|
REQUIRE(!decodeVolumeFluidQueries(eve::Value(eve::Value::Array(257, query))).ok());
|
|
auto colors =
|
|
decodeVolumeFluidColors(eve::Value(eve::Value::Array{eve::Value(eve::Value::Array{1.f, 0.f, 0.f, 1.f})}));
|
|
REQUIRE(colors.ok());
|
|
REQUIRE(colors.value()[0].x == 1.f);
|
|
REQUIRE(!decodeVolumeFluidColors(eve::Value(eve::Value::Array{eve::Value(eve::Value::Array{1.f, 0.f, 0.f})})).ok());
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.queryCollisionFiltersMatchBothDirections") {
|
|
auto created = VolumeFluid::create({});
|
|
REQUIRE(created.ok());
|
|
auto solver = std::move(created).takeValue();
|
|
VolumeFluidParticle a, b;
|
|
a.position = {0.f, 1.f, 0.f};
|
|
b.position = {.2f, 1.f, 0.f};
|
|
a.collisionFilter = 0x00020001u;
|
|
b.collisionFilter = 0x00010002u;
|
|
const VolumeFluidParticle particles[] = {a, b};
|
|
REQUIRE(solver->emit(particles).ok());
|
|
auto sphere = solver->querySphere({0.f, 1.f, 0.f}, .5f, 0.f, 0.f, 4, 15, 0x00010002u);
|
|
REQUIRE(sphere.ok());
|
|
REQUIRE(sphere.value().size() == 1);
|
|
REQUIRE(sphere.value()[0].particleIndex == 0);
|
|
VolumeFluidQueryShape query;
|
|
query.center = {0.f, 1.f, 0.f};
|
|
query.size = {.5f, 0.f, 0.f};
|
|
query.collisionFilter = 0x00010002u;
|
|
auto batch = solver->queryBatch(std::span(&query, 1), 4);
|
|
REQUIRE(batch.ok());
|
|
REQUIRE(batch.value().size() == 1);
|
|
REQUIRE(batch.value()[0].particleIndex == 0);
|
|
query.collisionFilter = 0;
|
|
REQUIRE(!solver->queryBatch(std::span(&query, 1), 4).ok());
|
|
VolumeFluidParticle invalid = a;
|
|
invalid.position.y = 1.2f;
|
|
invalid.collisionFilter = 0;
|
|
REQUIRE(!solver->emit(std::span(&invalid, 1)).ok());
|
|
REQUIRE(solver->particleCount() == 2);
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.particleInteractionsRequireReciprocalCollisionFilters") {
|
|
VolumeFluidSettings settings;
|
|
settings.gravity = {0, 0, 0};
|
|
settings.iterations = 2;
|
|
auto make = [&](unsigned secondFilter) {
|
|
auto created = VolumeFluid::create(settings);
|
|
REQUIRE(created.ok());
|
|
auto solver = std::move(created).takeValue();
|
|
VolumeFluidParticle particles[2];
|
|
particles[0].position = {0, 1, 0};
|
|
particles[1].position = {0, 1, 0};
|
|
particles[0].collisionFilter = 0x00020001u;
|
|
particles[1].collisionFilter = secondFilter;
|
|
particles[0].actorGroup = 1;
|
|
particles[1].actorGroup = 2;
|
|
REQUIRE(solver->emit(particles).ok());
|
|
REQUIRE(solver->step(1.f / 120.f, 1).ok());
|
|
return solver;
|
|
};
|
|
auto oneWay = make(0x00020002u);
|
|
const auto isolated = oneWay->particles();
|
|
REQUIRE(glm::length(isolated[0].position - isolated[1].position) == 0.f);
|
|
REQUIRE(isolated[0].collisionFilter == 0x00020001u);
|
|
REQUIRE(isolated[1].collisionFilter == 0x00020002u);
|
|
auto reciprocal = make(0x00010002u);
|
|
const auto interacting = reciprocal->particles();
|
|
REQUIRE(glm::length(interacting[0].position - interacting[1].position) > .001f);
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.actorGroupsRequireMutualSelfCollision") {
|
|
VolumeFluidSettings settings;
|
|
settings.gravity = {0, 0, 0};
|
|
settings.iterations = 2;
|
|
auto make = [&](unsigned secondGroup, bool firstSelf, bool secondSelf) {
|
|
auto created = VolumeFluid::create(settings);
|
|
REQUIRE(created.ok());
|
|
auto solver = std::move(created).takeValue();
|
|
VolumeFluidParticle particles[2];
|
|
particles[0].position = {0, 1, 0};
|
|
particles[1].position = {0, 1, 0};
|
|
particles[0].collisionFilter = 0x00020001u;
|
|
particles[1].collisionFilter = 0x00020002u;
|
|
particles[0].actorGroup = 7;
|
|
particles[1].actorGroup = secondGroup;
|
|
particles[0].selfCollide = firstSelf;
|
|
particles[1].selfCollide = secondSelf;
|
|
REQUIRE(solver->emit(particles).ok());
|
|
REQUIRE(solver->step(1.f / 120.f, 1).ok());
|
|
return solver;
|
|
};
|
|
REQUIRE(glm::length(make(7, true, true)->particles()[0].position - glm::vec3(0, 1, 0)) > .001f);
|
|
REQUIRE(make(7, false, true)->particles()[0].position == glm::vec3(0, 1, 0));
|
|
REQUIRE(make(7, true, false)->particles()[0].position == glm::vec3(0, 1, 0));
|
|
REQUIRE(make(8, true, true)->particles()[0].position == glm::vec3(0, 1, 0));
|
|
|
|
auto current = make(7, true, true)->snapshot();
|
|
auto encoded = encodeVolumeFluid(current);
|
|
eve::Value::Array particles;
|
|
for (size_t i = 0; i < encoded.find("particles")->arraySize(); ++i) {
|
|
const auto& source = encoded.find("particles")->at(i);
|
|
eve::Value legacy(eve::Value::Object{});
|
|
for (const auto& key : source.keys())
|
|
if (key != "actorGroup" && key != "selfCollide") legacy.set(key, *source.find(key));
|
|
particles.push_back(withoutRecentMaterialFields(std::move(legacy)));
|
|
}
|
|
encoded.set("particles", eve::Value(std::move(particles)));
|
|
encoded = withoutSdfColliders(encoded);
|
|
encoded.set("version", 8);
|
|
auto migrated = decodeVolumeFluid(encoded);
|
|
REQUIRE(migrated.ok());
|
|
REQUIRE(migrated.value().version == 20);
|
|
REQUIRE(migrated.value().particles[0].actorGroup == 0);
|
|
REQUIRE(migrated.value().particles[0].selfCollide);
|
|
VolumeFluidParticle invalid;
|
|
invalid.actorGroup = 0x01000000u;
|
|
auto created = VolumeFluid::create(settings);
|
|
REQUIRE(created.ok());
|
|
REQUIRE(!created.value()->emit(std::span(&invalid, 1)).ok());
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.colliderInteractionsRequireReciprocalCollisionFilters") {
|
|
VolumeFluidSettings settings;
|
|
settings.gravity = {0, 0, 0};
|
|
settings.iterations = 1;
|
|
auto make = [&](unsigned colliderFilter) {
|
|
auto created = VolumeFluid::create(settings);
|
|
REQUIRE(created.ok());
|
|
auto solver = std::move(created).takeValue();
|
|
VolumeFluidParticle particle;
|
|
particle.position = {0, 1, 0};
|
|
particle.collisionFilter = 0x00020001u;
|
|
VolumeFluidCollider collider;
|
|
collider.center = {0, 1, 0};
|
|
collider.collisionFilter = colliderFilter;
|
|
REQUIRE(solver->setColliders(std::span(&collider, 1)).ok());
|
|
REQUIRE(solver->emit(std::span(&particle, 1)).ok());
|
|
REQUIRE(solver->step(1.f / 120.f, 1).ok());
|
|
return solver;
|
|
};
|
|
auto oneWay = make(0x00020002u);
|
|
REQUIRE(oneWay->particles()[0].position == glm::vec3(0, 1, 0));
|
|
REQUIRE(oneWay->contacts().empty());
|
|
auto reciprocal = make(0x00010002u);
|
|
REQUIRE(glm::length(reciprocal->particles()[0].position - glm::vec3(0, 1, 0)) > .001f);
|
|
REQUIRE(reciprocal->contacts().size() == 1);
|
|
auto before = reciprocal->snapshot();
|
|
auto invalid = before.colliders[0];
|
|
invalid.collisionFilter = 0;
|
|
REQUIRE(!reciprocal->setColliders(std::span(&invalid, 1)).ok());
|
|
REQUIRE(reciprocal->snapshot().colliders[0].collisionFilter == 0x00010002u);
|
|
|
|
auto encoded = encodeVolumeFluid(before);
|
|
const auto& current = encoded.find("colliders")->at(0);
|
|
eve::Value oldCollider(eve::Value::Object{});
|
|
for (const auto& key : current.keys())
|
|
if (key != "collisionFilter" && key != "isTrigger") oldCollider.set(key, *current.find(key));
|
|
const auto& currentParticle = encoded.find("particles")->at(0);
|
|
eve::Value oldParticle(eve::Value::Object{});
|
|
for (const auto& key : currentParticle.keys())
|
|
if (key != "actorGroup" && key != "selfCollide") oldParticle.set(key, *currentParticle.find(key));
|
|
encoded.set("particles", eve::Value(eve::Value::Array{withoutRecentMaterialFields(std::move(oldParticle))}));
|
|
encoded.set("colliders", eve::Value(eve::Value::Array{oldCollider}));
|
|
encoded = withoutSdfColliders(encoded);
|
|
encoded.set("version", 7);
|
|
auto migrated = decodeVolumeFluid(encoded);
|
|
REQUIRE(migrated.ok());
|
|
REQUIRE(migrated.value().version == 20);
|
|
REQUIRE(migrated.value().colliders[0].collisionFilter == 0xffff0001u);
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.orientedEllipsoidQueriesAndCanonicalDefaults") {
|
|
VolumeFluidSettings settings;
|
|
settings.spacing = .2f;
|
|
settings.gravity = {0, 0, 0};
|
|
auto created = VolumeFluid::create(settings);
|
|
REQUIRE(created.ok());
|
|
auto solver = std::move(created).takeValue();
|
|
VolumeFluidParticle particle;
|
|
particle.position = {0.f, 1.f, 0.f};
|
|
REQUIRE(solver->emit(std::span(&particle, 1)).ok());
|
|
REQUIRE(solver->particles()[0].radii == glm::vec3(.1f));
|
|
solver->clear();
|
|
particle.radii = {.2f, .05f, .05f};
|
|
particle.orientation = {0.f, 0.f, .70710678f, .70710678f};
|
|
REQUIRE(solver->emit(std::span(&particle, 1)).ok());
|
|
auto vertical = solver->querySphere({0.f, 1.3f, 0.f}, 0.f, 0.f, 1.f, 1);
|
|
REQUIRE(vertical.ok());
|
|
REQUIRE(vertical.value().size() == 1);
|
|
REQUIRE(std::abs(vertical.value()[0].distance - .1f) < 1e-5f);
|
|
auto horizontal = solver->querySphere({.3f, 1.f, 0.f}, 0.f, 0.f, 1.f, 1);
|
|
REQUIRE(horizontal.ok());
|
|
REQUIRE(std::abs(horizontal.value()[0].distance - .25f) < 1e-5f);
|
|
auto ray = solver->raycast({0.f, .7f, 0.f}, {0.f, 1.f, 0.f}, 1.f, 1);
|
|
REQUIRE(ray.ok());
|
|
REQUIRE(ray.value().size() == 1);
|
|
REQUIRE(std::abs(ray.value()[0].distance - .1f) < 1e-5f);
|
|
REQUIRE(ray.value()[0].normal.y < -.999f);
|
|
auto state = solver->snapshot();
|
|
REQUIRE(state.version == 20);
|
|
REQUIRE(state.particles[0].radii == particle.radii);
|
|
VolumeFluidParticle invalid = particle;
|
|
invalid.radii = {.1f, 0.f, .1f};
|
|
REQUIRE(!solver->emit(std::span(&invalid, 1)).ok());
|
|
invalid = particle;
|
|
invalid.orientation = {0, 0, 0, 2};
|
|
REQUIRE(!solver->emit(std::span(&invalid, 1)).ok());
|
|
REQUIRE(solver->particleCount() == 1);
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.solidAttachmentRollbackAndLifetimeRemap") {
|
|
VolumeFluidSettings settings;
|
|
settings.gravity = {0, 0, 0};
|
|
auto created = VolumeFluid::create(settings);
|
|
REQUIRE(created.ok());
|
|
auto solver = std::move(created).takeValue();
|
|
VolumeFluidParticle expired, live, shortSolid;
|
|
expired.position = {1, 1, 0};
|
|
expired.life = .001f;
|
|
live.position = {0, 1.25f, 0};
|
|
shortSolid.position = {.25f, 1, 0};
|
|
shortSolid.life = .001f;
|
|
const VolumeFluidParticle particles[] = {expired, live, shortSolid};
|
|
VolumeFluidCollider c;
|
|
c.label = 17;
|
|
c.solidify = true;
|
|
REQUIRE(solver->setColliders(std::span(&c, 1)).ok());
|
|
REQUIRE(solver->emit(particles).ok());
|
|
REQUIRE(solver->step(1.f / 60.f).ok());
|
|
const auto before = solver->snapshot();
|
|
REQUIRE(before.particles.size() == 1);
|
|
REQUIRE(before.attachments.size() == 1);
|
|
REQUIRE(before.attachments[0].particleIndex == 0);
|
|
VolumeFluidThermalRule badRule;
|
|
badRule.colliderLabel = 17;
|
|
REQUIRE(!solver->stepWithColliders(1.f / 60.f, 4, {}, std::span(&badRule, 1)).ok());
|
|
REQUIRE(solver->snapshot().attachments.size() == 1);
|
|
REQUIRE(solver->snapshot().colliders[0].label == 17);
|
|
c.center.x = 10;
|
|
REQUIRE(!solver->stepWithColliders(1.f / 60.f, 4, std::span(&c, 1)).ok());
|
|
REQUIRE(solver->snapshot().colliders[0].center.x == 0.f);
|
|
REQUIRE(glm::length(solver->particles()[0].position - before.particles[0].position) == 0.f);
|
|
auto invalid = before;
|
|
invalid.attachments[0].particleIndex = 1;
|
|
REQUIRE(!solver->restore(invalid).ok());
|
|
invalid = before;
|
|
invalid.attachments[0].colliderLabel = 99;
|
|
REQUIRE(!solver->restore(invalid).ok());
|
|
invalid = before;
|
|
invalid.attachments.push_back(invalid.attachments[0]);
|
|
REQUIRE(!solver->restore(invalid).ok());
|
|
REQUIRE(solver->snapshot().attachments.size() == 1);
|
|
solver->clear();
|
|
REQUIRE(solver->snapshot().attachments.empty());
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.versionTwoMigratesWorldFixedSolids") {
|
|
VolumeFluidSnapshot original;
|
|
VolumeFluidParticle p;
|
|
p.position = {0, 1, 0};
|
|
p.material.phase = VolumeFluidPhase::Solid;
|
|
original.particles.push_back(p);
|
|
auto current = encodeVolumeFluid(original);
|
|
const auto& currentParticle = current.find("particles")->at(0);
|
|
eve::Value oldParticle(eve::Value::Object{});
|
|
for (const auto& key : currentParticle.keys())
|
|
if (key != "collisionFilter" && key != "radii" && key != "orientation" && key != "actorGroup" &&
|
|
key != "selfCollide")
|
|
oldParticle.set(key, *currentParticle.find(key));
|
|
current.set("particles", eve::Value(eve::Value::Array{withoutRecentMaterialFields(std::move(oldParticle))}));
|
|
eve::Value old(eve::Value::Object{});
|
|
for (const auto& key : current.keys())
|
|
if (key != "attachments" && key != "simplexes") old.set(key, *current.find(key));
|
|
old = withoutSdfColliders(old);
|
|
old.set("version", 2);
|
|
auto decoded = decodeVolumeFluid(old);
|
|
REQUIRE(decoded.ok());
|
|
REQUIRE(decoded.value().version == 20);
|
|
REQUIRE(decoded.value().attachments.empty());
|
|
REQUIRE(decoded.value().particles[0].material.phase == VolumeFluidPhase::Solid);
|
|
REQUIRE(decoded.value().particles[0].collisionFilter == 0xffff0001u);
|
|
REQUIRE(decoded.value().particles[0].radii == glm::vec3(.05f));
|
|
old.set("attachments", eve::Value(eve::Value::Array{}));
|
|
REQUIRE(!decodeVolumeFluid(old).ok());
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.outOfBoundsSolidificationRollsBackAttachment") {
|
|
auto created = VolumeFluid::create({});
|
|
REQUIRE(created.ok());
|
|
auto solver = std::move(created).takeValue();
|
|
VolumeFluidCollider c;
|
|
c.solidify = true;
|
|
c.center = {0, 2.9f, 0};
|
|
VolumeFluidParticle p;
|
|
p.position = {0, 2.94f, 0};
|
|
REQUIRE(solver->setColliders(std::span(&c, 1)).ok());
|
|
REQUIRE(solver->emit(std::span(&p, 1)).ok());
|
|
REQUIRE(!solver->step(1.f / 60.f).ok());
|
|
REQUIRE(solver->snapshot().attachments.empty());
|
|
REQUIRE(solver->contacts().empty());
|
|
REQUIRE(solver->particles()[0].material.phase == VolumeFluidPhase::Liquid);
|
|
REQUIRE(solver->particles()[0].position.y == p.position.y);
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.solidificationPropagatesOneContactWave") {
|
|
for (unsigned substeps : {1u, 4u})
|
|
for (unsigned iterations : {1u, 10u}) {
|
|
VolumeFluidSnapshot input;
|
|
input.settings.gravity = {0, 0, 0};
|
|
input.settings.iterations = iterations;
|
|
VolumeFluidCollider collider;
|
|
collider.label = 17;
|
|
collider.radius = .01f;
|
|
collider.solidify = true;
|
|
input.colliders.push_back(collider);
|
|
for (unsigned i = 0; i < 4; ++i) {
|
|
VolumeFluidParticle p;
|
|
p.position = {float(i) * .1f, 1, 0};
|
|
p.material.cohesion = 0;
|
|
p.material.viscosity = 0;
|
|
if (i == 0) p.material.phase = VolumeFluidPhase::Solid;
|
|
input.particles.push_back(p);
|
|
}
|
|
input.attachments.push_back({0, 17, {0, 0, 0}});
|
|
auto created = VolumeFluid::create(input.settings);
|
|
REQUIRE(created.ok());
|
|
auto solver = std::move(created).takeValue();
|
|
REQUIRE(solver->restore(input).ok());
|
|
for (unsigned wave = 1; wave <= 3; ++wave) {
|
|
REQUIRE(solver->step(1.f / 120.f, substeps).ok());
|
|
auto snapshot = solver->snapshot();
|
|
REQUIRE(snapshot.attachments.size() == wave + 1);
|
|
for (unsigned i = 0; i < 4; ++i)
|
|
REQUIRE((snapshot.particles[i].material.phase == VolumeFluidPhase::Solid) == (i <= wave));
|
|
}
|
|
collider.center.x = .1f;
|
|
REQUIRE(solver->stepWithColliders(1.f / 120.f, 1, std::span(&collider, 1)).ok());
|
|
for (unsigned i = 0; i < 4; ++i)
|
|
REQUIRE(std::abs(solver->particles()[i].position.x - (float(i) * .1f + .1f)) < 1e-5f);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.versionFiveMigratesAttachmentLocalOrientation") {
|
|
VolumeFluidSnapshot current;
|
|
VolumeFluidParticle particle;
|
|
particle.position = {0, 1, 0};
|
|
particle.material.phase = VolumeFluidPhase::Solid;
|
|
particle.radii = glm::vec3(.05f);
|
|
particle.orientation = {0, 0, .70710678f, .70710678f};
|
|
current.particles.push_back(particle);
|
|
VolumeFluidCollider collider;
|
|
collider.label = 41;
|
|
collider.rotation = {0, 0, .38268343f, .92387953f};
|
|
current.colliders.push_back(collider);
|
|
current.attachments.push_back({0, 41, {0, 1, 0}, {0, 0, .38268343f, .92387953f}});
|
|
auto encoded = encodeVolumeFluid(current);
|
|
auto attachment = encoded.find("attachments")->at(0);
|
|
eve::Value legacyAttachment(eve::Value::Object{});
|
|
for (const auto& key : attachment.keys())
|
|
if (key != "localOrientation" && key != "constrainOrientation" && key != "dynamic" && key != "compliance" &&
|
|
key != "breakThreshold")
|
|
legacyAttachment.set(key, *attachment.find(key));
|
|
encoded.set("attachments", eve::Value(eve::Value::Array{legacyAttachment}));
|
|
encoded = withoutSdfColliders(encoded);
|
|
encoded.set("version", 5);
|
|
const auto& encodedParticle = encoded.find("particles")->at(0);
|
|
eve::Value legacyParticle(eve::Value::Object{});
|
|
for (const auto& key : encodedParticle.keys())
|
|
if (key != "actorGroup" && key != "selfCollide") legacyParticle.set(key, *encodedParticle.find(key));
|
|
encoded.set("particles", eve::Value(eve::Value::Array{withoutRecentMaterialFields(std::move(legacyParticle))}));
|
|
const auto& encodedCollider = encoded.find("colliders")->at(0);
|
|
eve::Value legacyCollider(eve::Value::Object{});
|
|
for (const auto& key : encodedCollider.keys())
|
|
if (key != "collisionFilter") legacyCollider.set(key, *encodedCollider.find(key));
|
|
encoded.set("colliders", eve::Value(eve::Value::Array{legacyCollider}));
|
|
eve::Value legacy(eve::Value::Object{});
|
|
for (const auto& key : encoded.keys())
|
|
if (key != "simplexes") legacy.set(key, *encoded.find(key));
|
|
auto decoded = decodeVolumeFluid(legacy);
|
|
REQUIRE(decoded.ok());
|
|
REQUIRE(decoded.value().version == 20);
|
|
REQUIRE(std::abs(decoded.value().attachments[0].localOrientation.z - .38268343f) < 1e-5f);
|
|
REQUIRE(std::abs(decoded.value().attachments[0].localOrientation.w - .92387953f) < 1e-5f);
|
|
REQUIRE(decoded.value().attachments[0].constrainOrientation);
|
|
}
|
|
|
|
TEST_CASE("fluids.volume.simplexQueriesReturnBarycentricPointEdgeAndTriangleResults") {
|
|
VolumeFluidSettings settings;
|
|
settings.gravity = {0, 0, 0};
|
|
auto created = VolumeFluid::create(settings);
|
|
REQUIRE(created.ok());
|
|
auto solver = std::move(created).takeValue();
|
|
VolumeFluidParticle particles[3];
|
|
particles[0].position = {-1, 1, 0};
|
|
particles[1].position = {1, 1, 0};
|
|
particles[2].position = {0, 2, 0};
|
|
REQUIRE(solver->emit(particles).ok());
|
|
VolumeFluidSimplex triangle{{0, 1, 2}, 3};
|
|
REQUIRE(solver->setSimplexes(std::span(&triangle, 1)).ok());
|
|
std::vector<VolumeFluidQueryShape> queries(3);
|
|
queries[0].type = VolumeFluidQueryType::Sphere;
|
|
queries[0].center = {0, .5f, 0};
|
|
queries[0].size = {.1f, 0, 0};
|
|
queries[0].maxDistance = 2.f;
|
|
queries[1].type = VolumeFluidQueryType::Box;
|
|
queries[1].center = {0, 0, 0};
|
|
queries[1].size = {.4f, .4f, .4f};
|
|
queries[1].maxDistance = 2.f;
|
|
queries[2].type = VolumeFluidQueryType::Ray;
|
|
queries[2].center = {0, 0, 0};
|
|
queries[2].size = {0, 3, 0};
|
|
queries[2].maxDistance = 2.f;
|
|
auto hits = solver->querySimplexes(queries, 2);
|
|
REQUIRE(hits.ok());
|
|
REQUIRE(hits.value().size() == 3);
|
|
REQUIRE(hits.value()[0].queryIndex == 0);
|
|
REQUIRE(hits.value()[0].simplexIndex == 0);
|
|
REQUIRE(std::abs(hits.value()[0].simplexBary.x - .5f) < 1e-5f);
|
|
REQUIRE(std::abs(hits.value()[0].simplexBary.y - .5f) < 1e-5f);
|
|
REQUIRE(std::abs(hits.value()[0].distance - .35f) < 1e-5f);
|
|
REQUIRE(hits.value()[1].queryIndex == 1);
|
|
REQUIRE(std::abs(hits.value()[1].distance - .75f) < 1e-5f);
|
|
REQUIRE(hits.value()[2].queryIndex == 2);
|
|
REQUIRE(hits.value()[2].distance < 0.f);
|
|
REQUIRE(std::abs(hits.value()[2].simplexBary.x + hits.value()[2].simplexBary.y + hits.value()[2].simplexBary.z -
|
|
1.f) < 1e-5f);
|
|
REQUIRE(solver->setSimplexes(std::span<const VolumeFluidSimplex>{}).ok());
|
|
auto implicit = solver->querySimplexes(std::span(queries.data(), 1), 4);
|
|
REQUIRE(implicit.ok());
|
|
REQUIRE(implicit.value().size() == 3);
|
|
}
|