Curl.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. // This implementation of Curl Noise is based on the following (fantastic) references:
  2. //R1: http://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph2007-curlnoise.pdf
  3. //R2: http://prideout.net/blog/?p=63
  4. //R3: http://catlikecoding.com/unity/tutorials/noise-derivatives/
  5. #include "Curl.h"
  6. #include "Noise.h"
  7. #include <float.h> // FLT_MAX
  8. // For writing to Unity console
  9. #include <string>
  10. using namespace std;
  11. #if USING_UNITY
  12. // Allow writing to the Unity debug console from inside DLL land.
  13. extern "C"
  14. {
  15. void(_stdcall*debugLog)(const char*) = NULL;
  16. __declspec(dllexport) void LinkDebug(void(_stdcall*d)(const char *))
  17. {
  18. debugLog = d;
  19. }
  20. }
  21. void DebugLog(const char* str)
  22. {
  23. //# if _DEBUG
  24. if (debugLog) debugLog(str);
  25. //# endif
  26. }
  27. void DebugLog(string str)
  28. {
  29. //# if _DEBUG
  30. if (debugLog) debugLog(str.c_str());
  31. //# endif
  32. }
  33. using namespace Vectormath::Aos;
  34. string to_string(Vector3 v)
  35. {
  36. string s(to_string(v.getX()) + "," + to_string(v.getY()) + "," + to_string(v.getZ()));
  37. return s;
  38. }
  39. #else
  40. // do nothing
  41. void DebugLog(const char* str) {}
  42. void DebugLog(string str) {}
  43. using namespace Vectormath::Aos;
  44. string to_string(Vector3 v) {return "";}
  45. #endif
  46. //-------------------------------------------------------------------------------------------------------------
  47. namespace // unnamed to hide implementation functions & separate them from API code
  48. {
  49. using namespace Vectormath::Aos;
  50. using CurlNoise::Volume;
  51. using CurlNoise::CurlSettings;
  52. static CurlSettings s_Settings;
  53. struct DistanceGradient
  54. {
  55. float distance;
  56. Vector3 gradient; // normal
  57. };
  58. // Computes world space distance from 'p' (world space) to nearest obstacle primitive and returns a hacky approximation
  59. // of the normal at that point
  60. DistanceGradient SampleObstacleDistanceAndGradientCheap(Vector3 p, const Volume *pColliders, unsigned int length)
  61. {
  62. float d = FLT_MAX;
  63. unsigned int cminId = 0;
  64. // TODO This is a naive brute force way. We could do a space partitioning approach to
  65. // quickly eliminate those obstacles that are farther away
  66. for (unsigned int cId = 0; cId < length; cId++)
  67. {
  68. float dc = pColliders[cId].DistanceToSurface(p);
  69. if (dc < d)
  70. {
  71. d = dc;
  72. cminId = cId;
  73. }
  74. }
  75. DistanceGradient dg;
  76. dg.distance = d;
  77. if (length > 0)
  78. dg.gradient = p - pColliders[cminId].GetWorldPos(); // xform to object space w/o scaling
  79. else
  80. dg.gradient = Vector3(0.f, 0.f, 0.f);
  81. return dg;
  82. }
  83. //-------------------------------------------------------------------------------------------------------------
  84. // Blend noise based potential with distance gradient when near an obstacle
  85. // Based on sec 2.4 of https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph2007-curlnoise.pdf
  86. Vector3 ConstrainedPotential(Vector3 potential, Vector3 distanceGradient, float alpha)
  87. {
  88. float dp = dot(potential, distanceGradient);
  89. return alpha * potential + (1 - alpha) * distanceGradient * dp;
  90. }
  91. //-------------------------------------------------------------------------------------------------------------
  92. // Computes world space distance to the nearest obstacle from a world space position 'p' and the gradient at that point
  93. // This function determines the gradient by moving 'p' and calculating the change in the distance field and is
  94. // expensive as a result.
  95. // Use SampleObstacleDistanceAndGradientCheap(..) for a cheaper alternative
  96. DistanceGradient ComputeDistanceAndGradientExpensive(Vector3 p, const Volume *pColliders, unsigned int length)
  97. {
  98. const float e = 0.01f;
  99. Vector3 dx(e, 0, 0);
  100. Vector3 dy(0, e, 0);
  101. Vector3 dz(0, 0, e);
  102. float d = SampleObstacleDistanceAndGradientCheap(p, pColliders, length).distance; // distance of nearest obstacle
  103. float dfdx = SampleObstacleDistanceAndGradientCheap(p + dx, pColliders, length).distance - d; // distance change wrt x
  104. float dfdy = SampleObstacleDistanceAndGradientCheap(p + dy, pColliders, length).distance - d; // distance change wrt y
  105. float dfdz = SampleObstacleDistanceAndGradientCheap(p + dz, pColliders, length).distance - d; // distance change wrt z
  106. DistanceGradient dg;
  107. dg.distance = d;
  108. dg.gradient = normalize(Vector3(dfdx, dfdy, dfdz));
  109. return dg;
  110. }
  111. //-------------------------------------------------------------------------------------------------------------
  112. // Higher degree polynomial (used by Ken Perlin) that returns a value in the range [0,1]
  113. // 'r' is the obstacle distance in noise-space (which is equivalent to world space at the moment) TODO fix this
  114. inline float smooth_step(float r)
  115. {
  116. if (r < 0) return 0.f;
  117. else if (r>1) return 1.f;
  118. return r*r*r*(10 + r*(-15 + r * 6));
  119. // NOTE 6r^5 - 15r^4 + 10r^3 has a zero 1st and 2nd order derivative at r=0 and r=1
  120. // The paper by Bridson uses a different function.
  121. }
  122. // Returns [-1,1]
  123. inline float ramp(float r)
  124. {
  125. float rSmooth = smooth_step((r + 1) / 2);
  126. return rSmooth*2 - 1; // [0,1] -> [-1,1]
  127. }
  128. //-------------------------------------------------------------------------------------------------------------
  129. using CurlNoise::NoiseSample;
  130. using CurlNoise::PerlinNoise3;
  131. // Returns the 3D potential at 'p' in the presence of obstacles
  132. Vector3 SamplePotential(Vector3 p, const Volume *pColliders, unsigned int length)
  133. {
  134. Vector3 psi(0, 0, 0);
  135. DistanceGradient dg;
  136. if (s_Settings.m_bCheapGradient)
  137. dg = SampleObstacleDistanceAndGradientCheap(p, pColliders, length);
  138. else
  139. dg = ComputeDistanceAndGradientExpensive(p, pColliders, length);
  140. const auto& obstacleDistance = dg.distance;
  141. const auto& normal = dg.gradient;
  142. // Smoothly ramp down the potential based on distance to the obstacle so that it is 0 at its boundary
  143. float alpha = ramp(obstacleDistance);
  144. // evaluate 3D noise to get the potential field at p
  145. NoiseSample ns0 = PerlinNoise3::Noise(p, 1.f, 1, 0.f, 0.f);
  146. NoiseSample ns1 = PerlinNoise3::Noise(p, 1.f, 1, 0.f, 0.f);
  147. NoiseSample ns2 = PerlinNoise3::Noise(p, 1.f, 1, 0.f, 0.f);
  148. psi = Vector3(ns0.value, ns1.value, ns2.value); // noise based 3D potential
  149. // modulate the calculated potential to handle the inviscid boundary condition
  150. // i.e., v . n = 0 at the boundary (component of velocity along normal is 0)
  151. Vector3 psi_new = ConstrainedPotential(psi, normal, alpha);
  152. return psi_new;
  153. }
  154. //-------------------------------------------------------------------------------------------------------------
  155. // helpers
  156. Vector3 abs(const Vector3& v)
  157. {
  158. return Vector3(fabsf(v.getX()), fabsf(v.getY()), fabsf(v.getZ()));
  159. }
  160. Vector3 max(const Vector3& v1, const Vector3& v2)
  161. {
  162. return Vector3(fmaxf(v1.getX(), v2.getX()),
  163. fmaxf(v1.getY(), v2.getY()),
  164. fmaxf(v1.getZ(), v2.getZ())
  165. );
  166. }
  167. //-------------------------------------------------------------------------------------------------------------
  168. // Ref: http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
  169. // Signed distance in world space units from a point 'p' to the surface of a sphere of radius 'r' centered at the origin
  170. static float sdSphere(Vector3 p, float r)
  171. {
  172. return length(p) - r;
  173. }
  174. //-------------------------------------------------------------------------------------------------------------
  175. // Signed distance in world space units from a point 'p' to the surface of a box with extents 'b' centered at the origin
  176. static float sdBox(Vector3 p, Vector3 b)
  177. {
  178. Vector3 d = abs(p) - b;
  179. float dmax = fmaxf(d.getX(), fmaxf(d.getY(), d.getZ()));
  180. return fminf(dmax, 0.f) + length(max(d, Vector3(0.f, 0.f, 0.f)));
  181. }
  182. //-------------------------------------------------------------------------------------------------------------
  183. // Signed distance from a point 'p' to the surface of a cylinder of radius 'r' and height 'h' centered at the origin and
  184. // with its axis along the (object's) Y axis
  185. // Note: h is not the half-height
  186. static float sdCappedCylinder(Vector3 p, float r, float h)
  187. {
  188. Vector3 pXZ = Vector3(p.getX(), 0.f, p.getZ()); // todo: replace with Vec2
  189. float d1 = length(pXZ) - r; // xz
  190. float d2 = fabsf(p.getY()) - h*0.5f; // y
  191. float d = fminf(fmaxf(d1, d2), 0.f);
  192. float l = length(Vector3(fmaxf(d1, 0.f), fmaxf(d2, 0.f), 0.f));
  193. return d + l;
  194. }
  195. //-------------------------------------------------------------------------------------------------------------
  196. // TODO This function is uber brute force, requiring 12 potential samples for finite differentiation
  197. // Each potential sample involves:
  198. // (a) 1 or 4 calls to determine distance and gradient of the nearest obstacle (based on s_Settings.m_bCheapGradient)
  199. // (b) 3 perlin noise look ups for the 3D noise
  200. Vector3 ComputeCurlWithObstacles(Vector3 p, const Volume *pColliders, unsigned int length)
  201. {
  202. const float e = 1e-4f;
  203. Vector3 dx(e, 0, 0);
  204. Vector3 dy(0, e, 0);
  205. Vector3 dz(0, 0, e);
  206. float dfzdy = SamplePotential(p + dy, pColliders, length).getZ() -
  207. SamplePotential(p - dy, pColliders, length).getZ();
  208. float dfydz = SamplePotential(p + dz, pColliders, length).getY() -
  209. SamplePotential(p - dz, pColliders, length).getY();
  210. float dfxdz = SamplePotential(p + dz, pColliders, length).getX() -
  211. SamplePotential(p - dz, pColliders, length).getX();
  212. float dfzdx = SamplePotential(p + dx, pColliders, length).getZ() -
  213. SamplePotential(p - dx, pColliders, length).getZ();
  214. float dfydx = SamplePotential(p + dx, pColliders, length).getY() -
  215. SamplePotential(p - dx, pColliders, length).getY();
  216. float dfxdy = SamplePotential(p + dy, pColliders, length).getX() -
  217. SamplePotential(p - dy, pColliders, length).getX();
  218. return Vector3(dfzdy - dfydz, dfxdz - dfzdx, dfydx - dfxdy) / (2 * e);
  219. }
  220. } // unnamed namespace
  221. namespace CurlNoise
  222. {
  223. //-------------------------------------------------------------------------------------------------------------
  224. // CurlNoise API
  225. //-------------------------------------------------------------------------------------------------------------
  226. // PerlinNoise3 returns a scalar float given a 3D position in space.
  227. // To generate a potential force field, we need 3 values, so we use hardcoded offsets
  228. inline NoiseSample noiseX(Vector3 p) {
  229. return PerlinNoise3::Noise( p,
  230. s_Settings.m_Frequency,
  231. s_Settings.m_NumOctaves,
  232. s_Settings.m_Lacunarity,
  233. s_Settings.m_Persistence);
  234. }
  235. inline NoiseSample noiseY(Vector3 p) {
  236. return PerlinNoise3::Noise( p + Vector3(31.341f, -43.23f, 12.34f),
  237. s_Settings.m_Frequency,
  238. s_Settings.m_NumOctaves,
  239. s_Settings.m_Lacunarity,
  240. s_Settings.m_Persistence);
  241. }
  242. inline NoiseSample noiseZ(Vector3 p) {
  243. return PerlinNoise3::Noise( p + Vector3(-231.341f, 124.23f, -54.34f),
  244. s_Settings.m_Frequency,
  245. s_Settings.m_NumOctaves,
  246. s_Settings.m_Lacunarity,
  247. s_Settings.m_Persistence);
  248. }
  249. //-------------------------------------------------------------------------------------------------------------
  250. // Computes the curl at 'p' by sampling the potential field generated via 3D noise.
  251. // The perlin noise calculation takes care of calculating the derivatives analytically
  252. Vector3 ComputeCurlWithoutObstacles(Vector3 p)
  253. {
  254. NoiseSample nsX = noiseX(p);
  255. NoiseSample nsY = noiseY(p);
  256. NoiseSample nsZ = noiseZ(p);
  257. //curl = (dfzdy - dfydz, dfxdz - dfzdx, dfydx - dfxdy)
  258. Vector3 curl = Vector3(nsZ.derivative.getY() - nsY.derivative.getZ(),
  259. nsX.derivative.getZ() - nsZ.derivative.getX(),
  260. nsY.derivative.getX() - nsX.derivative.getY());
  261. return curl;
  262. }
  263. //-------------------------------------------------------------------------------------------------------------
  264. // Computes the curl at 'p' by sampling the potential field generated via 3D noise, while respecting
  265. // obstacle boundaries.
  266. Vector3 ComputeCurl(Vector3 p, const Volume *pColliders, unsigned int length)
  267. {
  268. auto dg = SampleObstacleDistanceAndGradientCheap(p, pColliders, length);
  269. float rampD = ramp(dg.distance); //TODO: account for what a unit-step in noise dimensions is
  270. Vector3 curl;
  271. if (rampD >= 1.f) // no need to account for obstacles to modify the potential
  272. {
  273. curl = ComputeCurlWithoutObstacles(p);
  274. }
  275. else
  276. {
  277. //DebugLog("Obstacle neat particle at " + to_string(p) + ". dist = " + to_string(obstacleDistance));
  278. curl = ComputeCurlWithObstacles(p, pColliders, length);
  279. }
  280. return curl;
  281. }
  282. //-------------------------------------------------------------------------------------------------------------
  283. // Set the parameters to control look/cost of noise/curl
  284. void SetCurlSettings(const CurlSettings& settings)
  285. {
  286. s_Settings = settings;
  287. }
  288. #if USING_UNITY
  289. //-------------------------------------------------------------------------------------------------------------
  290. using CurlNoise::float3;
  291. float3 Vector3ToFloat3(const Vector3& v)
  292. {
  293. float3 f;
  294. f.val[0] = v.getX();
  295. f.val[1] = v.getY();
  296. f.val[2] = v.getZ();
  297. return f;
  298. }
  299. //-------------------------------------------------------------------------------------------------------------
  300. // Wrappers to use with Unity
  301. //-------------------------------------------------------------------------------------------------------------
  302. float3 ComputeCurlBruteForce(Vector3 p, Volume *pColliders, unsigned int length)
  303. {
  304. Vector3 curl = ComputeCurlWithObstacles(p, pColliders, length);
  305. return Vector3ToFloat3(curl);
  306. }
  307. // This function calculates the potential derivatives analytically, which are then used to compute the curl.
  308. // It does not respect the presence of obstacles.
  309. float3 ComputeCurlNoBoundaries(Vector3 p)
  310. {
  311. Vector3 curl = ComputeCurlWithoutObstacles(p);
  312. return Vector3ToFloat3(curl);
  313. }
  314. float3 ComputeCurlNonBruteForce(Vector3 p, Volume *pColliders, unsigned int length)
  315. {
  316. Vector3 curl = ComputeCurl(p, pColliders, length);
  317. return Vector3ToFloat3(curl);
  318. }
  319. void SetCurlSettings(bool bCheapGradient,
  320. float frequency,
  321. unsigned int numOctaves,
  322. float lacunarity,
  323. float persistence)
  324. {
  325. s_Settings.m_bCheapGradient = bCheapGradient;
  326. s_Settings.m_Frequency = frequency;
  327. s_Settings.m_NumOctaves = numOctaves;
  328. s_Settings.m_Lacunarity = lacunarity;
  329. s_Settings.m_Persistence = persistence;
  330. }
  331. #endif
  332. //-------------------------------------------------------------------------------------------------------------
  333. // Volume
  334. //-------------------------------------------------------------------------------------------------------------
  335. // Creates a sphere collider primitive
  336. // The matrix expected is the inverse of the object to world matrix consisting of only translation and rotation.
  337. // The radius 'r' is in world space units
  338. Volume::Volume(const Matrix4& worldToObjectNoScale, float r)
  339. {
  340. m_Shape = kSphere;
  341. m_Extents = Vector3(r, r, r);
  342. m_WorldToObject = worldToObjectNoScale;
  343. }
  344. // Creates a box collider primitive
  345. // The matrix expected is the inverse of the object to world matrix consisting of only translation and rotation.
  346. // The extents are the half-lbh dimensions in world space
  347. Volume::Volume(const Matrix4& worldToObjectNoScale, const Vector3& extents)
  348. {
  349. m_Shape = kBox;
  350. m_Extents = extents;
  351. m_WorldToObject = worldToObjectNoScale;
  352. }
  353. // Creates a capped cylinder collider primitive
  354. // The matrix expected is the inverse of the object to world matrix consisting of only translation and rotation.
  355. // The radius and height are in world-space units
  356. Volume::Volume(const Matrix4& worldToObjectNoScale, float r, float h)
  357. {
  358. m_Shape = kCylinder;
  359. m_Extents = Vector3(r, h, 0.f);
  360. m_WorldToObject = worldToObjectNoScale;
  361. }
  362. // Updates the collider's inverse transform (worldToObject)
  363. // The matrix expected is the inverse of the object to world matrix consisting of only translation and rotation.
  364. void Volume::SetWorldToObjectTransform(const Matrix4& worldToObjectNoScale)
  365. {
  366. m_WorldToObject = worldToObjectNoScale;
  367. }
  368. // Returns distance of 'p' (which is in world space) from the surface of the collider
  369. // Since the transform doesn't account for scaling, the distance returned is in world space units.
  370. float Volume::DistanceToSurface(const Vector3& p) const
  371. {
  372. float d = 0.f;
  373. Vector4 osPos = m_WorldToObject * Vector4(p, 1.0f); // xform to object space w/o scaling
  374. switch (m_Shape)
  375. {
  376. case kSphere:
  377. d = sdSphere(osPos.getXYZ(), m_Extents.getX());
  378. break;
  379. default:
  380. case kBox:
  381. d = sdBox(osPos.getXYZ(), m_Extents);
  382. break;
  383. case kCylinder:
  384. d = sdCappedCylinder(osPos.getXYZ(), m_Extents.getX(), m_Extents.getY());
  385. break;
  386. }
  387. return d;
  388. }
  389. // Returns the world space position of the collider
  390. Vector3 Volume::GetWorldPos() const
  391. {
  392. Vector4 wsPos = m_WorldToObject.getCol3();
  393. return Vector3(-wsPos.getX(), -wsPos.getY(), -wsPos.getZ());
  394. }
  395. }