private void TrackClosest( Cell cell )
{
// Are we tracking?
if( !m_trackClosest )
return;
Complex transformedCenter = m_mouseMotion.Isometry.Apply( cell.Boundary.Center.ToComplex() );
double distToOrigin = transformedCenter.Magnitude;
if( distToOrigin < m_closestDist )
{
m_closestDist = distToOrigin;
m_mouseMotion.Closest = cell;
}
}
private void DrawCellDirectly( Cell cell )
{
// What will be the closest to origin after panning transform?
TrackClosest( cell );
int lod = LOD( cell );
if( -1 == lod )
return;
if( m_settings.ShowOnlyFundamental && !cell.IsMaster )
return;
// ZZZ:performance - avoid cloning these by passing in mobius to drawing functions?
foreach( Sticker sticker in cell.Stickers )
{
if( sticker.Twisting )
continue;
Polygon p = sticker.Poly.Clone();
p.Transform( m_mouseMotion.Isometry );
Color color = GetStickerColor( sticker );
GLUtils.DrawConcavePolygon( p, color, GrabModelTransform() );
}
}
private void RenderDirectly( bool renderingTexture = false )
{
SetupStandardGLSettings( m_settings.ColorTileEdges );
if( !renderingTexture )
GL.Disable( EnableCap.Texture2D );
// We use the stencil buffer,
// and need the depth buffer enabled as well.
GL.ClearStencil( 0 );
GL.Clear( ClearBufferMask.StencilBufferBit );
GL.StencilMask( 0x01 );
GL.Enable( EnableCap.StencilTest );
GL.Disable( EnableCap.DepthTest );
// Track what is close to the center.
ResetClosest();
foreach( Cell master in m_puzzle.MasterCells )
{
DrawCellDirectly( master );
foreach( Cell slave in m_puzzle.SlaveCells( master ) )
DrawCellDirectly( slave );
}
DrawMovingStickersDirectly();
// Draw a background disk if needed.
if( m_settings.SphericalModel == SphericalModel.Fisheye )
FillBackgroundExceptDisk();
GL.Disable( EnableCap.StencilTest );
}
private void ResetClosest()
{
m_closestDist = double.MaxValue;
m_mouseMotion.Closest = null;
m_mouseMotion.Template = m_puzzle.MasterCells.First();
}
private void PerformClick( ClickData clickData )
{
// Handle macros.
if( AltDown )
{
if( this.ShowAsSkew )
{
string message = "Sorry, macros not supported on skew polyhedra puzzles at this time.";
System.Windows.Forms.MessageBox.Show( message, "Unsupported", MessageBoxButtons.OK, MessageBoxIcon.Information );
}
// Get info about where we've clicked.
Vector3D? spaceCoordsNoMouseMotion;
Cell closest = FindClosestCell( clickData.X, clickData.Y, out spaceCoordsNoMouseMotion );
if( closest == null || !spaceCoordsNoMouseMotion.HasValue ) // ZZZ - make more robust
return;
// Starting a macro?
if( CtrlDown && clickData.Button == MouseButtons.Left )
{
Macro m = this.TwistHandler.m_workingMacro;
m.SetupMobius( closest, spaceCoordsNoMouseMotion.Value, m_puzzle, m_mouseMotion.Isometry.Reflected );
m.StartRecording();
m_status();
}
else
{
// We're executing a macro.
bool left = clickData.Button == MouseButtons.Left;
bool right = clickData.Button == MouseButtons.Right;
if( left || right )
{
Macro selected = m_selectedMacro();
if( selected == null )
return;
Macro transformedMacro = selected.Transform( closest, spaceCoordsNoMouseMotion.Value,
m_puzzle, m_mouseMotion.Isometry.Reflected );
this.TwistHandler.ApplyMacro( transformedMacro, right );
}
}
return;
}
//
// From here on down, we're doing normal twisting.
//
bool skewReverseTwist = false;
if( (ShowOnSurface || ShowAsSkew) && !RenderingDisks )
{
bool forPicking = true;
if( m_puzzle.AllTwistData.Count > 0 ) // Trying to do picking on tilings will cause issues.
RenderSurface( forPicking, clickData.X, clickData.Y, ref skewReverseTwist );
}
else
{
FindClosestTwistingCircles( clickData.X, clickData.Y );
}
if( m_puzzle.Config.IsToggling)
{
PerformTogglingClick(clickData);
}
if( m_closestTwistingCircles == null )
{
return;
}
SingleTwist twist = new SingleTwist();
twist.IdentifiedTwistData = m_closestTwistingCircles.IdentifiedTwistData;
twist.LeftClick = clickData.Button == MouseButtons.Left;
if( m_puzzle.Config.Earthquake )
{
twist.SliceMask = m_choppedPantsSeg / 2;
TwistData td = m_closestTwistingCircles;
Vector3D lookup = td.Pants.TinyOffset( m_choppedPantsSeg );
Vector3D reflected = td.Pants.Hexagon.Segments[m_choppedPantsSeg].ReflectPoint( lookup );
TwistData tdEarthQuake = m_puzzle.ClosestTwistingCircles( reflected );
twist.IdentifiedTwistDataEarthquake = tdEarthQuake.IdentifiedTwistData;
twist.SliceMaskEarthquake = tdEarthQuake.Pants.Closest( reflected ) / 2;
}
else
twist.SliceMask = this.SliceMaskEnsureSlice;
// Correction when clicking on mirrored tiles for non-orientable puzzles.
// We want the user to always see the tiles they left-click turn CCW.
if( m_mouseMotion.Isometry.Reflected ^ m_closestTwistingCircles.Reverse )
twist.LeftClick = !twist.LeftClick;
// This correction is for skew puzzles.
if( skewReverseTwist )
twist.LeftClick = !twist.LeftClick;
this.TwistHandler.StartRotate( twist );
}
private Cell FindClosestCell( int X, int Y, out Vector3D? spaceCoordsNoMouseMotion )
{
spaceCoordsNoMouseMotion = SpaceCoordsNoMouseMotion( X, Y );
if( m_puzzle == null || !spaceCoordsNoMouseMotion.HasValue )
return null;
return m_puzzle.ClosestCell( spaceCoordsNoMouseMotion.Value );
}
internal Cell ClosestCell( Vector3D location )
{
NearTreeObject nearTreeObject;
bool found = m_cellNearTree.FindNearestNeighbor( out nearTreeObject, location, double.MaxValue );
if( !found )
return null;
Cell result = (Cell)nearTreeObject.ID;
return result;
}
private void AddMaster( Tile tile, Tiling tiling, PuzzleIdentifications identifications, Dictionary<Vector3D, Cell> completed )
{
Cell master = SetupCell( tiling.Tiles.First(), tile.Boundary, completed );
master.IndexOfMaster = m_masters.Count;
// Paranoia.
if( 0 != this.Config.ExpectedNumColors &&
master.IndexOfMaster >= this.Config.ExpectedNumColors )
{
//Debug.Assert( false );
// It will already have an invalid index.
master.IndexOfMaster = -1;
return;
}
m_masters.Add( master );
// This is to help with recentering on puzzles constructed via group relations.
// We need to recurse deeper for some of them, but just for the slaves of the central tile.
Tile template = tiling.Tiles.First();
TilingPositions positions = null;
if( master.IndexOfMaster == 0 && UsingRelations )
{
tiling = null;
positions = new TilingPositions();
positions.Build( new TilingConfig( Config.P, Config.Q, maxTiles: Config.NumTiles * 5 ) );
}
// Now add all the slaves for this master.
List<Cell> parents = new List<Cell>();
parents.Add( master );
AddSlavesRecursive( master, parents, tiling, positions, template, identifications, completed );
}
private Cell ApplyOneIsometry( Cell master, Cell parent, Isometry identIsometry, Tiling tiling, TilingPositions positions, Tile template,
Dictionary<Vector3D, Cell> completed )
{
// Conjugate to get the identification relative to this parent.
// NOTE: When we don't conjugate, some cells near the boundary are missed being identified.
// I got around that by configuring the number of colors in the puzzle, and never adding more than that expected amount.
// That was maybe a good thing to do anyway.
// But conjugating was causing me lots of headaches because, e.g. it was causing extraneous mirroring/rotations
// in puzzles like the Klein bottle, which don't have symmetrical identifications. So I took it out for now.
// NOTE: Later I tried conjugating for spherical puzzles, but that just produced bad puzzles (copies would have different colors adjacent).
// So I think this is right.
//Isometry conjugated = parent.Isometry.Inverse() * identIsometry * parent.Isometry;
Isometry conjugated = identIsometry;
// We can use the conjugates when using relations, because those are regular maps.
//if( UsingRelations )
// conjugated = parent.Isometry.Inverse() * identIsometry * parent.Isometry;
Vector3D newCenter = parent.VertexCircle.CenterNE;
newCenter = conjugated.ApplyInfiniteSafe( newCenter );
// ZZZ - Hack for spherical. Some centers were projecting to very large values rather than DNE.
if( Infinity.IsInfinite( newCenter ) )
newCenter = Infinity.InfinityVector2D;
// In the tiling?
Tile tile;
if( tiling != null && !tiling.TilePositions.TryGetValue( newCenter, out tile ) )
{
return null;
}
if( positions != null && !positions.Positions.Contains( newCenter ) )
{
return null;
}
// Already done this one?
if( completed.ContainsKey( newCenter ) )
return null;
// New! Add it.
Polygon boundary = parent.Boundary.Clone();
boundary.Transform( conjugated );
Cell slave = SetupCell( template, boundary, completed );
AddSlave( master, slave );
return slave;
}
Created
January 19, 2023 14:21
-
-
Save lancejpollard/7d09e03ef1897eb0f5427fa1b3030e36 to your computer and use it in GitHub Desktop.
MagicTile Key Snippets for understanding Hyperbolic Tessellation Implementation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment