I asked ChatGPT for very fast FlowAlongCrv for Grasshopper

From 37s to 17ms is quite a big improvement!

At this speed I can adjust the Target curve in real time for complex geometry.

// C#
// G - your geometry, List Access
// BaseCrv, Type Hint: Curve
// TargetCrv, Type Hint: Curve
// Tol, Tolerance default 0.01
// Stretch, Boolean
// Flip, Boolean

using System;
using System.Collections;
using System.Collections.Generic;
using Rhino.Geometry;
using Rhino.Geometry.Morphs;

bool AsBool(object v, bool d)
{
  if (v == null) return d;
  if (v is bool b) return b;
  bool p; return bool.TryParse(v.ToString(), out p) ? p : d;
}

double AsDouble(object v, double d)
{
  if (v == null) return d;
  if (v is double dd) return dd;
  if (v is float ff) return (double)ff;
  double p; return double.TryParse(v.ToString(), out p) ? p : d;
}

List<GeometryBase> AsGeoList(object g)
{
  var res = new List<GeometryBase>();
  if (g == null) return res;

  var ie = g as IEnumerable;
  if (ie != null && !(g is GeometryBase))
  {
    foreach (var o in ie)
    {
      var gb = o as GeometryBase;
      if (gb != null) res.Add(gb);
    }
    if (res.Count > 0) return res;
  }

  var one = g as GeometryBase;
  if (one != null) res.Add(one);
  return res;
}

bool ShouldReverseTarget(Curve baseC, Curve targC, double tol)
{
  if (baseC.IsClosed && targC.IsClosed)
  {
    Plane pb, pt;
    if (baseC.TryGetPlane(out pb, tol) && targC.TryGetPlane(out pt, tol))
      return baseC.ClosedCurveOrientation(pb) != targC.ClosedCurveOrientation(pt);
  }
  return (baseC.TangentAtStart * targC.TangentAtStart) < 0.0;
}

// Build a stable frame: X=tangent, Z=WorldUp, Y=ZxX (side)
// If tangent is parallel to WorldUp, fallback to WorldY as up.
Plane StableSideFrameAtStart(Curve c)
{
  c.Domain = new Interval(0, 1);
  var o = c.PointAtStart;

  Vector3d x = c.TangentAtStart;
  if (!x.Unitize()) x = Vector3d.XAxis;

  Vector3d up = Vector3d.ZAxis;
  if (Math.Abs(x * up) > 0.999) up = Vector3d.YAxis; // avoid near-parallel

  Vector3d y = Vector3d.CrossProduct(up, x);
  if (!y.Unitize()) y = Vector3d.YAxis;

  Vector3d z = Vector3d.CrossProduct(x, y);
  if (!z.Unitize()) z = Vector3d.ZAxis;

  return new Plane(o, x, y); // z is implied by x,y
}

// =================== MAIN ===================
// Inputs: G, BaseCrv, TargetCrv, Stretch, Flip, Tol
// Output: A

if (BaseCrv == null || TargetCrv == null) { A = null; return; }

double tol     = AsDouble(Tol, 0.01);
bool stretch   = AsBool(Stretch, true);
bool flip      = AsBool(Flip, false);

// FlowSpaceMorph uses preventStretching (inverse of "stretch")
bool preventStretching = !stretch;

// Duplicate curves so edits don't affect upstream
Curve baseC = BaseCrv.DuplicateCurve();
Curve targC = TargetCrv.DuplicateCurve();

baseC.Domain = new Interval(0, 1);
targC.Domain = new Interval(0, 1);

// Lock direction independent of Stretch
bool reverseBase = false;
bool reverseTarg = ShouldReverseTarget(baseC, targC, tol);

// Build morph (correct ctor)
var morph = new FlowSpaceMorph(baseC, targC, reverseBase, reverseTarg, preventStretching);
morph.Tolerance = tol;
morph.PreserveStructure = true;

// Flip transform (mirror across XZ plane of a stable frame => flips side Y)
Transform xFlip = Transform.Identity;
if (flip)
{
  Plane f = StableSideFrameAtStart(baseC);
  Plane mirrorPlane = new Plane(f.Origin, f.XAxis, f.ZAxis); // XZ plane => flips Y (side)
  xFlip = Transform.Mirror(mirrorPlane);
}

// Morph geometry
var geos = AsGeoList(G);
var outGeos = new List<GeometryBase>(geos.Count);

foreach (var geo in geos)
{
  if (geo == null) { outGeos.Add(null); continue; }

  var dup = geo.Duplicate();

  if (flip) dup.Transform(xFlip);

  morph.Morph(dup);
  outGeos.Add(dup);
}

A = outGeos;

1 Like