Files

53 lines
2.8 KiB
C++

#pragma once
#include "FluidSim/AudioProcessing.hpp"
#include <algorithm>
#include <cmath>
#include <stdexcept>
#include <vector>
#include <utility>
namespace fluidsim::port::desktop {
struct PipeAnchor {float distance;Vec3 position;};
// Piecewise straight centerline; knot distances are measured from its geometry.
class PipePath {
public:
explicit PipePath(std::vector<PipeAnchor> anchors):anchors_(std::move(anchors)) {
if(anchors_.size()<2 || anchors_.front().distance!=0)throw std::invalid_argument("pipe path needs origin and endpoint");
for(std::size_t i=0;i<anchors_.size();++i) {
const auto& p=anchors_[i];
if(!std::isfinite(p.distance) || !std::isfinite(p.position.x) || !std::isfinite(p.position.y) || !std::isfinite(p.position.z) ||
(i && !(p.distance>anchors_[i-1].distance))) throw std::invalid_argument("invalid distance-referenced pipe path");
}
}
Vec3 position(float distance) const {
distance=std::clamp(distance,0.f,anchors_.back().distance);
auto it=std::upper_bound(anchors_.begin(),anchors_.end(),distance,[](float s,const PipeAnchor& a){return s<a.distance;});
std::size_t i=std::clamp<std::size_t>(it-anchors_.begin(),1,anchors_.size()-1)-1;
const auto& a=anchors_[i];const auto& b=anchors_[i+1];
const float t=(distance-a.distance)/(b.distance-a.distance);
return {std::lerp(a.position.x,b.position.x,t),std::lerp(a.position.y,b.position.y,t),std::lerp(a.position.z,b.position.z,t)};
}
Vec3 tangent(float distance) const {
const float epsilon=anchors_.back().distance*.0001f;
const auto a=position(distance-epsilon),b=position(distance+epsilon);
const float x=b.x-a.x,y=b.y-a.y,z=b.z-a.z,n=std::sqrt(x*x+y*y+z*z);
return n>1e-8f?Vec3{x/n,y/n,z/n}:Vec3{1,0,0};
}
Vec3 miter_offset(float distance,Vec3 radial) const {
const auto unit=[](Vec3 v){const float n=std::sqrt(v.x*v.x+v.y*v.y+v.z*v.z);return Vec3{v.x/n,v.y/n,v.z/n};};
for(std::size_t i=1;i+1<anchors_.size();++i)if(std::abs(distance-anchors_[i].distance)<1e-6f) {
const auto a=anchors_[i-1].position,b=anchors_[i].position,c=anchors_[i+1].position;
const auto u=unit({b.x-a.x,b.y-a.y,b.z-a.z}),v=unit({c.x-b.x,c.y-b.y,c.z-b.z});
const float cosine=std::clamp(u.x*v.x+u.y*v.y+u.z*v.z,-1.f,1.f);
if(cosine>.99999f)return radial;
const auto bend=unit({u.x-v.x,u.y-v.y,u.z-v.z});
const float scale=std::min(3.f,1/std::sqrt(std::max(.0001f,(1+cosine)*.5f)));
const float extension=(radial.x*bend.x+radial.y*bend.y+radial.z*bend.z)*(scale-1);
return {radial.x+bend.x*extension,radial.y+bend.y*extension,radial.z+bend.z*extension};
}
return radial;
}
private:
std::vector<PipeAnchor> anchors_;
};
}