/// <summary> /// A correct implementation of shrink tile. /// hmmmm, is "scaling" even well defined in non-E geometries? Am I really looking for an equidistant curve? /// Sadly, even if I figure out what is best, I fear changing out usage of the incorrect one below in MagicTile, /// because of the possibility of breaking existing puzzles. /// </summary> internal static void ShrinkTileCorrect( ref Tile tile, double shrinkFactor ) { System.Func<Vector3D,double,Vector3D> scaleFunc = null; switch( tile.Geometry ) { case Geometry.Euclidean: { scaleFunc = ( v, s ) => v * s; break; } case Geometry.Spherical: { scaleFunc = ( v, s ) => { // Move to spherical norm, scale, then move back to euclidean. double scale = Spherical2D.s2eNorm( ( Spherical2D.e2sNorm( v.Abs() ) * s ) ); v.Normalize(); return v * scale; }; break; } case Geometry.Hyperbolic: { throw new System.NotImplementedException(); } } }
public Tile Clone() { Tile newTile = new Tile(); newTile.Boundary = Boundary.Clone(); newTile.Drawn = Drawn.Clone(); newTile.VertexCircle = VertexCircle.Clone(); newTile.Geometry = Geometry; return newTile; }
/// <summary> /// This will trim back the tile using an equidistant curve. /// It assumes the tile is at the origin. /// </summary> internal static void ShrinkTile( ref Tile tile, double shrinkFactor ) { // This code is not correct in non-Euclidean cases! // But it works reasonable well for small shrink factors. // For example, you can easily use this function to grow a hyperbolic tile beyond the disk. Mobius m = new Mobius(); m.Hyperbolic( tile.Geometry, new Vector3D(), shrinkFactor ); tile.Drawn.Transform( m ); return; /* // ZZZ // Wow, all the work I did below was subsumed by 4 code lines above! // I can't bring myself to delete it yet. switch( tile.Geometry ) { case Geometry.Spherical: { List<Tile> clipped = new List<Tile>(); clipped.Add( tile ); Polygon original = tile.Drawn.Clone(); foreach( Segment seg in original.Segments ) { Debug.Assert( seg.Type == SegmentType.Arc ); if( true ) { // Unproject to sphere. Vector3D p1 = Spherical2D.PlaneToSphere( seg.P1 ); Vector3D p2 = Spherical2D.PlaneToSphere( seg.P2 ); // Get the poles of the GC, and project them to the plane. Vector3D pole1, pole2; Spherical2D.GreatCirclePole( p1, p2, out pole1, out pole2 ); pole1 = Spherical2D.SphereToPlane( pole1 ); pole2 = Spherical2D.SphereToPlane( pole2 ); // Go hyperbolic, dude. double scale = 1.065; // ZZZ - needs to be configurable. Complex fixedPlus = pole1; Mobius hyperbolic = new Mobius(); hyperbolic.Hyperbolic( tile.Geometry, fixedPlus, scale ); Vector3D newP1 = hyperbolic.Apply( seg.P1 ); Vector3D newMid = hyperbolic.Apply( seg.Midpoint ); Vector3D newP2 = hyperbolic.Apply( seg.P2 ); Circle trimmingCircle = new Circle(); trimmingCircle.From3Points( newP1, newMid, newP2 ); Slicer.Clip( ref clipped, trimmingCircle, true ); } else { // I think this block has logic flaws, but strangely it seems to work, // so I'm leaving it in commented out for posterity. Vector3D p1 = seg.P1; Vector3D mid = seg.Midpoint; Vector3D p2 = seg.P2; //double offset = .1; double factor = .9; double f1 = Spherical2D.s2eNorm( (Spherical2D.e2sNorm( p1.Abs() ) * factor) ); double f2 = Spherical2D.s2eNorm( (Spherical2D.e2sNorm( mid.Abs() ) * factor) ); double f3 = Spherical2D.s2eNorm( (Spherical2D.e2sNorm( p2.Abs() ) * factor) ); p1.Normalize(); mid.Normalize(); p2.Normalize(); p1 *= f1; mid *= f2; p2 *= f3; Circle trimmingCircle = new Circle(); trimmingCircle.From3Points( p1, mid, p2 ); Slicer.Clip( ref clipped, trimmingCircle, true ); } } Debug.Assert( clipped.Count == 1 ); tile = clipped[0]; return; } case Geometry.Euclidean: { double scale = .95; Mobius hyperbolic = new Mobius(); hyperbolic.Hyperbolic( tile.Geometry, new Vector3D(), scale ); tile.Drawn.Transform( hyperbolic ); return; } case Geometry.Hyperbolic: { List<Tile> clipped = new List<Tile>(); clipped.Add( tile ); Circle infinity = new Circle(); infinity.Radius = 1.0; Polygon original = tile.Drawn.Clone(); foreach( Segment seg in original.Segments ) { Debug.Assert( seg.Type == SegmentType.Arc ); Circle segCircle = seg.GetCircle(); // Get the intersection points with the disk at infinity. Vector3D p1, p2; int count = Euclidean2D.IntersectionCircleCircle( infinity, segCircle, out p1, out p2 ); Debug.Assert( count == 2 ); Vector3D mid = seg.Midpoint; //mid *= 0.75; // ZZZ - needs to be configurable. double offset = .03; double f1 = DonHatch.h2eNorm( DonHatch.e2hNorm( mid.Abs() ) - offset ); mid.Normalize(); mid *= f1; Circle trimmingCircle = new Circle(); trimmingCircle.From3Points( p1, mid, p2 ); Slicer.Clip( ref clipped, trimmingCircle, false ); } Debug.Assert( clipped.Count == 1 ); tile = clipped[0]; return; } } */ }
public void CalculateFromTwoPolygons( Tile home, Polygon boundaryPolygon, Geometry g ) { CalculateFromTwoPolygonsInternal( home.Boundary, boundaryPolygon, home.VertexCircle, g ); }
private static void MeshEdges( Mesh mesh, Tile tile, HashSet<Vector3D> completed, Polygon boundary ) { for( int i=0; i<tile.Boundary.Segments.Count; i++ ) { Segment boundarySeg = tile.Boundary.Segments[i]; Segment d1 = tile.Drawn.Segments[i]; if( completed.Contains( boundarySeg.Midpoint ) ) continue; // Find the incident segment. Segment seg2 = null, d2 = null; foreach( Tile incident in tile.EdgeIncidences ) { for( int j=0; j<incident.Boundary.Segments.Count; j++ ) { if( boundarySeg.Midpoint == incident.Boundary.Segments[j].Midpoint ) { seg2 = incident.Boundary.Segments[j]; d2 = incident.Drawn.Segments[j]; break; } } if( seg2 != null ) break; } // Found our incident edge? bool foundIncident = seg2 != null; if( !foundIncident ) seg2 = d2 = boundarySeg; // Do the endpoints mismatch? if( boundarySeg.P1 != seg2.P1 ) { Segment clone = d2.Clone(); clone.Reverse(); d2 = clone; } // Add the two vertices (careful of orientation). if( foundIncident ) { CheckAndAdd( mesh, new Mesh.Triangle( boundarySeg.P1, d1.P1, d2.P1 ), boundary ); CheckAndAdd( mesh, new Mesh.Triangle( boundarySeg.P2, d2.P2, d1.P2 ), boundary ); } int num = 1 + (int)(d1.Length * m_divisions); Vector3D[] list1 = d1.Subdivide( num ); Vector3D[] list2 = d2.Subdivide( num ); for( int j=0; j<num; j++ ) { CheckAndAdd( mesh, new Mesh.Triangle( list1[j], list1[j + 1], list2[j + 1] ), boundary ); CheckAndAdd( mesh, new Mesh.Triangle( list2[j], list1[j], list2[j + 1] ), boundary ); } completed.Add( boundarySeg.Midpoint ); } }
/// <summary> /// Will clone the tile, transform it and add it to our tiling. /// </summary> private bool TransformAndAdd( Tile tile ) { // Will we want to include it? if( !tile.IncludeAfterMobius( this.TilingConfig.M ) ) return false; Tile clone = tile.Clone(); clone.Transform( this.TilingConfig.M ); m_tiles.Add( clone ); this.TilePositions[clone.Boundary.Center] = clone; return true; }
/// <summary> /// Calculates an isometry by taking a tile boundary polygon to a home. /// </summary> public void CalculateFromTwoPolygons( Tile home, Tile tile, Geometry g ) { Polygon poly = tile.Boundary; CalculateFromTwoPolygons( home, poly, g ); }
/// <summary> /// This will fill out all the tiles with the isometry that will take them back to a home tile. /// </summary> private static void FillOutIsometries( Tile home, List<Tile> tiles, Geometry g ) { foreach( Tile tile in tiles ) tile.Isometry.CalculateFromTwoPolygons( home, tile, g ); }
/// <summary> /// This will return whether we'll be a new tile after reflecting through a segment. /// This allows us to do the check without having to do all the work of reflecting the entire tile. /// </summary> public bool NewTileAfterReflect( Tile t, Segment s, Dictionary<Vector3D, bool> completed ) { /* This was too slow! Polygon newPolyBoundary = t.Boundary.Clone(); newPolyBoundary.Reflect( s ); Vector3D testCenter = this.TilingConfig.M.Apply( newPolyBoundary.Center );*/ CircleNE newVertexCircle = t.VertexCircle.Clone(); newVertexCircle.Reflect( s ); Vector3D testCenter = this.TilingConfig.M.Apply( newVertexCircle.CenterNE ); return !completed.ContainsKey( testCenter ); }
public static Tile CreateBaseTile( TilingConfig config ) { Polygon boundary = new Polygon(), drawn = new Polygon(); boundary.CreateRegular( config.P, config.Q ); drawn = boundary.Clone(); //boundary.CreateRegular( 3, 10 ); //drawn.CreateRegular( 3, 8 ); //boundary.CreateRegular( 3, 7 ); //drawn = Heart(); //for( int i=0; i<drawn.NumSides; i++ ) // drawn.Segments[i].Center *= 0.1; // Good combos: // ( 5, 5 ), ( 10, 10 ) // ( 3, 10 ), ( 3, 9 ) // ( 6, 4 ), ( 6, 8 ) // ( 7, 3 ), ( 7, 9 ) Tile tile = new Tile( boundary, drawn, config.Geometry ); Tile.ShrinkTile( ref tile, config.Shrink ); return tile; }
private static void GetAssociatedTiling( EHoneycomb honeycomb, out Tiling tiling, out Tile baseTile ) { int p, q; GetPQ( honeycomb, out p, out q ); TilingConfig tilingConfig = new TilingConfig( p, q, maxTiles: m_params.MaxTiles ); tiling = new Tiling(); tiling.Generate( tilingConfig ); baseTile = Tiling.CreateBaseTile( tilingConfig ); }
private static double EdgeCenteredScale( Tile baseTile ) { Segment seg = baseTile.Boundary.Segments[0]; return 1.0 / ( seg.Length / 2 ); }