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;
