2018-11-07 00:53:10 +01:00
// show the fundamental domain for quotient spaces
2025-02-24 11:49:43 +01:00
// Copyright (C) 2018-2025 Zeno and Tehora Rogue, see 'hyper.cpp' for details
2018-11-07 00:53:10 +01:00
2025-02-24 11:49:43 +01:00
# include "hyper.h"
2020-03-29 16:03:31 +02:00
2018-11-07 00:53:10 +01:00
namespace hr {
2025-02-24 11:49:43 +01:00
EX namespace fundamental {
2018-11-07 00:53:10 +01:00
2025-02-23 22:22:58 +01:00
transmatrix current_position , last_view ;
void analyze_view_pre ( ) {
current_position = current_position * last_view * inverse ( View ) ;
}
void analyze_view_post ( ) {
last_view = View ;
}
2025-02-24 10:00:29 +01:00
struct settings {
2025-02-24 10:32:49 +01:00
int funmode ;
bool single_edges ;
bool fill_faces ;
ld widthfactor ;
color_t color_other ;
2025-02-24 10:10:18 +01:00
color_t color_main ;
2025-02-24 10:32:49 +01:00
int lq ;
color_t color_mirage ;
color_t color_mirror ;
ld label_dist ;
ld label_scale ;
int alpha ;
2025-02-24 10:00:29 +01:00
void show_options ( ) ;
} sett ;
2018-11-07 00:53:10 +01:00
2025-02-23 20:03:34 +01:00
cell * starter ;
2025-02-24 09:07:32 +01:00
2025-02-24 10:00:29 +01:00
struct shapedata {
map < cell * , set < int > > same ;
map < cell * , shiftmatrix > gm ;
int corners , corners0 ;
vector < hyperpoint > abs_cornerpos ;
vector < cell * > cells ;
vector < cellwalker > cornerlist ;
map < cellwalker , int > corner_id ;
cell * current_starter ;
2025-02-24 10:41:16 +01:00
vector < int > connections ;
vector < bool > mirrored ;
2025-02-24 10:00:29 +01:00
bool is_connected ( cellwalker cw ) ;
void be_connected ( cellwalker cw ) ;
int group_count ( cellwalker cw ) ;
void auto_corners ( ) ;
void find_corners ( ) ;
shiftpoint cwcorner ( cellwalker cw ) ;
void compute_shape ( ) ;
void render ( ) ;
} sd ;
bool shapedata : : is_connected ( cellwalker cw ) {
2025-02-23 20:52:23 +01:00
return same [ cw . at ] . count ( cw . spin ) ;
2018-11-07 00:53:10 +01:00
}
2025-02-24 10:00:29 +01:00
void shapedata : : be_connected ( cellwalker cw ) {
2025-02-23 20:52:23 +01:00
same [ cw . at ] . insert ( cw . spin ) ;
2018-11-07 00:53:10 +01:00
cw + = wstep ;
2025-02-23 20:52:23 +01:00
same [ cw . at ] . insert ( cw . spin ) ;
2018-11-07 00:53:10 +01:00
}
2025-02-23 21:03:03 +01:00
2025-02-24 10:00:29 +01:00
shiftpoint shapedata : : cwcorner ( cellwalker cw ) {
2025-02-24 09:33:39 +01:00
shiftmatrix T = gm [ cw . at ] ;
2025-02-23 21:03:03 +01:00
2025-02-24 10:00:29 +01:00
if ( sett . funmode = = 2 & & BITRUNCATED ) {
2018-11-07 00:53:10 +01:00
while ( cw . at - > type ! = S7 ) {
cw + + ;
2019-11-23 23:52:04 +01:00
T = T * currentmap - > adj ( cw . at , cw . spin ) ;
2018-11-07 00:53:10 +01:00
cw + = wstep ;
}
return T * C0 ;
}
2025-02-24 09:33:39 +01:00
2018-11-07 00:53:10 +01:00
return gm [ cw . at ] * get_corner_position ( cw . at , cw . spin + ( cw . mirrored ? 0 : 1 ) , 3 ) ;
}
2025-02-24 10:00:29 +01:00
void shapedata : : auto_corners ( ) {
2025-02-24 09:33:39 +01:00
cellwalker cw ;
corners = 0 ;
for ( int k = 0 ; k < isize ( cells ) ; k + + ) {
cell * c = cells [ k ] ;
for ( int i = 0 ; i < c - > type ; i + + ) {
cellwalker cw0 ( c , i ) ;
2025-02-24 10:00:29 +01:00
if ( sett . single_edges ) {
2025-02-24 09:33:39 +01:00
if ( ! is_connected ( cw0 ) ) corners + + , cw = cw0 ;
}
else {
if ( group_count ( cw0 ) > = 3 ) corners + + , cw = cw0 ;
}
}
}
if ( ! corners ) return ;
cornerlist . clear ( ) ;
corner_id . clear ( ) ;
for ( int ci = 0 ; ci < corners ; ci + + ) {
corner_id [ cw ] = cornerlist . size ( ) ;
cornerlist . push_back ( cw ) ;
while ( true ) {
cw + + ;
while ( is_connected ( cw ) ) {
cw + = wstep ;
cw + + ;
}
2025-02-24 10:00:29 +01:00
if ( sett . single_edges | | group_count ( cw ) > = 3 ) break ;
2025-02-24 09:33:39 +01:00
}
}
2025-02-24 10:00:29 +01:00
corners0 = corners ;
2025-02-24 09:33:39 +01:00
corners = isize ( cornerlist ) ;
cornerlist . push_back ( cw ) ;
if ( corners0 ! = corners ) println ( hlog , " corners= " , tie ( corners0 , corners ) ) ;
abs_cornerpos . clear ( ) ;
for ( auto c : cornerlist ) {
2025-02-24 09:38:04 +01:00
auto co = inverse_shift ( gm [ starter ] , cwcorner ( c ) ) ;
if ( isize ( abs_cornerpos ) & & ( hdist ( co , abs_cornerpos [ 0 ] ) < 1e-3 | | hdist ( co , abs_cornerpos . back ( ) ) < 1e-3 ) ) continue ;
abs_cornerpos . push_back ( co ) ;
2025-02-24 09:33:39 +01:00
}
2025-02-24 09:38:04 +01:00
corners = isize ( abs_cornerpos ) ; abs_cornerpos . push_back ( abs_cornerpos [ 0 ] ) ;
2025-02-24 09:33:39 +01:00
}
2025-02-24 10:00:29 +01:00
void shapedata : : find_corners ( ) {
2025-02-24 09:33:39 +01:00
abs_cornerpos . clear ( ) ;
auto build = [ & ] ( int sides , ld a , ld a1 , ld shift ) {
transmatrix T = Id ;
if ( variation = = eVariation : : untruncated )
T = T * rgpushxto0 ( get_corner_position ( starter , 0 , 3 ) ) ;
for ( int i = 0 ; i < = sides ; i + + ) {
abs_cornerpos . push_back ( T * xspinpush0 ( - i * TAU / sides + shift , ( i & 1 ) ? a : a1 ) ) ;
}
corners = sides ;
} ;
2025-02-24 10:00:29 +01:00
if ( sett . funmode = = 3 ) switch ( geometry ) {
2025-02-24 09:33:39 +01:00
case gKleinQuartic : {
ld a = edge_of_triangle_with_angles ( 90. _deg , M_PI / 14 , M_PI * 2 / 14 ) ;
return build ( 14 , a , a , 0 ) ;
}
case gSchmutzM2 : {
ld a = edge_of_triangle_with_angles ( 90. _deg , M_PI / 12 , M_PI / 6 ) ;
ld a1 = edge_of_triangle_with_angles ( 45. _deg , M_PI / 12 , 60. _deg ) ;
return build ( 24 , a1 , a , 0 ) ;
}
case gSchmutzM3 : {
ld a = edge_of_triangle_with_angles ( 60. _deg , M_PI / 12 , M_PI / 12 ) ;
ld a1 = edge_of_triangle_with_angles ( M_PI / 12 , 60. _deg , M_PI / 12 ) ;
return build ( 24 , a , a1 , 0 ) ;
}
case gBolza : {
ld a = edge_of_triangle_with_angles ( 90. _deg , M_PI / 8 , M_PI / 8 ) ;
return build ( 8 , a , a , 22.5 _deg ) ;
}
case gBolza2 : {
ld a = edge_of_triangle_with_angles ( 90. _deg , M_PI / 8 , M_PI / 8 ) ;
hyperpoint h1 = xspinpush0 ( 0 , a ) ;
hyperpoint h2 = xspinpush0 ( 45. _deg , a ) ;
hyperpoint hm = mid ( h1 , h2 ) ;
ld a1 = hdist0 ( hm ) * 2 ;
return build ( 16 , a , a1 , 0 ) ;
}
default : break ;
}
return auto_corners ( ) ;
}
2018-11-07 00:53:10 +01:00
transmatrix rel ( cellwalker cw ) {
2019-11-23 23:52:04 +01:00
return currentmap - > adj ( cw . at , cw . spin ) ;
2018-11-07 00:53:10 +01:00
}
2020-07-29 23:34:00 +02:00
shiftmatrix labelpos ( shiftpoint h1 , shiftpoint h2 ) {
shiftpoint h = mid ( h1 , h2 ) ;
shiftmatrix T = rgpushxto0 ( h ) ;
hyperpoint hx = inverse_shift ( T , h2 ) ;
2018-11-07 00:53:10 +01:00
ld alpha = atan2 ( - hx [ 1 ] , hx [ 0 ] ) ;
2025-02-24 10:00:29 +01:00
return T * xspinpush ( alpha + 90. _deg , sett . label_dist ) ;
2018-11-07 00:53:10 +01:00
}
2025-02-24 10:00:29 +01:00
int shapedata : : group_count ( cellwalker cw ) {
2025-02-23 20:54:23 +01:00
if ( is_connected ( cw ) ) return 0 ;
auto cw1 = cw ;
int groups = 0 ;
do {
if ( ! is_connected ( cw1 ) ) groups + + ;
cw1 = cw1 + wstep - 1 ;
}
while ( cw1 ! = cw ) ;
return groups ;
}
2025-02-23 22:07:29 +01:00
map < unsigned , color_t > bucket_color ;
2025-02-23 22:34:06 +01:00
color_t * current_domain = nullptr ;
2025-02-24 10:00:29 +01:00
void shapedata : : compute_shape ( ) {
2025-02-24 09:07:32 +01:00
if ( current_starter = = starter ) return ;
current_starter = starter ;
2018-11-07 00:53:10 +01:00
same . clear ( ) ;
gm . clear ( ) ;
2025-02-23 20:03:34 +01:00
gm [ starter ] = ggmatrix ( starter ) ;
2018-11-07 00:53:10 +01:00
2025-02-24 09:07:32 +01:00
cells = { starter } ;
2018-11-07 00:53:10 +01:00
int tree_edges = 0 ;
int face_edges = 0 ;
2025-02-24 08:56:32 +01:00
2025-02-24 10:00:29 +01:00
bool first_zebra_phase = geometry = = gZebraQuotient & & sett . funmode = = 3 & & PURE ;
2025-02-24 08:56:32 +01:00
bool second_zebra_phase = false ;
again :
2018-11-07 00:53:10 +01:00
for ( int k = 0 ; k < isize ( cells ) ; k + + ) {
cell * c = cells [ k ] ;
for ( int i = 0 ; i < c - > type ; i + + ) {
cellwalker cw ( c , i ) ;
cell * c2 = cw . cpeek ( ) ;
if ( gm . count ( c2 ) ) continue ;
2025-02-24 08:56:32 +01:00
if ( first_zebra_phase & & ( zebra40 ( c ) % 4 ) ! = ( zebra40 ( c2 ) % 4 ) )
continue ;
if ( second_zebra_phase ) {
int j = 0 ;
for ( ; j < c - > type ; j + + ) if ( zebra40 ( c - > move ( j ) ) = = 4 + zebra40 ( c ) % 12 ) break ;
int v = gmod ( i - j + 2 , 7 ) ; if ( v < 5 ) continue ;
}
2018-11-07 00:53:10 +01:00
gm [ c2 ] = gm [ c ] * rel ( cw ) ;
be_connected ( cw ) ;
tree_edges + + ;
cells . push_back ( c2 ) ;
}
}
2025-02-24 08:56:32 +01:00
if ( first_zebra_phase ) {
first_zebra_phase = false ;
second_zebra_phase = true ;
goto again ;
}
2018-11-07 00:53:10 +01:00
2025-02-24 10:00:29 +01:00
while ( sett . fill_faces ) {
2018-11-07 00:53:10 +01:00
int f = face_edges ;
for ( int k = 0 ; k < isize ( cells ) ; k + + ) {
cell * c = cells [ k ] ;
for ( int i = 0 ; i < c - > type ; i + + ) {
cellwalker cw ( c , i ) ;
2025-02-23 20:54:23 +01:00
if ( group_count ( cw ) = = 1 ) {
2018-11-07 00:53:10 +01:00
face_edges + + ;
2025-02-23 20:54:23 +01:00
be_connected ( cw ) ;
2018-11-07 00:53:10 +01:00
}
}
}
if ( f = = face_edges ) break ;
}
2025-02-24 09:07:32 +01:00
2025-02-24 09:33:39 +01:00
find_corners ( ) ;
2025-02-24 10:41:16 +01:00
connections . clear ( ) ; connections . resize ( corners , - 1 ) ;
mirrored . clear ( ) ; mirrored . resize ( corners , false ) ;
2025-02-24 09:07:32 +01:00
}
2025-02-24 10:00:29 +01:00
void shapedata : : render ( ) {
if ( ! sd . corners ) return ;
2025-02-24 09:07:32 +01:00
2025-02-23 22:07:29 +01:00
set < unsigned > buckets_used ;
2025-02-24 10:00:29 +01:00
for ( int i = 0 ; i < corners ; i + + ) curvepoint_pretty ( sd . abs_cornerpos [ i ] , sd . abs_cornerpos [ i + 1 ] , sett . lq ) ;
2025-02-23 22:07:29 +01:00
curvepoint_first ( ) ;
2025-02-23 22:22:58 +01:00
auto pos = current_position * last_view * inverse ( View ) ;
2025-02-24 09:33:39 +01:00
map < unsigned , int > midedge_id ;
auto T = ggmatrix ( starter ) ;
unsigned central_bucket = bucketer ( unshift ( T * C0 ) ) ;
for ( int i = 0 ; i < corners ; i + + )
midedge_id [ bucketer ( unshift ( T * mid ( abs_cornerpos [ i ] , abs_cornerpos [ i + 1 ] ) ) ) ] = i ;
int next_connection_id = 0 ;
2025-02-24 10:00:29 +01:00
dynamicval < ld > lw ( vid . linewidth , vid . linewidth * sett . widthfactor ) ;
2025-02-24 09:33:39 +01:00
2025-02-23 22:07:29 +01:00
for ( auto c : cells )
for ( const shiftmatrix & V : hr : : span_at ( current_display - > all_drawn_copies , c ) ) {
auto V1 = V * inverse_shift ( gm [ c ] , gm [ starter ] ) ;
2025-02-23 22:22:58 +01:00
auto bu = bucketer ( pos * unshift ( V1 * C0 ) ) ;
2025-02-23 22:07:29 +01:00
if ( buckets_used . count ( bu ) ) continue ;
buckets_used . insert ( bu ) ;
2025-02-24 10:00:29 +01:00
if ( sett . alpha & & ! bucket_color . count ( bu ) ) {
if ( bucket_color . empty ( ) ) bucket_color [ bu ] = sett . alpha ;
else bucket_color [ bu ] = ( hrand ( 0x1000000 ) < < 8 ) | sett . alpha ;
2018-11-07 00:53:10 +01:00
}
2025-02-23 22:07:29 +01:00
2025-02-24 10:00:29 +01:00
if ( c = = cwt . at & & sett . alpha & & ! current_domain ) current_domain = & bucket_color [ bu ] ;
queuecurve_reuse ( V1 , sett . color_other , sett . alpha ? bucket_color [ bu ] : 0 , PPR : : LINE ) ;
2018-11-07 00:53:10 +01:00
2025-02-24 09:33:39 +01:00
if ( bu ! = central_bucket ) {
for ( int i = 0 ; i < corners ; i + + ) {
unsigned val = bucketer ( unshift ( V1 * mid ( abs_cornerpos [ i ] , abs_cornerpos [ i + 1 ] ) ) ) ;
auto p = at_or_null ( midedge_id , val ) ;
if ( p & & connections [ * p ] = = - 1 ) {
connections [ * p ] = connections [ i ] = next_connection_id + + ;
mirrored [ * p ] = mirrored [ i ] = det ( V1 . T ) * det ( T . T ) < 0 ;
}
}
}
}
2018-11-07 00:53:10 +01:00
2025-02-24 10:00:29 +01:00
queuecurve ( T , sett . color_main , 0 , PPR : : LINE ) ;
2025-02-24 09:07:32 +01:00
2025-02-24 09:39:02 +01:00
string conlabels = " 123456789ABCDEFGHIJKLMNOPQRSTUVWYXZabcdefghijklmnopqrstuvwxyz$%@&+# " ;
2018-11-07 00:53:10 +01:00
for ( int ci = 0 ; ci < corners ; ci + + ) {
2025-02-24 10:00:29 +01:00
int mc = mirrored [ ci ] ? sett . color_mirror : sett . color_mirage ;
2025-02-24 09:33:39 +01:00
int id = connections [ ci ] ;
if ( id = = - 1 ) continue ;
2025-02-24 10:33:07 +01:00
string lab = s0 + conlabels [ id % isize ( conlabels ) ] ;
for ( int i = 0 ; i < id / isize ( conlabels ) ; i + + ) lab + = " ' " ;
queuestr ( labelpos ( T * abs_cornerpos [ ci ] , T * abs_cornerpos [ ci + 1 ] ) , sett . label_scale / cgi . scalefactor , lab , mc ) ;
2018-11-07 00:53:10 +01:00
}
2025-02-24 09:33:39 +01:00
}
2018-11-07 00:53:10 +01:00
2025-02-24 10:00:29 +01:00
void fundamental_marker ( ) {
current_domain = nullptr ;
2025-02-24 11:49:43 +01:00
if ( ! sett . funmode | | ! quotient | | ! closed_manifold | | WDIM ! = 2 ) return ;
2025-02-24 10:00:29 +01:00
sd . compute_shape ( ) ;
sd . render ( ) ;
}
2025-02-24 09:33:39 +01:00
void clear_data ( ) {
2025-02-24 10:00:29 +01:00
sd = { } ;
2025-02-24 09:33:39 +01:00
bucket_color . clear ( ) ;
2018-11-07 00:53:10 +01:00
}
2025-02-24 10:00:29 +01:00
void settings : : show_options ( ) {
2025-02-23 20:06:52 +01:00
dialog : : init ( XLAT ( " display fundamental domains " ) , 0xFFFFFFFF , 150 , 0 ) ;
2025-02-24 10:32:49 +01:00
add_edit ( funmode ) ;
add_edit ( single_edges ) ;
add_edit ( fill_faces ) ;
add_edit ( alpha ) ;
2025-02-24 10:00:29 +01:00
dialog : : addBreak ( 50 ) ;
2025-02-24 10:32:49 +01:00
add_edit ( widthfactor ) ;
add_edit ( color_main ) ;
add_edit ( color_other ) ;
add_edit ( lq ) ;
dialog : : addBreak ( 50 ) ;
add_edit ( label_dist ) ;
add_edit ( label_scale ) ;
add_edit ( color_mirage ) ;
add_edit ( color_mirror ) ;
2025-02-24 10:00:29 +01:00
}
2025-02-24 10:10:18 +01:00
EX void showMenu ( ) {
2025-02-24 10:00:29 +01:00
cmode = sm : : SIDE | sm : : MAYDARK ;
gamescreen ( ) ;
sett . show_options ( ) ;
dialog : : addBreak ( 100 ) ;
dialog : : addSelItem ( " set the central tile to current position " , its ( celldistance ( starter , cwt . at ) ) , ' s ' ) ;
dialog : : add_action ( [ ] ( ) {
starter = cwt . at ;
} ) ;
if ( sett . alpha ) {
2025-02-23 22:34:06 +01:00
dialog : : addItem ( " reshuffle all fill colors " , ' r ' ) ;
dialog : : add_action ( [ ] { bucket_color . clear ( ) ; } ) ;
if ( current_domain ) {
dialog : : addColorItem ( " current central domain " , * current_domain , ' y ' ) ;
dialog : : add_action ( [ ] ( ) {
dialog : : openColorDialog ( * current_domain , NULL ) ;
dialog : : get_di ( ) . dialogflags | = sm : : MAYDARK | sm : : SIDE ;
} ) ;
}
}
2025-02-23 20:06:52 +01:00
dialog : : addBack ( ) ;
dialog : : display ( ) ;
}
2025-02-24 10:10:18 +01:00
int enable_fundamental ( ) {
addHook ( hooks_post_initgame , 100 , [ ] {
starter = cwt . at ; current_position = Id ; last_view = View ;
} ) ;
addHook ( hooks_frame , 100 , fundamental_marker ) ;
addHook ( hooks_clearmemory , 100 , clear_data ) ;
addHook ( hooks_o_key , 80 , [ ] ( o_funcs & v ) {
if ( sett . funmode ) v . push_back ( named_dialog ( " fundamental " , showMenu ) ) ;
} ) ;
addHook ( hooks_preoptimize , 75 , analyze_view_pre ) ;
addHook ( hooks_postoptimize , 75 , analyze_view_post ) ;
addHook ( hooks_configfile , 100 , [ ] {
2025-02-24 10:32:49 +01:00
param_enum ( sett . funmode , " funmode " , 0 )
- > editable ( { { " disabled " , " do not construct or draw the fundamental domains " } , { " corners " , " find the shape automatically " } , { " centers " , " may produce less corners " } , { " special " , " nice domains for specific quotient shapes " } } , " fundamental domain method " , ' m ' )
- > set_reaction ( clear_data ) ;
param_color ( sett . color_main , " fundamental_color_main " , true , 0xFFFFFFFF )
- > editable ( " boundary of the central domain " , " " , ' c ' ) ;
param_color ( sett . color_other , " fundamental_color_other " , true , 0xFFFFFF40 )
- > editable ( " boundary of the other domains " , " " , ' o ' ) ;
param_color ( sett . color_mirage , " fundamental_color_mirage " , false , winf [ waCloud ] . color )
- > editable ( " gluing label color " , " " , ' d ' ) ;
param_color ( sett . color_mirror , " fundamental_color_mirror " , false , winf [ waMirror ] . color )
- > editable ( " mirrored gluing label color " , " " , ' b ' ) ;
param_b ( sett . single_edges , " fundamental_single_edges " , false )
- > editable ( " fundamental: single edges " , ' 1 ' )
- > set_reaction ( clear_data ) ;
param_b ( sett . fill_faces , " fundamental_fill_faces " , true )
- > editable ( " fundamental: fill faces " , ' 2 ' )
- > set_reaction ( clear_data ) ;
param_f ( sett . label_dist , " fundamental_label_dist " , .3 )
- > editable ( 0 , 10 , .05 , " gluing label distance " , " " , ' d ' ) ;
param_f ( sett . widthfactor , " fundamental_width " , 5 )
- > editable ( 0 , 5 , 1 , " domain boundary width factor " , " " , ' w ' ) ;
param_f ( sett . label_scale , " fundamental_label_scale " , 1 )
- > editable ( 0 , 10 , 0.2 , " gluing label scale " , " " , ' s ' ) ;
param_i ( sett . lq , " fundamental_lq " , 3 )
- > editable ( 0 , 5 , 1 , " domain line quality " , " " , ' w ' ) ;
param_i ( sett . alpha , " fundamental_alpha " , 32 )
- > editable ( 0 , 5 , 8 , " opacity of domain fill colors " , " " , ' a ' ) ;
2025-02-24 10:10:18 +01:00
} ) ;
2018-11-07 00:53:10 +01:00
return 0 ;
}
2025-02-24 10:10:18 +01:00
auto fundamentalhook = enable_fundamental ( ) ;
2025-02-23 20:06:18 +01:00
}
2018-11-07 00:53:10 +01:00
}