Geometry.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. // River Modeler
  2. // Staggart Creations (http://staggart.xyz)
  3. // Copyright protected under Unity Asset Store EULA
  4. // Copying or referencing source code for the production of new asset store content is strictly prohibited.
  5. //#define FLOW_RAYCASTS
  6. #if SWS_DEV
  7. //#define FLOW_RAYCASTS
  8. #endif
  9. using System;
  10. using System.Collections.Generic;
  11. using UnityEngine;
  12. #if MATHEMATICS
  13. using Unity.Mathematics;
  14. #endif
  15. #if SPLINES
  16. using UnityEngine.Splines;
  17. using Interpolators = UnityEngine.Splines.Interpolators;
  18. #endif
  19. namespace sc.modeling.river.runtime
  20. {
  21. public static class Geometry
  22. {
  23. #if MATHEMATICS
  24. //Mesh data
  25. private static readonly List<Vector3> vertices = new List<Vector3>();
  26. //private static readonly List<Vector3> normals = new List<Vector3>();
  27. private static readonly List<Vector4> tangents = new List<Vector4>();
  28. private static readonly List<Vector2> uv0 = new List<Vector2>();
  29. private static readonly List<Vector2> uv1 = new List<Vector2>();
  30. private static readonly List<Vector2> flow = new List<Vector2>();
  31. private static readonly List<int> triangles = new List<int>();
  32. private static readonly List<Color> colors = new List<Color>();
  33. private static Mesh mesh = new Mesh();
  34. #if SPLINES
  35. private static readonly Interpolators.LerpFloat3 float3Interpolator = new Interpolators.LerpFloat3();
  36. private static readonly Interpolators.LerpFloat floatInterpolator = new Interpolators.LerpFloat();
  37. #endif
  38. /// <summary>
  39. /// Generates river geometry from a spline
  40. /// </summary>
  41. /// <param name="splineContainer"></param>
  42. /// <param name="settings"></param>
  43. /// <param name="worldToLocal">The transformation matrix of the Transform holding the mesh (MeshFilter)</param>
  44. /// <param name="lod">Percentage to multiple the vertex count with</param>
  45. /// <param name="scaleData"></param>
  46. /// <param name="transparencyData"></param>
  47. /// <param name="foamData"></param>
  48. /// <returns></returns>
  49. #if SPLINES
  50. public static Mesh Create(SplineContainer splineContainer, Settings settings, Matrix4x4 worldToLocal, float lod = 100f,
  51. List<SplineData<float3>> scaleData = null, List<SplineData<float>> transparencyData = null, List<SplineData<float>> foamData = null)
  52. {
  53. if (splineContainer.Splines.Count == 0) return null;
  54. float resolutionScale = (lod * 0.01f);
  55. mesh = new Mesh();
  56. mesh.Clear();
  57. #if SPLINES && MATHEMATICS
  58. mesh.name = $"{splineContainer.gameObject.name} (LOD {lod}%)";
  59. float3 boundsMin = Vector3.one * -math.INFINITY;
  60. float3 boundsMax = Vector3.one * math.INFINITY;
  61. //Spline sampling data
  62. float width = settings.shape.width;
  63. float vertexDistanceWidth = settings.triangulation.vertexDistanceWidth;
  64. float vertexDistance = settings.triangulation.vertexDistance;
  65. var widthCurve = settings.shape.widthCurve;
  66. float2 offset = settings.shape.offset;
  67. //float worldLength = length / splineContainer.transform.lossyScale.z;
  68. float yScalar = 1f / splineContainer.transform.lossyScale.y;
  69. float sampleDist = vertexDistance / resolutionScale;
  70. var splineCount = splineContainer.Splines.Count;
  71. List<CombineInstance> combineInstances = new List<CombineInstance>();
  72. #if FLOW_RAYCASTS
  73. RaycastHit hit = new RaycastHit();
  74. float2 prevFlowCoords = 0f;
  75. float3 prevFlowVector = 0f;
  76. #endif
  77. float3 flowVector = 0f;
  78. for (int s = 0; s < splineCount; s++)
  79. {
  80. var subMesh = new Mesh();
  81. vertices.Clear();
  82. //normals.Clear();
  83. tangents.Clear();
  84. uv0.Clear();
  85. if(settings.uv.lightmapUV) uv1.Clear();
  86. if(settings.uv.flowVectors) flow.Clear();
  87. triangles.Clear();
  88. colors.Clear();
  89. float length = splineContainer.Splines[s].CalculateLength(splineContainer.transform.localToWorldMatrix);
  90. //The scale of the spline's transform affects the geometry scale, so would make this value in the inspector seem inaccurate
  91. //Divide the scale so that the 'width' parameter remains constant
  92. float m_width = width / splineContainer.transform.lossyScale.x;
  93. //Number of edge loops across the width
  94. int widthSegments = Mathf.CeilToInt(width / (vertexDistanceWidth / resolutionScale));
  95. widthSegments = (int)Mathf.Clamp(widthSegments, 1, 100);
  96. int xCount = widthSegments + 1;
  97. /*
  98. //Check if the spline's first knot is linked, if so consider this river a child branch
  99. KnotLinkCollection knotLinks = splineContainer.KnotLinkCollection;
  100. if (knotLinks.TryGetKnotLinks(new SplineKnotIndex(s, 0), out var _))
  101. {
  102. parentWidth = width;
  103. //m_width *= 0.5f;
  104. }
  105. */
  106. if (length < settings.triangulation.vertexDistance)
  107. {
  108. //throw new Exception($"[{splineContainer.gameObject.name}] Cannot create a river mesh from Spline (#{s}) with a length of {length}m. It is too short");
  109. continue;
  110. }
  111. int sampleCount = Mathf.RoundToInt(length / sampleDist);
  112. //Default
  113. Color color = Color.black;
  114. int rows = 0;
  115. float lastRowT = 0f;
  116. for (int y = 0; y <= sampleCount; y++)
  117. {
  118. float t = (float)y / (float)(sampleCount); //Normalized position
  119. float distance = t * length; //Position in metric units
  120. splineContainer.Splines[s].Evaluate(t, out var origin, out var tangent, out var normal);
  121. float3 forward = math.normalize(tangent);
  122. quaternion rotation = quaternion.LookRotationSafe(tangent, normal);
  123. if (settings.shape.twistCorrection) rotation = TwistCorrectedRotation(tangent);
  124. float rowStride = 1f / sampleCount;
  125. //Skip edge loops based on turning strength or flatness
  126. if (settings.triangulation.flatFilter > 0 || settings.triangulation.turnFilter > 0)
  127. {
  128. float3 acceleration = splineContainer.Splines[s].EvaluateAcceleration(t);
  129. float turn = CalculateTurnFactor(tangent, acceleration);
  130. //Scale the distance between rows up based on the flatness of the curve
  131. rowStride *= math.lerp(1f, 0.5f, (math.dot(normal, math.up())) * settings.triangulation.flatFilter);
  132. //Same for turns
  133. rowStride *= math.lerp(1f, 0.5f, ((turn)) * settings.triangulation.turnFilter * 10f);
  134. }
  135. //Always create an edge loop at the very start & end.
  136. bool createRow = (y == 0 || y == sampleCount);
  137. //If current distance at sampling point crosses the desired threshold
  138. createRow |= (lastRowT - t) <= rowStride;
  139. if (createRow)
  140. {
  141. lastRowT = t;
  142. float3 scale = scaleData != null ? scaleData[s].Evaluate(splineContainer.Splines[s], distance, scaleData[s].PathIndexUnit, float3Interpolator) : Vector3.one;
  143. float opacity = transparencyData != null ? math.clamp(transparencyData[s].Evaluate(splineContainer.Splines[s], distance, transparencyData[s].PathIndexUnit, floatInterpolator), 0, 1) : 0f;
  144. //Normalized value. 0=0, 0.5f=1, 1f=2f
  145. float foamWeight = foamData != null ? foamData[s].Evaluate(splineContainer.Splines[s], distance, foamData[s].PathIndexUnit, floatInterpolator) : 0.5f;
  146. float slopeAngle = CalculateSlopeAngle(normal);
  147. for (int x = 0; x <= widthSegments; x++)
  148. {
  149. float xt = (float)x / (widthSegments);
  150. float heightCurve = widthCurve.Evaluate(xt) * yScalar;
  151. float3 localPosition = new Vector3(x * (m_width / widthSegments) - (m_width / 2f), heightCurve, 0);
  152. float sideAlpha = EdgeDistanceMask(x, widthSegments, (opacity * xCount) - 1f, 1f);
  153. float alpha = sideAlpha;
  154. float blendWeight = CalculateDistanceWeight(distance, length,
  155. settings.transparency.startFadeOffset, settings.transparency.startFadeLength,
  156. settings.transparency.endFadeOffset, settings.transparency.endFadeLength);
  157. alpha = Mathf.Clamp01(alpha + blendWeight);
  158. //The spline could very well be used for something else as well. Allow offsetting the geometry
  159. localPosition.x += offset.x;
  160. localPosition.y += offset.y;
  161. float displacementSum = 0f;
  162. float displacementFoam = 0f;
  163. foreach (Settings.Displacement.Layer d in settings.displacement.layers)
  164. {
  165. var dispFoamMask = SlopeMask(slopeAngle, d.minMaxSlopeAngle.x, d.minMaxSlopeAngle.y);
  166. if (dispFoamMask > 0)
  167. {
  168. float displacement = noise.cnoise(new float2(xt * d.noiseFrequency.x * width * scale.x, t * d.noiseFrequency.y * length));
  169. displacementFoam += displacement * dispFoamMask * d.noiseAmplitude;
  170. displacement = math.lerp(displacement * 0.5f - 0.5f, displacement * 0.5f + 0.5f, d.normalization);
  171. displacement *= d.noiseAmplitude * scale.y * dispFoamMask * (1f-blendWeight);
  172. displacementSum += displacement;
  173. }
  174. }
  175. //Local-space displacement... create overlapping triangles on pinches and crevices
  176. //localPosition += new float3(0f, displacementSum * yScalar, 0);
  177. float3 position = origin + math.rotate(rotation, localPosition * (new float3(scale.x, 1f, 1f)));
  178. if (settings.uv.flowVectors)
  179. {
  180. float3 tangentWS = splineContainer.transform.TransformDirection(forward);
  181. flowVector = tangentWS;
  182. #if FLOW_RAYCASTS
  183. float minSamplePos = 1f;
  184. float3 positionWS = splineContainer.transform.TransformPoint(position);
  185. //if (math.abs(prevFlowCoords.x - (xt * width)) >= minSamplePos )
  186. if(math.abs(prevFlowCoords.x - (xt * width)) >= minSamplePos || math.abs(prevFlowCoords.y - distance) >= minSamplePos)
  187. {
  188. if (Physics.Raycast(positionWS, tangentWS, out hit, settings.triangulation.vertexDistance * 2f, -1, QueryTriggerInteraction.Ignore))
  189. {
  190. if (hit.collider.GetType() != typeof(TerrainCollider))
  191. {
  192. flowVector = math.reflect(hit.normal, tangentWS) * 2f;
  193. prevFlowCoords.x = xt * width;
  194. prevFlowCoords.y = distance;
  195. prevFlowVector = flowVector;
  196. }
  197. }
  198. else if (Physics.Raycast(positionWS, -forward, out hit, settings.triangulation.vertexDistance * 2f, -1, QueryTriggerInteraction.Ignore))
  199. {
  200. if (hit.collider.GetType() != typeof(TerrainCollider))
  201. {
  202. flowVector = math.reflect(-hit.normal, tangentWS) * 0.25f;
  203. prevFlowCoords.x = xt * width;
  204. prevFlowCoords.y = distance;
  205. prevFlowVector = flowVector;
  206. }
  207. }
  208. }
  209. else
  210. {
  211. flowVector = prevFlowVector;
  212. }
  213. #endif
  214. flow.Add(new Vector2(flowVector.x, flowVector.z));
  215. }
  216. //Transform back to object-space of spline so it matches 1:1, regardless of the MeshFilter's transform
  217. position = splineContainer.transform.localToWorldMatrix.MultiplyPoint(position);
  218. position += math.up() * displacementSum;
  219. //Make that the local-space position of the mesh filter
  220. position = worldToLocal.MultiplyPoint(position);
  221. uv0.Add(CalculateUV(xt, t, width * scale.x, length, settings.uv.tiling.x, settings.uv.tiling.y, settings.uv.edgeDrag, settings.uv.rotate, settings.uv.reverse));
  222. if(settings.uv.lightmapUV) uv1.Add(CalculateUV(xt, t, 1f, 1f, 1f, 1f, 0f, false, false));
  223. tangents.Add(new float4(0,0,1, 1f));
  224. float foamNoise = 0f;
  225. float foamMask = 1f;
  226. if (settings.foam.opacity > 0)
  227. {
  228. foamNoise = noise.cnoise(new float2(xt * settings.foam.noiseFrequency.x * width * scale.x, t * settings.foam.noiseFrequency.y * length)) * settings.foam.noiseAmplitude;
  229. foamNoise = math.smoothstep(settings.foam.noiseLevels.x, settings.foam.noiseLevels.y, foamNoise);
  230. foamNoise += math.smoothstep(settings.foam.displacementLevels.x, settings.foam.displacementLevels.y, (displacementFoam * settings.foam.displacementFoam));
  231. foamNoise += settings.foam.uniformAmount;
  232. foamNoise *= settings.foam.opacity;
  233. //Values between 0.5/1.0 contribute uniform foam
  234. foamNoise += (foamWeight - 0.5f) * 2f;
  235. //Nullify foam on the very edges
  236. foamMask *= (x == 0 ? 0f : 1f);
  237. foamMask *= (x == xCount-1 ? 0f : 1f);
  238. foamMask *= (1-blendWeight);
  239. //Values between 0.0/1.0 subtract generated foam
  240. //foamMask *= (foamWeight - 0.5f * 2.0f);
  241. foamNoise *= (foamMask);
  242. }
  243. //float sideFade = Geometry.EdgeDistanceMask(x, (m_width * scale.x) + settings.geometry.vertexDistanceWidth, settings.transparency.sideFadeDistance, settings.transparency.sideFadeFalloff);
  244. //alpha = math.max(sideFade, alpha);
  245. //foamNoise = math.smoothstep(0.5f, 1f, math.max(xt, 1f-xt)) * settings.uv.edgeDrag;
  246. color[(int)settings.foam.vertexColorChannel] = foamNoise;
  247. color[(int)settings.transparency.vertexColorChannel] = alpha;
  248. colors.Add(color);
  249. vertices.Add(position);
  250. //Extend bounds as it expands
  251. boundsMin = math.min(position, boundsMin);
  252. boundsMax = math.max(position, boundsMax);
  253. }
  254. if (y <= sampleCount - 1) //Stop at 2nd last row
  255. {
  256. for (int x = 0; x < widthSegments; x++)
  257. {
  258. triangles.Insert(0, (rows * xCount) + x);
  259. triangles.Insert(1, ((rows + 1) * xCount) + x);
  260. triangles.Insert(2, (rows * xCount) + x + 1);
  261. triangles.Insert(3, ((rows + 1) * xCount) + x);
  262. triangles.Insert(4, ((rows + 1) * xCount) + x + 1);
  263. triangles.Insert(5, (rows * xCount) + x + 1);
  264. }
  265. }
  266. rows++;
  267. }
  268. }
  269. subMesh.SetVertices(vertices);
  270. subMesh.SetUVs(0, uv0);
  271. if (settings.uv.lightmapUV) subMesh.SetUVs(1, uv1);
  272. if (settings.uv.flowVectors) subMesh.SetUVs(2, flow);
  273. subMesh.subMeshCount = 1;
  274. subMesh.SetIndices(triangles, MeshTopology.Triangles, 0);
  275. subMesh.SetColors(colors);
  276. subMesh.SetTangents(tangents);
  277. //subMesh.SetNormals(normals);
  278. subMesh.RecalculateNormals();
  279. CombineInstance combineInstance = new CombineInstance()
  280. {
  281. mesh = subMesh,
  282. transform = worldToLocal
  283. };
  284. combineInstances.Add(combineInstance);
  285. }
  286. mesh.CombineMeshes(combineInstances.ToArray(), true, false);
  287. mesh.bounds.SetMinMax(boundsMin, boundsMax);
  288. #endif
  289. return mesh;
  290. }
  291. #endif
  292. private static float2 CalculateUV(float xt, float yt, float width, float length, float tilingX, float tilingY, float edgeDrag, bool rotateUV, bool reverse)
  293. {
  294. int sign = (reverse ? 1 : -1);
  295. //Will map the 0-1 UV scale over 1 world-space unit
  296. float uvFactorX = width * tilingX * sign;
  297. float uvFactorY = length * tilingY * sign;
  298. var u = xt * uvFactorX;
  299. var v = yt * uvFactorY;
  300. if (rotateUV) (u,v) = (v,u);
  301. v -= math.smoothstep(0.25f, 1f, math.max(xt, 1f-xt)) * edgeDrag;
  302. return new Vector2(1f+u, v+1f);
  303. }
  304. private static float EdgeDistanceMask(float position, float maxWidth, float distance, float falloff)
  305. {
  306. float start = Mathf.Clamp01(((distance + falloff) - (position - distance)) / (falloff));
  307. float end = Mathf.Clamp01(((maxWidth - distance) - (position + distance)) / (falloff));
  308. return Mathf.Max(start, 1f - end);
  309. }
  310. private static float CalculateDistanceWeight(float position, float splineLength, float startDistance, float startFalloff, float endDistance, float endFalloff)
  311. {
  312. float start = Mathf.Clamp01(((startDistance) - (position - (startDistance + startFalloff))) / (startFalloff));
  313. float end = Mathf.Clamp01(((splineLength - endDistance) - (position + endDistance)) / (endFalloff));
  314. return Mathf.Max(start, 1f - end);
  315. }
  316. private static float CalculateTurnFactor(float3 tangent, float3 acceleration)
  317. {
  318. float2 firstDerivativeNormSq = math.lengthsq(tangent.xz);
  319. float2 secondDerivativeNormSq = math.lengthsq(acceleration.xz);
  320. float2 derivativesDot = math.dot(tangent.xz, acceleration.xz);
  321. var kappa = math.sqrt((firstDerivativeNormSq * secondDerivativeNormSq ) - (derivativesDot * derivativesDot)) / (firstDerivativeNormSq * math.length(tangent.xz));
  322. return kappa.x;
  323. }
  324. #if MATHEMATICS
  325. public static quaternion TwistCorrectedRotation(float3 tangent)
  326. {
  327. //Classic function (needs conversion to radians)
  328. //float3 euler = Quaternion.LookRotation(tangent, math.up()).eulerAngles;
  329. quaternion q = quaternion.LookRotation(tangent, math.up());
  330. var euler = float3.zero;
  331. #if !MATHEMATICS_1_3_1
  332. const float epsilon = 1e-6f;
  333. const float cutoff = (1f - 2f * epsilon) * (1f - 2f * epsilon);
  334. var qv = q.value;
  335. var d1 = qv * qv.wwww * math.float4(2f); //xw, yw, zw, ww
  336. var d2 = qv * qv.yzxw * math.float4(2f); //xy, yz, zx, ww
  337. var d3 = qv * qv;
  338. var y1 = d2.x + d1.z;
  339. if (y1 * y1 < cutoff)
  340. {
  341. var x1 = -d2.y + d1.x;
  342. var x2 = d3.y + d3.w - d3.z - d3.x;
  343. var z1 = -d2.z + d1.y;
  344. var z2 = d3.x + d3.w - d3.y - d3.z;
  345. euler = math.float3(math.atan2(x1, x2), math.asin(y1), math.atan2(z1, z2));
  346. }
  347. else //xyx
  348. {
  349. y1 = math.clamp(y1, -1f, 1f);
  350. var abcd = math.float4(d2.x, d1.z, d2.z, d1.y);
  351. var x1 = 2f * (abcd.x * abcd.w + abcd.y * abcd.z); //2(ad+bc)
  352. var x2 = math.csum(abcd * abcd * math.float4(-1f, 1f, -1f, 1f));
  353. euler = math.float3(math.atan2(x1, x2), math.asin(y1), 0f);
  354. }
  355. euler.xyz = euler.xzy;
  356. #else
  357. //NOTE: Mathematics v1.3.1+ has a "math.Euler" function to be used here. Also note that the angles are then already in radians
  358. euler = math.Euler(q, math.RotationOrder.XZY);
  359. #endif
  360. //Create a new rotation from just the Y-axis
  361. return quaternion.AxisAngle(math.up(), euler.y);
  362. }
  363. #endif
  364. public static float CalculateSlopeAngle(float3 normal)
  365. {
  366. return ((float)math.acos(math.dot(normal, math.up())) * Mathf.Rad2Deg);
  367. }
  368. public static float SlopeMask(float slope, float minAngle, float maxAngle)
  369. {
  370. float slopeMask = 0f;
  371. if (slope >= minAngle && slope <= maxAngle) slopeMask = 1f;
  372. return slopeMask;
  373. }
  374. #endif
  375. }
  376. }