public void AnimationSections(Settings config) { HoneycombDef imageData = new HoneycombDef(config.P, config.Q, config.R); int p = imageData.P, q = imageData.Q, r = imageData.R; string filename = imageData.FormatFilename(); Sphere[] mirrors = SimplexCalcs.Mirrors(p, q, r); double bounds = 1.0; //config.UhsBoundary.Bounds; bounds = 9.0; // Calculate the color scale. int size = 200; CoxeterImages.Settings settings = new CoxeterImages.Settings() { Honeycomb = imageData, Width = size, Height = size, Bounds = bounds, Mirrors = mirrors, FileName = imageData.FormatFilename(), }; CoxeterImages imageCalculator = new CoxeterImages(); //imageCalculator.AutoCalcScale( settings ); if (settings.ColorScaling < 1) { settings.ColorScaling = 15; } settings.ColorScaling = 11; Program.Log("\nGenerating sections..."); size = 500; settings.Width = size; settings.Height = size; settings.FileName = filename; double max = Spherical2D.e2sNorm(15); double min = Spherical2D.e2sNorm(1.0 / 15); DonHatch.e2hNorm(max); int numSteps = 1800; // 1 minute double step = (max - min) / numSteps; for (int i = 0; i < 1; i++) { Program.Log("\nSection " + i); imageCalculator.m_z = 1.0 / 0.5; Spherical2D.s2eNorm(min + step * i); DonHatch.h2eNorm(step * i); settings.FileName = string.Format("533_{0:D4}.png", i); imageCalculator.GenImage(settings); } }
public static void Create(HoneycombDef def, string filename) { int p = def.P; int q = def.Q; int r = def.R; double scale = 5.0; Vector3D cen = HoneycombPaper.InteriorPointBall; Sphere[] simplex = SimplexCalcs.Mirrors(p, q, r, moveToBall: false); // Apply transformations. simplex = simplex.Select(s => { Sphere.ScaleSphere(s, scale); return(H3Models.UHSToBall(s)); }).ToArray(); for (int i = 0; i < 4; i++) { if (simplex[i].IsPointInside(cen)) { simplex[i].Invert = true; } } Sphere[] simplexForColorScale = SimplexCalcs.Mirrors(p, q, r, moveToBall: true); CoxeterImages.Settings temp = HoneycombPaper.AutoCalcScale(def, simplexForColorScale); int maxDepth = (int)temp.ColorScaling; bool ball = true; bool dual = false; H3.Cell[] simplicesFinal = HoneycombPaper.GenCell(simplex, null, cen, ball, dual); simplicesFinal = simplicesFinal.Where(s => s.Depths[0] < 1).ToArray(); //simplicesFinal = simplicesFinal.Where( s => s.) // Output the facets. using (StreamWriter sw = File.CreateText(filename)) // We need to reuse this StreamWriter (vs. calling AppendSimplex) for performance. { sw.WriteLine("#include \"hyper_ball.pov\""); int[] include = new int[] { 0 }; foreach (H3.Cell cell in simplicesFinal) { Sphere[] facets = cell.Facets.Select(f => f.Sphere).ToArray(); int depth = cell.Depths[0] + 1; Color c = Coloring.ColorAlongHexagon(maxDepth, depth); PovRay.AddSimplex(sw, facets, cell.Center, include, filename, Coloring.ToVec(c)); } } }
private static Sphere[] Simplex(ref Vector3D cen) { int p = 7; int q = 3; int r = 3; // For vertex-centered. //Vector3D v = SimplexCalcs.VertexPointBall( p, q, r ); //v = H3Models.BallToUHS( v ); bool ball = true; Sphere[] simplex = SimplexCalcs.Mirrors(p, q, r, ref cen, moveToBall: ball /*, scaling: 1.0/v.Abs()*/); return(simplex); }
public static void OneImage(Settings config, double t = 0.0) { HoneycombDef imageData = new HoneycombDef(config.P, config.Q, config.R); int p = imageData.P, q = imageData.Q, r = imageData.R; string filename = imageData.FormatFilename(); //if( File.Exists( filename ) ) // return; Sphere[] mirrors = SimplexCalcs.Mirrors(p, q, r); double bounds = config.UhsBoundary.Bounds; // Calculate the color scale. int size = 200; CoxeterImages.Settings settings = new CoxeterImages.Settings() { Honeycomb = imageData, Width = size, Height = size, Bounds = bounds, Mirrors = mirrors, FileName = imageData.FormatFilename(), }; CoxeterImages imageCalculator = new CoxeterImages(); imageCalculator.AutoCalcScale(settings); if (settings.ColorScaling < 1) { settings.ColorScaling = 15; } Program.Log("\nGenerating full image..."); size = 500; settings.Width = config.UhsBoundary.ImageWidth; settings.Height = config.UhsBoundary.ImageHeight; settings.FileName = filename; imageCalculator.GenImage(settings, t); }
private static void CreateSimplex(HoneycombDef imageData) { int p = imageData.P; int q = imageData.Q; int r = imageData.R; Vector3D cen = InteriorPointBall; bool ball = true; Sphere[] simplex = SimplexCalcs.Mirrors(p, q, r, ref cen, moveToBall: ball); // Offset as we do for the boundary images. //Sphere s = H3Models.UHSToBall( simplex[0] ); //s = CoxeterImages.GeodesicOffset( s, 0.02, ball: true ); if (m_toKlein) { simplex = simplex.Select(s => H3Models.BallToKlein(s)).ToArray(); } int[] include = new int[] { 0, 1, 2, 3 }; // All facets //int[] include = new int[] { 1 }; File.Delete("simplex.pov"); PovRay.AppendSimplex(simplex, cen, include, "simplex.pov"); bool includeEdges = false; if (includeEdges) { H3.Cell.Edge[] edges = SimplexCalcs.SimplexEdgesUHS(p, q, r); PovRay.WriteEdges(new PovRay.Parameters { Halfspace = true, AngularThickness = 0.03 }, Geometry.Hyperbolic, edges, "simplex.pov", append: true); } }
private static void OneAnimationFrame(HoneycombDef imageData, string filename, double colorScaling, double t = 0.0) { int p = imageData.P, q = imageData.Q, r = imageData.R; Sphere[] mirrors = SimplexCalcs.Mirrors(p, q, r); int size = 750; size = 1024; CoxeterImages.Settings settings = new CoxeterImages.Settings() { Honeycomb = imageData, Width = size * 2, Height = size, Bounds = 1.0, Mirrors = mirrors, FileName = filename, }; CoxeterImages imageCalculator = new CoxeterImages(); settings.ColorScaling = colorScaling; imageCalculator.GenImage(settings, t); }
// CHEAT! (would be better to do a geometrical construction) // We are going to iterate to the starting point that will make all edge lengths the same. public static Vector3D IterateToStartingPoint(HoneycombDef?def, int[] activeMirrors, Simplex simplex) { if (activeMirrors.Length == 1) { return(simplex.Verts[activeMirrors[0]]); } // We are minimizing the output of this function, // because we want all edge lengths to be as close as possible. // Input vector should be in the Ball Model. Func <Vector3D, double> diffFunc = v => { List <double> lengths = new List <double>(); for (int i = 0; i < activeMirrors.Length; i++) { Vector3D reflected = simplex.ReflectInFacet(v, activeMirrors[i]); lengths.Add(H3Models.Ball.HDist(v, reflected)); } double result = 0; double average = lengths.Average(); foreach (double length in lengths) { result += Math.Abs(length - average); } if (Infinity.IsInfinite(result)) { result = double.PositiveInfinity; } return(result); }; // So that we can leverage Euclidean barycentric coordinates, we will first convert our simplex to the Klein model. // We will need to take care to properly convert back to the Ball as needed. Vector3D[] kleinVerts = simplex.Verts.Select(v => HyperbolicModels.PoincareToKlein(v)).ToArray(); if (def != null) { HoneycombDef d = def.Value; Geometry vertexGeometry = Geometry2D.GetGeometry(d.Q, d.R); if (vertexGeometry == Geometry.Hyperbolic) { kleinVerts[3] = SimplexCalcs.VertexPointKlein(d.P, d.Q, d.R); } } // Normalizing barycentric coords amounts to making sure the 4 coords add to 1. Func <Vector3D, Vector3D> baryNormalize = b => { return(b / (b.X + b.Y + b.Z + b.W)); }; // Bary Coords to Euclidean Func <Vector3D[], Vector3D, Vector3D> baryToEuclidean = (kv, b) => { Vector3D result = kv[0] * b.X + kv[1] * b.Y + kv[2] * b.Z + kv[3] * b.W; return(result); }; // Our starting barycentric coords (halfway between all active mirrors). Vector3D bary = new Vector3D(); foreach (int a in activeMirrors) { bary[a] = 0.5; } bary = baryNormalize(bary); // For each iteration, we'll shrink this search offset. // NOTE: The starting offset and decrease factor I'm using don't guarantee convergence, // but it seems to be working pretty well (even when varying these parameters). //double searchOffset = 1.0 - bary[activeMirrors[0]]; //double searchOffset = bary[activeMirrors[0]]; double factor = 1.5; // Adjusting this helps get some to converge, e.g. 4353-1111 double searchOffset = bary[activeMirrors[0]] / factor; double min = double.MaxValue; int iterations = 1000; for (int i = 0; i < iterations; i++) { min = diffFunc(HyperbolicModels.KleinToPoincare(baryToEuclidean(kleinVerts, bary))); foreach (int a in activeMirrors) { Vector3D baryTest1 = bary, baryTest2 = bary; baryTest1[a] += searchOffset; baryTest2[a] -= searchOffset; baryTest1 = baryNormalize(baryTest1); baryTest2 = baryNormalize(baryTest2); double t1 = diffFunc(HyperbolicModels.KleinToPoincare(baryToEuclidean(kleinVerts, baryTest1))); double t2 = diffFunc(HyperbolicModels.KleinToPoincare(baryToEuclidean(kleinVerts, baryTest2))); if (t1 < min) { min = t1; bary = baryTest1; } if (t2 < min) { min = t2; bary = baryTest2; } } if (Tolerance.Equal(min, 0.0, 1e-14)) { System.Console.WriteLine(string.Format("Converged in {0} iterations.", i)); break; } searchOffset /= factor; } if (!Tolerance.Equal(min, 0.0, 1e-14)) { System.Console.WriteLine("Did not converge: " + min); // Be a little looser before thrown an exception. if (!Tolerance.Equal(min, 0.0, 1e-12)) { System.Console.ReadKey(true); //throw new System.Exception( "Boo. We did not converge." ); return(Vector3D.DneVector()); } } Vector3D euclidean = baryToEuclidean(kleinVerts, bary); return(HyperbolicModels.KleinToPoincare(euclidean)); }
public static H3.Cell.Edge[] OneHoneycombOrthoscheme(HoneycombDef def, int[] active, int baseHue, Settings settings = null) { // Setup parameters. int numEdges = 250000; if (settings != null) { active = settings.PovRay.Active; def = new HoneycombDef(settings.P, settings.Q, settings.R); numEdges = settings.PovRay.NumEdges; } CalcThickness(active); if (settings != null) { H3.m_settings.AngularThickness = settings.PovRay.EdgeWidth; // ZZZ - should really stop using that settings class. } string baseName = BaseName(def); string mirrorsString = ActiveMirrorsString(active); string suffix = "-" + mirrorsString; string fileName = baseName + suffix; if (ViewPath != null) { fileName += string.Format("_{0:D4}", ViewPath.Step); } if (File.Exists(fileName + ".pov")) { File.Delete(fileName + ".pov"); //Console.WriteLine( string.Format( "Skipping {0}", fileName ) ); //return; } Program.Log(string.Format("Building {0}", fileName)); // The wiki mirrors are labeled in the reverse of ours. Func <int, int> mapMirror = i => 3 - i; active = active.Select(i => mapMirror(i)).OrderBy(i => i).ToArray(); Simplex simplex = new Simplex(); simplex.Facets = SimplexCalcs.Mirrors(def.P, def.Q, def.R); simplex.Verts = SimplexCalcs.VertsBall(def.P, def.Q, def.R); Vector3D startingPoint = IterateToStartingPoint(def, active, simplex); if (startingPoint.DNE) { return(null); } List <H3.Cell.Edge> startingEdges = new List <H3.Cell.Edge>(); foreach (int a in active) { Vector3D reflected = simplex.ReflectInFacet(startingPoint, a); startingEdges.Add(new H3.Cell.Edge(startingPoint, reflected)); //startingEdges.Add( new H3.Cell.Edge( simplex.Verts[0], simplex.Verts[3] ) ); // Used for Borromean Rings complement image. } if (false) { Vector3D[] kv = simplex.Verts.Select(v => HyperbolicModels.PoincareToKlein(v)).ToArray(); kv[3] = SimplexCalcs.VertexPointKlein(def.P, def.Q, def.R); Vector3D t = (kv[3] - kv[0]) * 0.5; Sphere gSphere = H3Models.Ball.OrthogonalSphereInterior(HyperbolicModels.KleinToPoincare(t)); gSphere = H3Models.BallToKlein(gSphere); Vector3D t2 = Euclidean3D.IntersectionPlaneLine(gSphere.Normal, gSphere.Offset, kv[3] - kv[2], kv[2]); //t2 = kv[2] + ( kv[3] - kv[2]) * 0.5; t = HyperbolicModels.KleinToPoincare(t); t2 = HyperbolicModels.KleinToPoincare(t2); startingEdges.Add(new H3.Cell.Edge(t, t2)); startingEdges.Add(new H3.Cell.Edge(t, simplex.ReflectInFacet(t, 3))); } // If we are doing a view path, transform our geometry. if (ViewPath != null) { //Vector3D p = new Vector3D( 0, 0, .5 ); Vector3D p = new Vector3D(0.08, 0.12, 0.07); simplex.Facets = simplex.Facets.Select(f => H3Models.Transform_PointToOrigin(f, p)).ToArray(); simplex.Verts = simplex.Verts.Select(v => H3Models.Transform_PointToOrigin(v, p)).ToArray(); startingEdges = startingEdges.Select(e => new H3.Cell.Edge( H3Models.Transform_PointToOrigin(e.Start, p), H3Models.Transform_PointToOrigin(e.End, p))).ToList(); } SetupBaseHue(fileName, mirrorsString, baseHue); Recurse.m_background = baseHue == -1 ? new Vector3D() : new Vector3D(baseHue, 1, .1); H3.Cell.Edge[] edges = Recurse.CalcEdgesSmart2(simplex.Facets, startingEdges.ToArray(), numEdges); //H3.Cell.Edge[] edges = Recurse.CalcEdges( simplex.Facets, startingEdges.ToArray(), // new Recurse.Settings() { ThreshType = Recurse.EdgeThreshType.Radial, Threshold = H3Models.Ball.FindLocationForDesiredRadius( settings.PovRay.EdgeWidth, 0.8/100 ) } ); //edges = edges.Where( e => e.Depths[0] % 2 == 1 ).ToArray(); // Used for Borromean Rings complement image. // Shapeways truncated 436. if (false) { if (true) { Mobius m = Mobius.Scale(1.0 / H3Models.UHS.ToE(Honeycomb.InRadius(def.P, def.Q, def.R))); double a = -Math.PI / 2 + Math.Asin(1 / Math.Sqrt(3)); edges = edges.Select(e => { Vector3D v1 = e.Start; Vector3D v2 = e.End; v1.RotateAboutAxis(new Vector3D(1, 0, 0), a); v2.RotateAboutAxis(new Vector3D(1, 0, 0), a); v1 = H3Models.Ball.ApplyMobius(m, v1); v2 = H3Models.Ball.ApplyMobius(m, v2); return(new H3.Cell.Edge(v1, v2)); }).ToArray(); double thresh = -.01; Vector3D looking = new Vector3D(0, 0, -1); edges = edges.Where(e => e.Start.Dot(looking) > thresh && e.End.Dot(looking) > thresh).ToArray(); Dictionary <H3.Cell.Edge, int> edgeDict = edges.ToDictionary(e => e, e => 1); H3.RemoveDanglingEdgesRecursive(edgeDict); edges = edgeDict.Keys.ToArray(); } else { Mobius m = Mobius.Scale(2); edges = edges.Select(e => { Vector3D v1 = e.Start; Vector3D v2 = e.End; v1 = H3Models.Ball.ApplyMobius(m, v1); v2 = H3Models.Ball.ApplyMobius(m, v2); return(new H3.Cell.Edge(v1, v2)); }).ToArray(); Dictionary <H3.Cell.Edge, int> edgeDict = edges.ToDictionary(e => e, e => 1); H3.RemoveDanglingEdgesRecursive(edgeDict); edges = edgeDict.Keys.ToArray(); } } //H3.m_settings.Output = H3.Output.STL; //H3.m_settings.Scale = 50; H3.SaveToFile(fileName, edges, finite: true, append: true); bool doCells = false; H3.Cell[] cellsToHighlight = null; if (doCells) { int[] polyMirrors = new int[] { 1, 2, 3 }; active = active.Select(i => mapMirror(i)).OrderBy(i => i).ToArray(); H3.Cell startingCell = PolyhedronToHighlight(Geometry.Hyperbolic, polyMirrors, simplex, startingPoint); cellsToHighlight = Recurse.CalcCells(simplex.Facets, new H3.Cell[] { startingCell }); H3.AppendFacets(fileName, cellsToHighlight); } return(edges); }
public static void Test() { HoneycombDef def = new HoneycombDef(5, 3, 4); Simplex simplex = new Simplex(); simplex.Facets = SimplexCalcs.Mirrors(def.P, def.Q, def.R); // Simplices will be the "cells" H3.Cell.Facet[] simplexFacets = simplex.Facets.Select(m => new H3.Cell.Facet(m)).ToArray(); H3.Cell startingCell = new H3.Cell(simplexFacets); startingCell.AuxPoints = SimplexCalcs.VertsBall(def.P, def.Q, def.R); startingCell.Center = HoneycombPaper.InteriorPointBall; var cells = CalcCells(simplex.Facets, new H3.Cell[] { startingCell }); // Get all the cell centers HashSet <Vector3D> centers = new HashSet <Vector3D>(); foreach (var cell in cells) { Vector3D cellCen = cell.AuxPoints[0]; centers.Add(cellCen); } // Colors. Dictionary <double, Color> colors = new Dictionary <double, Color>(new DoubleEqualityComparer()); System.Random rand = new System.Random(0); // Get all the in-spheres double inRad = startingCell.AuxPoints[1].Abs(); //inRad *= 1.16; List <Sphere> inSpheres = new List <Sphere>(); foreach (Vector3D c in centers) { Vector3D p = c; //SphericalModels.GnomonicToStereo( c ); Geometry g = Geometry.Hyperbolic; Vector3D cen; double rad; H3Models.Ball.DupinCyclideSphere(p, inRad, g, out cen, out rad); Sphere i = new Sphere(cen, rad); Color color; if (!colors.TryGetValue(c.Abs(), out color)) { Vector3D rgb = ColorUtil.CHSL2RGB(new Vector3D(rand.NextDouble() * 360, .5, .5)); rgb *= 255; color = Color.FromArgb(255, (int)rgb.X, (int)rgb.Y, (int)rgb.Z); colors[c.Abs()] = color; } i.Color = color; inSpheres.Add(i); } // Project sphere to unit sphere. List <Circle3D> circlesOnUnitSphere = new List <Circle3D>(); foreach (Sphere i in inSpheres) { if (i.Center.IsOrigin || i.Center.DNE || Infinity.IsInfinite(i.Center)) { continue; } Sphere orthogonal = new Sphere(new Vector3D(), RadiusOrthogonal(i)); Circle3D c = orthogonal.Intersection(i); // We need to scale this based on the size of the orthogonal sphere. c.Center /= orthogonal.Radius; c.Radius /= orthogonal.Radius; c.Color = i.Color; circlesOnUnitSphere.Add(c); } Circle3D unit = new Circle3D(); //circlesOnUnitSphere.Add( unit ); ProjectAndSave(circlesOnUnitSphere); }
private static void CreateCellPovRay(HoneycombDef def, string filename, double t = 0) { int p = def.P; int q = def.Q; int r = def.R; //Vector3D trans = new Vector3D( 1.0/3, 0 ) * (2 + 2 * Math.Sin( Math.PI / 6 )) * t; //double scale = 1.8; Vector3D trans = new Vector3D(); double scale = 1.0; Vector3D[] sVerts = null; // SimplexCalcs.VertsBall( p, q, r ); Vector3D vUHS = H3Models.BallToUHS(SimplexCalcs.VertexPointBall(p, q, r)); // Just did this for everything. Non-general position working better and will make all heads consistent. scale = 2.0; if (Geometry2D.GetGeometry(q, r) != Geometry.Hyperbolic) // Vertex-centered if possible { scale = 1.0 / vUHS.Z; } //else if( Geometry2D.GetGeometry( p, q ) == Geometry.Hyperbolic ) // Make the biggest head somewhat smaller. // scale = 2.0; Vector3D cen = InteriorPointBall; /*var kleinVerts = sVerts.Select( v => HyperbolicModels.PoincareToKlein( v ) ); * Vector3D avg = new Vector3D(); * foreach( Vector3D v in kleinVerts ) * avg += v; * avg /= kleinVerts.Count(); * Vector3D cen = HyperbolicModels.KleinToPoincare( avg );*/ cen = H3Models.BallToUHS(cen); cen += trans; //cen *= scale; cen = H3Models.UHSToBall(cen); Sphere[] simplex = SimplexCalcs.Mirrors(p, q, r, moveToBall: false); // Apply transformations. simplex = simplex.Select(s => { Sphere.TranslateSphere(s, trans); Sphere.ScaleSphere(s, scale); return(H3Models.UHSToBall(s)); }).ToArray(); for (int i = 0; i < 4; i++) { if (simplex[i].IsPointInside(cen)) { simplex[i].Invert = true; } } Sphere[] simplexForColorScale = SimplexCalcs.Mirrors(p, q, r, moveToBall: true); CoxeterImages.Settings temp = AutoCalcScale(def, simplexForColorScale); int maxDepth = (int)temp.ColorScaling; //Random rand = new Random( p+q+r ); //int randOffset = rand.Next( maxDepth ); bool ball = true; bool dual = false; H3.Cell[] simplicesFinal = GenCell(simplex, null, cen, ball, dual); using (StreamWriter sw = File.CreateText(filename)) // We need to reuse this StreamWriter (vs. calling AppendSimplex) for performance. { sw.WriteLine("#include \"hyper_ball.pov\""); //int[] include = new int[] { 0, 1, 2, 3 }; int[] include = new int[] { 0 }; if (dual) { include = new int[] { 3 } } ; // Output the facets. foreach (H3.Cell cell in simplicesFinal) { Sphere[] facets = cell.Facets.Select(f => f.Sphere).ToArray(); if (m_toKlein) { facets = facets.Select(s => H3Models.BallToKlein(s)).ToArray(); } int depth = cell.Depths[0] + 1; Color c = Coloring.ColorAlongHexagon(maxDepth, depth); if (cell.Depths.Sum() % 2 == 0) { c = Coloring.Inverse(c); } PovRay.AddSimplex(sw, facets, cell.Center, include, filename, Coloring.ToVec(c)); } /*include = new int[] { 1, 2, 3 }; * foreach( H3.Cell cell in simplicesFinal ) * { * Sphere[] facets = cell.Facets.Select( f => f.Sphere ).ToArray(); * Color c = Color.Red; * Vector3D cv = Coloring.ToVec( c ); * cv.W = 0.9; * PovRay.AddSimplex( sw, facets, cell.Center, include, filename, cv ); * }*/ } // Output the edges/verts. bool includeEdges = false; if (includeEdges) { sVerts = sVerts.Select(v => { v = H3Models.BallToUHS(v); v += trans; v *= scale; return(H3Models.UHSToBall(v)); }).ToArray(); H3.Cell.Edge[] edges = Recurse.CalcEdges(simplex.Skip(1).ToArray(), new H3.Cell.Edge[] { new H3.Cell.Edge(sVerts[2], sVerts[3], order: false) }, new Recurse.Settings() { Threshold = 0.01 }); PovRay.WriteH3Edges(new PovRay.Parameters { AngularThickness = 0.01 }, edges, filename, append: true); HashSet <Vector3D> verts = new HashSet <Vector3D>(); foreach (H3.Cell.Edge e in edges) { verts.Add(e.End); } PovRay.WriteVerts(new PovRay.Parameters { AngularThickness = 0.02 }, Geometry.Hyperbolic, verts.ToArray(), filename, append: true); } }
/// <summary> /// This generates a honeycomb by reflecting in 4 mirrors of the fundamental simplex. /// This "new" method is now old. /// </summary> public static void OneHoneycombNew(HoneycombDef imageData) { int p = imageData.P; int q = imageData.Q; int r = imageData.R; double thickness = 0.05; double thicknessSpherical = Spherical2D.s2eNorm(thickness); double thicknessHyperbolic = R3.Math.DonHatch.h2eNorm(thickness); double threshold = 1; H3.Cell.Edge[] edges = null; H3.Cell[] cellsToHighlight = null; Sphere[] simplex = null; Vector3D vertex = new Vector3D(); Geometry g = Util.GetGeometry(p, q, r); if (g == Geometry.Spherical) { thickness = thicknessSpherical /*.07 for 333*/ /* 0.05for 433*/ /*.025 for 533,335*/; threshold = 10000; simplex = SimplexCalcs.MirrorsSpherical(p, q, r); vertex = SimplexCalcs.VertexSpherical(p, q, r); // Ugly special casing for 333, since it has a vertex project to infinity. if (p == 3 && q == 3 && r == 3) { SpecialCase333(); } } else if (g == Geometry.Euclidean) { thickness = thickness / 2; threshold = 1 /*20*/; //SimplexCalcs.CalcEScale(); simplex = SimplexCalcs.MirrorsEuclidean(); Vector3D[] verts = SimplexCalcs.VertsEuclidean(); vertex = verts[2]; } else { thickness = thicknessHyperbolic; threshold = 0.01; simplex = SimplexCalcs.Mirrors(p, q, r); Vector3D[] verts = SimplexCalcs.VertsBall(p, q, r); vertex = verts[2]; //Vector3D[] simplexVerts = SimplexCalcs.VertsBall( p, q, r ); //H3.Cell.Edge edge = new H3.Cell.Edge( simplexVerts[2], simplexVerts[3] ); //H3.Cell.Edge edge = SimplexCalcs.HoneycombEdgeBall( p, q, r ); //H3.Cell.Edge[] startingEdges = new H3.Cell.Edge[] { edge }; //H3.Cell.Edge[] edges = Recurse.CalcEdgesSmart2( simplex, startingEdges ); // Vertex Centered. bool vertexCentered = false; if (vertexCentered) { Vector3D v = SimplexCalcs.VertexPointBall(p, q, r); v = H3Models.BallToUHS(v); double scale = 1.0 / v.Abs(); edges = edges.Select(e => { Vector3D start = H3Models.UHSToBall(H3Models.BallToUHS(e.Start) * scale); Vector3D end = H3Models.UHSToBall(H3Models.BallToUHS(e.End) * scale); return(new H3.Cell.Edge(start, end)); }).ToArray(); } // Code to show endpoints of 535 /*using( StreamWriter sw = File.CreateText( "535_points.pov" ) ) * { * HashSet<Vector3D> verts = new HashSet<Vector3D>(); * foreach( H3.Cell.Edge e in edges ) * { * verts.Add( Sterographic.SphereToPlane( e.Start ) ); * verts.Add( Sterographic.SphereToPlane( e.End ) ); * } * * foreach( Vector3D vert in verts ) * if( !Infinity.IsInfinite( vert ) ) * sw.WriteLine( PovRay.Sphere( new Sphere() { Center = vert, Radius = 0.01 } ) ); * }*/ } // Recurse bool dual = false; { H3.Cell.Edge[] startingEdges = null; if (dual) { startingEdges = new H3.Cell.Edge[] { SimplexCalcs.DualEdgeBall(simplex) } } ; else { //startingEdges = new H3.Cell.Edge[] { SimplexCalcs.HoneycombEdgeBall( simplex, vertex ) }; Vector3D[] verts = SimplexCalcs.VertsEuclidean(); Vector3D v1 = verts[0] + 2 * verts[2]; // adjacent cube center Vector3D corner = verts[3]; startingEdges = new H3.Cell.Edge[] { new H3.Cell.Edge(v1, corner) }; } edges = Recurse.CalcEdges(simplex, startingEdges, new Recurse.Settings() { G = g, Threshold = threshold }); edges = edges.Where(e => { int sum = e.Depths.Count(d => d == 0); return(true); }).ToArray(); //CullHalfOfEdges( ref edges ); // No need to cull edges in spherical case. // This was just to generate some images for 350-cell paper. //edges = Cull120Cell( edges ); Simplex tet = new Simplex(); tet.Facets = simplex; if (dual) { H3.Cell.Edge[] oneDualCell = edges.Where(e => e.Depths[2] == 0).ToArray(); simplex = simplex.Skip(1).ToArray(); edges = Recurse.CalcEdges(simplex, oneDualCell, new Recurse.Settings() { G = g, Threshold = threshold }); int[] polyMirrors = new int[] { 0, 1, 3 }; H3.Cell startingCell = HoneycombGen.PolyhedronToHighlight(g, polyMirrors, tet, new Vector3D()); cellsToHighlight = Recurse.CalcCells(simplex, new H3.Cell[] { startingCell }); //cellsToHighlight = new H3.Cell[] { startingCell }; //cellsToHighlight = cellsToHighlight.Skip( 7 ).ToArray(); } else { int[] polyMirrors = new int[] { 1, 2, 3 }; H3.Cell startingCell = HoneycombGen.PolyhedronToHighlight(g, polyMirrors, tet, vertex); //cellsToHighlight = Recurse.CalcCells( simplex, new H3.Cell[] { startingCell } ); cellsToHighlight = new H3.Cell[] { startingCell }; } // Include just one cell? bool includeOne = false; if (includeOne) { edges = edges.Where(e => e.Depths[0] == 0).ToArray(); //cellsToHighlight = cellsToHighlight.Where( c => c.Depths[0] == 0 ).ToArray(); } } // Rotate bool rotate = false; if (rotate) { CompoundOfFive24Cells(ref edges); } // Write the file bool pov = true; if (pov) { string filename = string.Format("{0}{1}{2}.pov", p, q, r); PovRay.WriteEdges(new PovRay.Parameters() { AngularThickness = thickness }, g, edges, filename, append: false); //File.Delete( filename ); //PovRay.AppendFacets( cellsToHighlight, filename ); HashSet <Vector3D> verts = new HashSet <Vector3D>(); foreach (H3.Cell.Edge e in edges) { verts.Add(e.Start); verts.Add(e.End); } /*foreach( Vector3D v in verts ) * { * Vector3D t = v; * t.Normalize(); * t *= 0.9; * System.Diagnostics.Trace.WriteLine( string.Format( "light_source {{ <{0},{1},{2}> White*.2 }}", t.X, t.Y, t.Z ) ); * }*/ /* * // Include the standard pov stuff, so we can batch this. * string fileName = imageData.FormatFilename( string.Empty ); * using( StreamWriter sw = File.CreateText( fileName + ".pov" ) ) * { * sw.WriteLine( "#include \"C:\\Users\\hrn\\Documents\\roice\\povray\\paper\\H3.pov\"" ); * } * * bool dummy = true; // Doesn't matter for Pov-Ray, just Shapeways meshes. * H3.SaveToFile( fileName, edges, dummy, append: true ); */ } else { if (g == Geometry.Spherical) { edges = edges.Where(e => e.Start.Valid() && e.End.Valid() && !Infinity.IsInfinite(e.Start) && !Infinity.IsInfinite(e.End)).ToArray(); S3.EdgesToStl(edges); } else { throw new System.NotImplementedException(); } } }
private static void CompoundOfFive24Cells(ref H3.Cell.Edge[] edges) { List <H3.Cell.Edge> allEdges = new List <H3.Cell.Edge>(); Vector3D v600 = Sterographic.R3toS3(SimplexCalcs.VertexSpherical(3, 3, 5)); Vector3D v24 = Sterographic.R3toS3(SimplexCalcs.VertexSpherical(3, 4, 3)); Sphere[] mirrors600 = SimplexCalcs.MirrorsSpherical(3, 3, 5); double a24 = v24.AngleTo(Sterographic.R3toS3(new Vector3D())); double a600 = v600.AngleTo(Sterographic.R3toS3(new Vector3D())); Matrix4D m600 = Matrix4D.MatrixToRotateinCoordinatePlane(a600, 2, 3); Matrix4D m600_ = Matrix4D.MatrixToRotateinCoordinatePlane(-a600, 2, 3); Matrix4D m24 = Matrix4D.MatrixToRotateinCoordinatePlane(a24, 2, 3); Matrix4D m24_ = Matrix4D.MatrixToRotateinCoordinatePlane(-a24, 2, 3); double eLength = 2 * Math.PI / 10; // 600-cell edge length double a_id = Math.Asin(Math.Sin(eLength / 2) / Math.Sin(Math.PI / 3) * Math.Sin(Math.PI / 2)); eLength = 1.0 / Math.Sin(2 * Math.PI / 5); // icosahedron edge length double a_i = Math.Asin(Math.Sin(eLength / 2) / Math.Sin(Math.PI / 3) * Math.Sin(Math.PI / 5)); Func <Vector3D, Vector3D> rot600 = v => { v = Sterographic.R3toS3(v); v = m600.RotateVector(v); v = Sterographic.S3toR3(v); return(v); }; Func <Vector3D, int, Vector3D> rotOne = (v, idx) => { v = Sterographic.R3toS3(v); v = m24.RotateVector(v); v = Sterographic.S3toR3(v); v.RotateAboutAxis(new Vector3D(1, 0, 0), -a_id); // Vertex to cell center. v = Sterographic.R3toS3(v); v = m600_.RotateVector(v); v = Sterographic.S3toR3(v); List <int> reflections = new List <int>(); if (idx == 0) { reflections.Add(2); reflections.Add(1); reflections.Add(2); reflections.Add(0); reflections.Add(1); reflections.Add(2); reflections.Add(1); reflections.Add(2); } if (idx != 0) { reflections.Add(3); } if (idx == 2) { reflections.Add(1); reflections.Add(2); } if (idx == 3) { reflections.Add(2); reflections.Add(1); } if (idx == 4) { reflections.Add(1); reflections.Add(0); reflections.Add(1); reflections.Add(2); reflections.Add(0); reflections.Add(1); reflections.Add(0); reflections.Add(1); } foreach (int reflection in reflections) { v = mirrors600[reflection].ReflectPoint(v); } v = Sterographic.R3toS3(v); v = m600.RotateVector(v); v = Sterographic.S3toR3(v); //v.RotateAboutAxis( new Vector3D( 0, 0, 1 ), Math.PI/3 ); //v.RotateAboutAxis( new Vector3D( 1, 0, 0 ), -a_i*2 ); v = Sterographic.R3toS3(v); //v = m24_.RotateVector( v ); v = Sterographic.S3toR3(v); return(v); }; for (int i = 0; i < 5; i++) { //if( i == 0 ) // continue; allEdges.AddRange(edges.Select(e => { H3.Cell.Edge newEdge = new H3.Cell.Edge(rotOne(e.Start, i), rotOne(e.End, i)); //H3.Cell.Edge newEdge = new H3.Cell.Edge( rot600( e.Start ), rot600( e.End ) ); switch (i) { case 0: newEdge.Color = new Vector3D(1, 0, 0, 1); //newEdge.Color = new Vector3D( 1, 1, 1, 0 ); break; case 1: newEdge.Color = new Vector3D(0, 1, 0, 2); break; case 2: newEdge.Color = new Vector3D(0, 0, 1, 3); break; case 3: newEdge.Color = new Vector3D(1, 0, 1, 4); break; case 4: newEdge.Color = new Vector3D(0, 1, 1, 5); break; } return(newEdge); })); } edges = allEdges.ToArray(); //edges = edges.Where( e => Tolerance.Equal( 1, e.Start.Abs() ) || Tolerance.Equal( 1, e.End.Abs() ) ).ToArray(); HashSet <Vector3D> uniqueVerts = new HashSet <Vector3D>(); foreach (H3.Cell.Edge e in edges) { uniqueVerts.Add(e.Start); uniqueVerts.Add(e.End); } System.Diagnostics.Trace.WriteLine("Number of verts = " + uniqueVerts.Count); /*edges = edges.Where( e => * { * Vector3D v = Tolerance.Equal( 1, e.Start.Abs() ) ? e.End : e.Start; * if( v.Abs() >= 0.8 || v.Abs() <= 0.7 ) * return false; * * if( Tolerance.LessThanOrEqual( v.X, 0 ) || Tolerance.GreaterThanOrEqual( v.Y, 0 ) || Tolerance.GreaterThanOrEqual( v.Z, 0 ) ) * return false; * * return true; * } ).ToArray(); * * edges = edges.OrderBy( e => Tolerance.Equal( 1, e.Start.Abs() ) ? e.End.Abs() : e.Start.Abs() ).ToArray();*/ }
/// <summary> /// Create an STL file for a cell. /// Currently only works for cells with both hyperideal vertices and cells. /// </summary> public static void HoneycombHyperidealLegs(HoneycombDef def, int lod, Dictionary <Vector3D, H3.Cell> complete) { int p = def.P; int q = def.Q; int r = def.R; m_div = TextureHelper.SetLevels(lod); bool ball = false; Sphere[] simplex = SimplexCalcs.Mirrors(p, q, r, moveToBall: ball); H3.Cell.Edge[] edges; if (ball) { edges = SimplexCalcs.SimplexEdgesBall(p, q, r); } else { edges = SimplexCalcs.SimplexEdgesUHS(p, q, r); } // Two edges of one simplex facet. H3.Cell.Edge e1 = edges[2]; H3.Cell.Edge e2 = edges[3]; Vector3D[] points1, points2; if (ball) { points1 = H3Models.Ball.GeodesicPoints(e1.Start, e1.End, 2 * m_div); points2 = H3Models.Ball.GeodesicPoints(e2.Start, e2.End, 2 * m_div); } else { points1 = H3Models.UHS.GeodesicPoints(e1.Start, e1.End, 2 * m_div); points2 = H3Models.UHS.GeodesicPoints(e2.Start, e2.End, 2 * m_div); } Sphere cellSphere = simplex[0]; Sphere vertexSphere = simplex[3]; // Because one vertex the facet triangle is hyperideal, it will actually look like a square. List <Vector3D[]> allPoints = new List <Vector3D[]>(); for (int i = 0; i < points1.Length; i++) { Vector3D p1 = points1[i]; Vector3D p2 = points2[i]; Vector3D[] arcPoints; if (i == points1.Length - 1) //if( false ) { // NOTE: This arc is not generally geodesic! // Or is it? arcPoints = ball ? H3Models.Ball.GeodesicPoints(p1, p2, m_div) : H3Models.UHS.GeodesicPoints(p1, p2, m_div); /*Circle3D arc = cellSphere.Intersection( vertexSphere ); * double angleTot = (p1 - arc.Center).AngleTo( p2 - arc.Center ); * arcPoints = Shapeways.CalcArcPoints( arc.Center, arc.Radius, p1, arc.Normal, -angleTot, div );*/ } else { Circle3D c = Circle3D.FromCenterAnd2Points(cellSphere.Center, p1, p2); double angleTot = (p1 - c.Center).AngleTo(p2 - c.Center); arcPoints = Shapeways.CalcArcPoints(cellSphere.Center, cellSphere.Radius, p1, c.Normal, -angleTot, m_div); } //Vector3D[] arcPoints = new Vector3D[] { p1, p2 }; allPoints.Add(arcPoints); } // Create the triangles for the patch. Mesh mesh = new Mesh(); for (int i = 0; i < allPoints.Count - 1; i++) { Vector3D[] arc1 = allPoints[i]; Vector3D[] arc2 = allPoints[i + 1]; for (int j = 0; j < arc1.Length - 1; j++) { // Points of (i,j) box; Vector3D p1 = arc1[j]; Vector3D p2 = arc2[j]; Vector3D p3 = arc1[j + 1]; Vector3D p4 = arc2[j + 1]; Mesh.Triangle tri1 = new Mesh.Triangle(p1, p2, p3); Mesh.Triangle tri2 = new Mesh.Triangle(p2, p4, p3); // We need to thicken after reflecting around, otherwise we can't apply a min thickness. /*Sphere normal = cellSphere; * Mesh.Triangle[] thickened1 = Thicken( tri1, normal ); * Mesh.Triangle[] thickened2 = Thicken( tri2, normal ); * mesh.Triangles.AddRange( thickened1 ); * mesh.Triangles.AddRange( thickened2 );*/ mesh.Triangles.Add(tri1); mesh.Triangles.Add(tri2); } } // AuxPoints will be used for multiple things. // - The first two points are for an an that will fill the gap where there is a missing face. // - We'll also store the points for the 4 edges of our fundamental triangle. List <Vector3D> auxPoints = new List <Vector3D>(); { var edge1 = allPoints.First(); var edge2 = allPoints.Last(); List <Vector3D> edge3 = new List <Vector3D>(), edge4 = new List <Vector3D>(); for (int i = 0; i < allPoints.Count; i++) { edge3.Add(allPoints[i][0]); edge4.Add(allPoints[i][allPoints[i].Length - 1]); } edge4.Reverse(); auxPoints.Add(e1.Start); auxPoints.Add(e1.End); auxPoints.AddRange(edge1.Reverse()); auxPoints.AddRange(edge2); auxPoints.AddRange(edge3); auxPoints.AddRange(edge4); } Vector3D cen = HoneycombPaper.InteriorPointBall; /* Reorientation code. Move this elsewhere. * * // Face centered orientation. * bool faceCentered = false; * if( faceCentered ) * SimplexCalcs.PrepForFacetCentering( p, q, simplex, ref cen ); * * Mobius mUHS = SimplexCalcs.FCOrientMobius( p, q ); * Mobius mBall = HoneycombPaper.FCOrientMobius( H3Models.UHSToBall( cellSphere ) ); * * simplex = simplex.Select( s => * { * s = H3Models.UHSToBall( s ); * //H3Models.TransformInBall2( s, mBall ); * return s; * } ).ToArray(); * * * { * for( int i = 0; i < mesh.Triangles.Count; i++ ) * { * Mesh.Triangle tri = mesh.Triangles[i]; * * if( faceCentered ) * { * tri.a = mUHS.ApplyToQuaternion( tri.a ); * tri.b = mUHS.ApplyToQuaternion( tri.b ); * tri.c = mUHS.ApplyToQuaternion( tri.c ); * } * * tri.a = H3Models.UHSToBall( tri.a ); * tri.b = H3Models.UHSToBall( tri.b ); * tri.c = H3Models.UHSToBall( tri.c ); * * if( faceCentered ) * { * tri.a = H3Models.TransformHelper( tri.a, mBall ); * tri.b = H3Models.TransformHelper( tri.b, mBall ); * tri.c = H3Models.TransformHelper( tri.c, mBall ); * } * mesh.Triangles[i] = tri; * } * * if( faceCentered ) * cen = H3Models.TransformHelper( cen, mBall ); * } */ // Now we need to reflect around this fundamental patch. H3.Cell[] simplices = GenCell(simplex, mesh, cen, auxPoints.ToArray(), ball); // Existing cells take precedence. foreach (H3.Cell c in simplices) { Vector3D t = c.Center; H3.Cell dummy; if (!complete.TryGetValue(t, out dummy)) { complete[t] = c; } } }
private static void HoneycombFiniteVertexFig(HoneycombDef def, int lod, Dictionary <Vector3D, H3.Cell> complete) { int p = def.P; int q = def.Q; int r = def.R; double scale = 1.0; Vector3D vUHS = H3Models.BallToUHS(SimplexCalcs.VertexPointBall(p, q, r)); if (Geometry2D.GetGeometry(q, r) != Geometry.Hyperbolic) // Vertex-centered if possible { scale = 1.0 / vUHS.Z; } System.Func <Vector3D, Vector3D> trans = v => { v = H3Models.BallToUHS(v); v *= scale; v = H3Models.UHSToBall(v); return(v); }; bool ball = true; Sphere[] simplex = SimplexCalcs.Mirrors(p, q, r, moveToBall: ball); simplex = simplex.Select(s => { s = H3Models.BallToUHS(s); Sphere.ScaleSphere(s, scale); s = H3Models.UHSToBall(s); return(s); }).ToArray(); H3.Cell.Edge[] edges = SimplexCalcs.SimplexEdgesBall(p, q, r); // Two edges of the simplex facet. // NOTE: This contruction only works for material triangles, and matches the construction in the TextureHelper. m_div = TextureHelper.SetLevels(lod); int[] elementIndices = TextureHelper.TextureElements(1, lod); List <Vector3D> points = new List <Vector3D>(); H3.Cell.Edge e1 = edges[2]; H3.Cell.Edge e2 = edges[3]; Vector3D p1 = trans(e1.Start), p2 = trans(e1.End), p3 = trans(e2.End); Vector3D[] points1 = H3Models.Ball.GeodesicPoints(p2, p1, m_div); Vector3D[] points2 = H3Models.Ball.GeodesicPoints(p3, p1, m_div); for (int i = 0; i < m_div; i++) { points.AddRange(H3Models.Ball.GeodesicPoints(points1[i], points2[i], m_div - i)); } points.Add(p1); Mesh mesh = new Mesh(); for (int i = 0; i < elementIndices.Length / 3; i++) { int idx1 = i * 3; int idx2 = i * 3 + 1; int idx3 = i * 3 + 2; Vector3D v1 = points[elementIndices[idx1]]; Vector3D v2 = points[elementIndices[idx2]]; Vector3D v3 = points[elementIndices[idx3]]; mesh.Triangles.Add(new Mesh.Triangle(v1, v2, v3)); } // AuxPoints will be used for multiple things. // - The first is a definition point for a face, so we can check for duplicates. // - We'll also store the points for the 3 edges of our fundamental triangle. List <Vector3D> auxPoints = new List <Vector3D>(); { auxPoints.Add((p1 + p2 + p3) / 3); auxPoints.AddRange(points1); auxPoints.AddRange(points2.Reverse()); auxPoints.AddRange(H3Models.Ball.GeodesicPoints(points2[0], points1[0], m_div)); } Vector3D cen = HoneycombPaper.InteriorPointBall; H3.Cell[] simplices = GenCell(simplex, mesh, cen, auxPoints.ToArray(), ball); // Existing cells take precedence. foreach (H3.Cell c in simplices) { Vector3D t = c.AuxPoints[0]; H3.Cell dummy; if (!complete.TryGetValue(t, out dummy)) { complete[t] = c; } } }
public void Gen(int p, int q, int r) { Geometry g = Util.GetGeometry(p, q, r); if (g != Geometry.Spherical) { throw new System.Exception("Point group code only for spherical geometry."); } Simplex simplex = new Simplex(); simplex.Facets = SimplexCalcs.Mirrors(p, q, r); Vector3D cen = new Vector3D(); Vector3D faceCenter = SimplexCalcs.FaceCenterSpherical(p, q, r); Vector3D edgeMid = SimplexCalcs.EdgeMidpointSpherical(p, q, r); Vector3D vertex = SimplexCalcs.VertexSpherical(p, q, r); List <Vector3D> startingPoles = new List <Vector3D>(); startingPoles.Add(Sterographic.S3toR3(GreatSphere.FromSphere(simplex.Facets[0]).Pole)); GreatSphere[] spheres = CalcSpheres(simplex.Facets, startingPoles.ToArray()); string filename = "point_group.pov"; using (StreamWriter sw = File.CreateText(filename)) { //foreach( var greatSphere in spheres ) //sw.WriteLine( PovRay.Sphere( greatSphere.ToSphere() ) ); } /* * List<H3.Cell.Edge> startingEdges = new List<H3.Cell.Edge>(); * startingEdges.Add( new H3.Cell.Edge( cen, faceCenter ) ); * H3.Cell.Edge[] edges = Recurse.CalcEdges( simplex.Facets, startingEdges.ToArray(), new Recurse.Settings() { G = Geometry.Spherical, Threshold = 0.001 } ); * //edges = edges.Where( e => !( Infinity.IsInfinite( e.Start ) || Infinity.IsInfinite( e.End ) ) ).ToArray(); * * PovRay.WriteEdges( new PovRay.Parameters() { AngularThickness = 0.01 }, Geometry.Spherical, edges, filename, append: true ); */ double minRad = 0; double thick = 0.005; System.Func <Vector3D, Sphere> sizeFunc = v => { Vector3D c; double rad; H3Models.Ball.DupinCyclideSphere(v, thick / 2, g, out c, out rad); return(new Sphere() { Center = c, Radius = Math.Max(rad, minRad) }); }; // All geodesics List <Circle3D> startingCircles = new List <Circle3D>(); startingCircles.Add(GeodesicFrom2Points(cen, edgeMid)); Circle3D[] geodesics = CalcGeodesics(simplex.Facets, startingCircles.ToArray()); Vector3D color = new Vector3D(0, 0, 1); using (StreamWriter sw = File.CreateText(filename)) { Shapeways shapeways = new Shapeways(); foreach (Circle3D geodesic in geodesics) { Vector3D[] points; if (Infinity.IsInfinite(geodesic.Radius)) { double cutoff = 15; Segment seg = Segment.Line(geodesic.Normal * cutoff, geodesic.Normal * -cutoff); points = seg.Subdivide(42); } else { List <Vector3D> tempPoints = geodesic.Subdivide(150).ToList(); tempPoints.Add(tempPoints[0]); tempPoints.Add(tempPoints[1]); points = tempPoints.ToArray(); } List <Vector3D> ePoints = new List <Vector3D>(); List <double> eRadii = new List <double>(); foreach (Vector3D pNE in points) { Sphere sphere = sizeFunc(pNE); ePoints.Add(sphere.Center); eRadii.Add(sphere.Radius); } shapeways.AddCurve(ePoints.ToArray(), eRadii.ToArray()); sw.WriteLine(PovRay.EdgeSphereSweep(points, sizeFunc, color)); } STL.SaveMeshToSTL(shapeways.Mesh, "533.stl"); } }