#pragma once #include "FluidSim/AudioProcessing.hpp" #include #include #include #include #include namespace fluidsim::port::desktop { struct PipeAnchor {float distance;Vec3 position;}; // Knots are distances along the simulated pipe, in metres. Display positions // define its schematic route; changing them never changes fluid cell lengths. class PipePath { public: explicit PipePath(std::vector 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;ianchors_[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(it-anchors_.begin(),1,anchors_.size()-1)-1; const auto& a=anchors_[i];const auto& b=anchors_[i+1]; const float span=b.distance-a.distance,t=(distance-a.distance)/span,t2=t*t,t3=t2*t; const auto ma=tangent_at(i),mb=tangent_at(i+1); auto coordinate=[&](float pa,float pb,float ta,float tb){return (2*t3-3*t2+1)*pa+(t3-2*t2+t)*span*ta+(-2*t3+3*t2)*pb+(t3-t2)*span*tb;}; return {coordinate(a.position.x,b.position.x,ma.x,mb.x),coordinate(a.position.y,b.position.y,ma.y,mb.y),coordinate(a.position.z,b.position.z,ma.z,mb.z)}; } 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}; } private: Vec3 tangent_at(std::size_t i) const { const auto& a=anchors_[i?i-1:0];const auto& b=anchors_[std::min(i+1,anchors_.size()-1)]; const float d=b.distance-a.distance; return {(b.position.x-a.position.x)/d,(b.position.y-a.position.y)/d,(b.position.z-a.position.z)/d}; } std::vector anchors_; }; }