public static bool GenerateMeshFromSpline <SplinePointCustomData>(Spline.Spline spline, Transform transform, int subdivisions, float radius, int smoothingIterations, Vector2 customDataDefault, ref Mesh mesh) where SplinePointCustomData : ISplinePointCustomData { var splinePoints = spline.GetComponentsInChildren <SplinePoint>(); if (splinePoints.Length < 2) { return(false); } var splinePointCount = splinePoints.Length; if (spline._closed && splinePointCount > 2) { splinePointCount++; } var points = new Vector3[(splinePointCount - 1) * 3 + 1]; if (!SplineInterpolation.GenerateCubicSplineHull(splinePoints, points, spline._closed)) { return(false); } // Sample spline // Estimate total length of spline and use this to compute a sample count var lengthEst = 0f; for (int i = 1; i < splinePointCount; i++) { lengthEst += (splinePoints[i % splinePoints.Length].transform.position - splinePoints[i - 1].transform.position).magnitude; } lengthEst = Mathf.Max(lengthEst, 1f); var spacing = 16f / Mathf.Pow(2f, subdivisions + 1); var pointCount = Mathf.CeilToInt(lengthEst / spacing); pointCount = Mathf.Max(pointCount, 1); var sampledPtsOnSpline = new Vector3[pointCount]; var sampledPtsOffSpline = new Vector3[pointCount]; var customData = new Vector2[pointCount]; // First set of sample points lie on spline sampledPtsOnSpline[0] = points[0]; customData[0] = customDataDefault; if (splinePoints[0].TryGetComponent(out SplinePointCustomData customDataComp00)) { customData[0] = customDataComp00.GetData(); } for (var i = 1; i < pointCount; i++) { float t = i / (float)(pointCount - 1); SplineInterpolation.InterpolateCubicPosition(splinePointCount, points, t, out sampledPtsOnSpline[i]); var tpts = t * (splinePoints.Length - 1f); var spidx = Mathf.FloorToInt(tpts); var alpha = tpts - spidx; var customData0 = customDataDefault; if (splinePoints[spidx].TryGetComponent(out SplinePointCustomData customDataComp0)) { customData0 = customDataComp0.GetData(); } var customData1 = customDataDefault; if (splinePoints[Mathf.Min(spidx + 1, splinePoints.Length - 1)].TryGetComponent(out SplinePointCustomData customDataComp1)) { customData1 = customDataComp1.GetData(); } customData[i] = Vector2.Lerp(customData0, customData1, Mathf.SmoothStep(0f, 1f, alpha)); } // Second set of sample points lie off-spline - some distance to the right for (var i = 0; i < pointCount; i++) { var ibefore = i - 1; var iafter = i + 1; if (!spline._closed) { // Not closed - clamp to range ibefore = Mathf.Max(ibefore, 0); iafter = Mathf.Min(iafter, pointCount - 1); } else { // Closed - wrap into range if (ibefore < 0) { ibefore += pointCount; } iafter %= pointCount; } var tangent = sampledPtsOnSpline[iafter] - sampledPtsOnSpline[ibefore]; var normal = tangent; normal.x = tangent.z; normal.z = -tangent.x; normal.y = 0f; normal = normal.normalized; sampledPtsOffSpline[i] = sampledPtsOnSpline[i] + normal * radius; } if (spline._closed) { var midPoint = Vector3.Lerp(sampledPtsOffSpline[0], sampledPtsOffSpline[sampledPtsOffSpline.Length - 1], 0.5f); sampledPtsOffSpline[0] = sampledPtsOffSpline[sampledPtsOffSpline.Length - 1] = midPoint; } // Blur the second set of points to help solve overlaps or large distortions. Not perfect but helps in many cases. if (smoothingIterations > 0) { var scratchPoints = new Vector3[pointCount]; // Ring buffer style access when closed spline if (!spline._closed) { for (var j = 0; j < smoothingIterations; j++) { scratchPoints[0] = sampledPtsOffSpline[0]; scratchPoints[pointCount - 1] = sampledPtsOffSpline[pointCount - 1]; for (var i = 1; i < pointCount - 1; i++) { scratchPoints[i] = (sampledPtsOffSpline[i] + sampledPtsOffSpline[i + 1] + sampledPtsOffSpline[i - 1]) / 3f; scratchPoints[i] = sampledPtsOnSpline[i] + (scratchPoints[i] - sampledPtsOnSpline[i]).normalized * radius; } var tmp = sampledPtsOffSpline; sampledPtsOffSpline = scratchPoints; scratchPoints = tmp; } } else { for (var j = 0; j < smoothingIterations; j++) { for (var i = 0; i < sampledPtsOffSpline.Length; i++) { // Slightly odd indexing. The first and last point are the same, the indices need to wrap to either the // second element (if overflow) or the penultimate element (if underflow) to ensure tension is maintained at ends. var ibefore = i - 1; var iafter = i + 1; if (ibefore < 0) { ibefore = sampledPtsOffSpline.Length - 2; } if (iafter >= sampledPtsOffSpline.Length) { iafter = 1; } scratchPoints[i] = (sampledPtsOffSpline[i] + sampledPtsOffSpline[iafter] + sampledPtsOffSpline[ibefore]) / 3f; scratchPoints[i] = sampledPtsOnSpline[i] + (scratchPoints[i] - sampledPtsOnSpline[i]).normalized * radius; } var tmp = sampledPtsOffSpline; sampledPtsOffSpline = scratchPoints; scratchPoints = tmp; } } } return(UpdateMesh(transform, sampledPtsOnSpline, sampledPtsOffSpline, customData, spline._closed, ref mesh)); }
public static void ExtendSpline(Spline spline) { var newPoint = SplinePointEditor.AddSplinePointAfter(spline.transform); Undo.RegisterCreatedObjectUndo(newPoint, "Add Crest Spline Point"); }
public static bool GenerateMeshFromSpline(Spline.Spline spline, Transform transform, int subdivisions, float radius, int smoothingIterations, Vector2 customDataDefault, ref Mesh mesh) { return(GenerateMeshFromSpline <SplinePointDataNone>(spline, transform, subdivisions, radius, smoothingIterations, customDataDefault, ref mesh)); }