2018-11-17 19:25:47 +00:00
// flocking simulations
// Copyright (C) 2018 Zeno and Tehora Rogue, see 'hyper.cpp' for details
2018-11-18 16:34:26 +00:00
// based on Flocking by Daniel Shiffman (which in turn implements Boids by Craig Reynold)
// https://processing.org/examples/flocking.html
// Our implementation simplifies some equations a bit.
2018-11-17 19:25:47 +00:00
// example parameters:
// flocking on a torus:
2020-03-20 18:48:21 +00:00
// -t2 3 0 0 3 0 -geo 1 -flocking 10 -rvshape 3
2018-11-17 19:25:47 +00:00
// flocking on the Zebra quotient:
// -geo 4 -flocking 10 -rvshape 3 -zoom .9
2018-11-18 16:34:26 +00:00
// press 'o' when flocking active to change the parameters.
2018-11-17 19:25:47 +00:00
2019-09-13 17:07:31 +00:00
# ifdef USE_THREADS
2019-04-21 18:49:32 +00:00
# include <thread>
int threads = 1 ;
# endif
template < class T > auto parallelize ( long long N , T action ) - > decltype ( action ( 0 , 0 ) ) {
2019-09-13 17:07:31 +00:00
# ifndef USE_THREADS
2019-04-21 18:49:32 +00:00
return action ( 0 , N ) ;
# else
if ( threads = = 1 ) return action ( 0 , N ) ;
std : : vector < std : : thread > v ;
typedef decltype ( action ( 0 , 0 ) ) Res ;
std : : vector < Res > results ( threads ) ;
for ( int k = 0 ; k < threads ; k + + )
v . emplace_back ( [ & , k ] ( ) {
results [ k ] = action ( N * k / threads , N * ( k + 1 ) / threads ) ;
} ) ;
for ( std : : thread & t : v ) t . join ( ) ;
Res res = 0 ;
for ( Res r : results ) res + = r ;
return res ;
# endif
}
2018-11-17 19:25:47 +00:00
namespace rogueviz {
namespace flocking {
int N ;
bool draw_lines = false ;
2018-11-19 17:59:22 +00:00
int follow = 0 ;
string follow_names [ 3 ] = { " nothing " , " specific boid " , " center of mass " } ;
2018-11-17 19:25:47 +00:00
map < cell * , map < cell * , transmatrix > > relmatrices ;
ld ini_speed = .5 ;
ld max_speed = 1 ;
ld sep_factor = 1.5 ;
ld sep_range = .25 ;
ld align_factor = 1 ;
ld align_range = .5 ;
ld coh_factor = 1 ;
2018-11-20 11:58:35 +00:00
ld coh_range = 2.5 ;
2018-11-17 19:25:47 +00:00
ld check_range = 2.5 ;
2019-04-21 18:50:20 +00:00
char shape = ' b ' ;
2018-11-17 19:25:47 +00:00
vector < tuple < hyperpoint , hyperpoint , color_t > > lines ;
2018-11-20 11:50:00 +00:00
// parameters of each boid
// m->base: the cell it is currently on
// m->vel: velocity
// m->at: determines the position and speed:
// m->at * (0, 0, 1) is the current position (in Minkowski hyperboloid coordinates relative to m->base)
// m->at * (m->vel, 0, 0) is the current velocity vector (tangent to the Minkowski hyperboloid)
// m->pat: like m->at but relative to the screen
2018-11-17 19:25:47 +00:00
void init ( ) {
if ( ! bounded ) {
addMessage ( " Flocking simulation needs a bounded space. " ) ;
return ;
}
stop_game ( ) ;
rogueviz : : init ( ) ; kind = kFlocking ;
vdata . resize ( N ) ;
const auto v = currentmap - > allcells ( ) ;
printf ( " computing relmatrices... \n " ) ;
2018-11-20 11:50:00 +00:00
// relmatrices[c1][c2] is the matrix we have to multiply by to
// change from c1-relative coordinates to c2-relative coordinates
2018-11-17 19:25:47 +00:00
for ( cell * c1 : v ) {
manual_celllister cl ;
cl . add ( c1 ) ;
for ( int i = 0 ; i < isize ( cl . lst ) ; i + + ) {
cell * c2 = cl . lst [ i ] ;
transmatrix T = calc_relative_matrix ( c2 , c1 , C0 ) ;
if ( hdist0 ( tC0 ( T ) ) < = check_range ) {
relmatrices [ c1 ] [ c2 ] = T ;
forCellEx ( c3 , c2 ) cl . add ( c3 ) ;
}
}
}
printf ( " setting up... \n " ) ;
for ( int i = 0 ; i < N ; i + + ) {
vertexdata & vd = vdata [ i ] ;
2018-11-20 11:50:00 +00:00
// set initial base and at to random cell and random position there
2018-11-17 19:25:47 +00:00
createViz ( i , v [ hrand ( isize ( v ) ) ] , spin ( hrand ( 100 ) ) * xpush ( hrand ( 100 ) / 200. ) ) ;
vd . name = its ( i + 1 ) ;
vd . cp = dftcolor ;
vd . cp . color2 = ( ( hrand ( 0x1000000 ) < < 8 ) + 0xFF ) | 0x808080FF ;
2019-04-21 18:50:20 +00:00
vd . cp . shade = shape ;
2018-11-17 19:25:47 +00:00
vd . m - > vel = ini_speed ;
}
storeall ( ) ;
printf ( " done \n " ) ;
}
2018-11-18 16:31:17 +00:00
int precision = 10 ;
2018-11-17 19:25:47 +00:00
void simulate ( int delta ) {
2019-04-21 18:45:15 +00:00
int iter = 0 ;
while ( delta > precision & & iter < 100 ) {
2018-11-18 16:31:17 +00:00
simulate ( precision ) ; delta - = precision ;
2019-04-21 18:45:15 +00:00
iter + + ;
2018-11-18 16:31:17 +00:00
}
2018-11-17 19:25:47 +00:00
ld d = delta / 1000. ;
int N = isize ( vdata ) ;
vector < transmatrix > pats ( N ) ;
vector < ld > vels ( N ) ;
using shmup : : monster ;
map < cell * , vector < monster * > > monsat ;
for ( int i = 0 ; i < N ; i + + ) {
vertexdata & vd = vdata [ i ] ;
auto m = vd . m ;
monsat [ m - > base ] . push_back ( m ) ;
}
lines . clear ( ) ;
2019-04-21 18:49:32 +00:00
parallelize ( N , [ & monsat , & d , & vels , & pats ] ( int a , int b ) { for ( int i = a ; i < b ; i + + ) {
2018-11-17 19:25:47 +00:00
vertexdata & vd = vdata [ i ] ;
auto m = vd . m ;
transmatrix I = inverse ( m - > at ) ;
2018-11-20 11:50:00 +00:00
// we do all the computations here in the frame of reference
// where m is at (0,0,1) and its velocity is (m->vel,0,0)
hyperpoint velvec = hpxyz ( m - > vel , 0 , 0 ) ;
2018-11-17 19:25:47 +00:00
hyperpoint sep = hpxyz ( 0 , 0 , 0 ) ;
int sep_count = 0 ;
hyperpoint align = hpxyz ( 0 , 0 , 0 ) ;
int align_count = 0 ;
hyperpoint coh = hpxyz ( 0 , 0 , 0 ) ;
int coh_count = 0 ;
for ( auto & p : relmatrices [ m - > base ] ) {
2019-04-21 18:49:32 +00:00
auto f = monsat . find ( p . first ) ;
if ( f ! = monsat . end ( ) ) for ( auto m2 : f - > second ) if ( m ! = m2 ) {
2018-11-17 19:25:47 +00:00
ld vel2 = m2 - > vel ;
transmatrix at2 = I * p . second * m2 - > at ;
2018-11-20 11:50:00 +00:00
// at2 is like m2->at but relative to m->at
// m2's position relative to m (tC0 means *(0,0,1))
2018-11-17 19:25:47 +00:00
hyperpoint ac = tC0 ( at2 ) ;
2018-11-20 11:50:00 +00:00
// distance and azimuth to m2
2018-11-17 19:25:47 +00:00
ld di = hdist0 ( ac ) ;
2019-04-21 18:51:16 +00:00
transmatrix alphaspin = rspintox ( ac ) ; // spin(-atan2(ac));
2018-11-17 19:25:47 +00:00
color_t col = 0 ;
if ( di < align_range ) {
2018-11-20 11:50:00 +00:00
// we need to transfer m2's velocity vector to m's position
// this is done by applying an isometry which sends m2 to m1
// and maps the straight line on which m1 and m2 are to itself
2018-11-17 19:25:47 +00:00
align + = gpushxto0 ( ac ) * at2 * hpxyz ( vel2 , 0 , 0 ) ;
align_count + + ;
2018-11-20 11:58:49 +00:00
col | = 0xFF0040 ;
2018-11-17 19:25:47 +00:00
}
2018-11-20 11:58:35 +00:00
if ( di < coh_range ) {
2018-11-20 11:50:00 +00:00
// azimuthal equidistant projection of ac
// (thus the cohesion force pushes us towards the
// average of azimuthal equidistant projections)
2019-04-21 18:51:16 +00:00
coh + = alphaspin * hpxyz ( di , 0 , 0 ) ;
2018-11-17 19:25:47 +00:00
coh_count + + ;
2018-11-20 11:58:49 +00:00
col | = 0xFF40 ;
2018-11-17 19:25:47 +00:00
}
2019-04-21 18:51:16 +00:00
if ( di < sep_range & & di > 0 ) {
sep - = alphaspin * hpxyz ( 1 / di , 0 , 0 ) ;
2018-11-20 11:50:00 +00:00
sep_count + + ;
2018-11-20 11:58:49 +00:00
col | = 0xFF000040 ;
2018-11-17 19:25:47 +00:00
}
if ( col & & draw_lines )
lines . emplace_back ( m - > pat * C0 , m - > pat * at2 * C0 , col ) ;
}
}
2018-11-18 16:34:26 +00:00
// a bit simpler rules than original
2018-11-17 19:25:47 +00:00
if ( sep_count ) velvec + = sep * ( d * sep_factor / sep_count ) ;
if ( align_count ) velvec + = align * ( d * align_factor / align_count ) ;
if ( coh_count ) velvec + = coh * ( d * coh_factor / coh_count ) ;
2018-11-20 11:50:00 +00:00
// hypot2 is the length of a vector in R^2
2019-03-11 17:49:27 +00:00
vels [ i ] = hypot_d ( 2 , velvec ) ;
2019-04-21 18:51:16 +00:00
transmatrix alphaspin = rspintox ( velvec ) ; // spin(-atan2(velvec));
2018-11-17 19:25:47 +00:00
if ( vels [ i ] > max_speed ) {
velvec = velvec * ( max_speed / vels [ i ] ) ;
vels [ i ] = max_speed ;
}
2019-04-21 18:48:15 +00:00
pats [ i ] = m - > at * alphaspin * xpush ( vels [ i ] * d ) ;
fixmatrix ( pats [ i ] ) ;
2019-04-21 18:49:32 +00:00
} return 0 ; } ) ;
2018-11-17 19:25:47 +00:00
for ( int i = 0 ; i < N ; i + + ) {
vertexdata & vd = vdata [ i ] ;
auto m = vd . m ;
2018-11-20 11:50:00 +00:00
// these two functions compute new base and at, based on pats[i]
2019-04-21 18:48:15 +00:00
m - > at = pats [ i ] ;
2019-11-14 19:26:07 +00:00
virtualRebase ( m ) ;
2018-11-17 19:25:47 +00:00
m - > vel = vels [ i ] ;
}
shmup : : fixStorage ( ) ;
2018-11-19 17:59:22 +00:00
2018-11-17 19:25:47 +00:00
}
bool turn ( int delta ) {
if ( ! on ) return false ;
if ( kind = = kFlocking ) simulate ( delta ) , timetowait = 0 ;
2018-11-19 23:21:48 +00:00
if ( follow ) {
2018-11-19 17:59:22 +00:00
2019-04-21 18:51:47 +00:00
if ( follow = = 1 ) {
gmatrix . clear ( ) ;
2019-11-14 19:26:07 +00:00
vdata [ 0 ] . m - > pat = View * calc_relative_matrix ( vdata [ 0 ] . m - > base , centerover , C0 ) * vdata [ 0 ] . m - > at ;
2018-11-19 23:21:48 +00:00
View = spin ( 90 * degree ) * inverse ( vdata [ 0 ] . m - > pat ) * View ;
2019-08-15 13:05:43 +00:00
if ( GDIM = = 3 ) {
2019-04-21 18:51:47 +00:00
View = hr : : cspin ( 1 , 2 , 90 * degree ) * View ;
}
}
2018-11-19 23:21:48 +00:00
if ( follow = = 2 ) {
2018-11-20 11:50:00 +00:00
// we take the average in R^3 of all the boid positions of the Minkowski hyperboloid
// (in quotient spaces, the representants closest to the current view
// are taken), and normalize the result to project it back to the hyperboloid
// (the same method is commonly used on the sphere AFAIK)
2018-11-19 23:21:48 +00:00
hyperpoint h = Hypc ;
2019-04-21 18:51:47 +00:00
for ( int i = 0 ; i < N ; i + + ) if ( gmatrix . count ( vdata [ i ] . m - > base ) ) {
vdata [ i ] . m - > pat = gmatrix [ vdata [ i ] . m - > base ] * vdata [ i ] . m - > at ;
h + = tC0 ( vdata [ i ] . m - > pat ) ;
}
2018-11-19 23:21:48 +00:00
h = normalize ( h ) ;
View = gpushxto0 ( h ) * View ;
}
2018-11-19 17:59:22 +00:00
optimizeview ( ) ;
2018-11-19 23:21:48 +00:00
compute_graphical_distance ( ) ;
gmatrix . clear ( ) ;
2018-11-19 17:59:22 +00:00
playermoved = false ;
}
2018-11-17 19:25:47 +00:00
return false ;
// shmup::pc[0]->rebase();
}
# if CAP_COMMANDLINE
int readArgs ( ) {
using namespace arg ;
// options before reading
if ( 0 ) ;
else if ( argis ( " -flocking " ) ) {
2018-12-13 16:03:52 +00:00
PHASEFROM ( 2 ) ;
2018-11-17 19:25:47 +00:00
shift ( ) ; N = argi ( ) ;
init ( ) ;
}
else if ( argis ( " -cohf " ) ) {
shift ( ) ; coh_factor = argf ( ) ;
}
else if ( argis ( " -alignf " ) ) {
shift ( ) ; align_factor = argf ( ) ;
}
else if ( argis ( " -sepf " ) ) {
shift ( ) ; sep_factor = argf ( ) ;
}
2019-04-21 18:52:20 +00:00
else if ( argis ( " -checkr " ) ) {
shift ( ) ; check_range = argf ( ) ;
}
2018-11-20 11:59:12 +00:00
else if ( argis ( " -cohr " ) ) {
shift ( ) ; coh_range = argf ( ) ;
}
else if ( argis ( " -alignr " ) ) {
shift ( ) ; align_range = argf ( ) ;
}
else if ( argis ( " -sepr " ) ) {
shift ( ) ; sep_range = argf ( ) ;
}
2018-11-20 18:01:29 +00:00
else if ( argis ( " -flockfollow " ) ) {
shift ( ) ; follow = argi ( ) ;
}
2019-04-21 18:52:10 +00:00
else if ( argis ( " -flockprec " ) ) {
shift ( ) ; precision = argi ( ) ;
}
2019-04-21 18:50:20 +00:00
else if ( argis ( " -flockshape " ) ) {
shift ( ) ; shape = argcs ( ) [ 0 ] ;
for ( int i = 0 ; i < N ; i + + )
vdata [ i ] . cp . shade = shape ;
}
2019-09-13 17:07:31 +00:00
# ifdef USE_THREADS
2019-04-21 18:49:32 +00:00
else if ( argis ( " -threads " ) ) {
shift ( ) ; threads = argi ( ) ;
}
# endif
2018-11-17 19:25:47 +00:00
else return 1 ;
return 0 ;
}
void flock_marker ( ) {
if ( draw_lines )
for ( auto p : lines ) queueline ( get < 0 > ( p ) , get < 1 > ( p ) , get < 2 > ( p ) , 0 ) ;
}
void show ( ) {
2018-12-13 16:02:10 +00:00
cmode = sm : : SIDE | sm : : MAYDARK ;
2018-11-17 19:25:47 +00:00
gamescreen ( 0 ) ;
dialog : : init ( XLAT ( " flocking " ) , iinf [ itPalace ] . color , 150 , 0 ) ;
dialog : : addSelItem ( " initial speed " , fts ( ini_speed ) , ' i ' ) ;
dialog : : add_action ( [ ] ( ) {
dialog : : editNumber ( ini_speed , 0 , 2 , .1 , .5 , " " , " " ) ;
} ) ;
dialog : : addSelItem ( " max speed " , fts ( max_speed ) , ' m ' ) ;
dialog : : add_action ( [ ] ( ) {
dialog : : editNumber ( max_speed , 0 , 2 , .1 , .5 , " " , " " ) ;
} ) ;
dialog : : addSelItem ( " separation factor " , fts ( sep_factor ) , ' s ' ) ;
dialog : : add_action ( [ ] ( ) {
dialog : : editNumber ( sep_factor , 0 , 2 , .1 , 1.5 , " " , " " ) ;
} ) ;
2018-11-20 11:50:00 +00:00
string rangehelp = " Increasing this parameter may also require increasing the 'check range' parameter. " ;
2018-11-17 19:25:47 +00:00
dialog : : addSelItem ( " separation range " , fts ( sep_range ) , ' S ' ) ;
2018-11-20 11:50:00 +00:00
dialog : : add_action ( [ rangehelp ] ( ) {
dialog : : editNumber ( sep_range , 0 , 2 , .1 , .5 , " " , rangehelp ) ;
2018-11-17 19:25:47 +00:00
} ) ;
dialog : : addSelItem ( " alignment factor " , fts ( align_factor ) , ' a ' ) ;
dialog : : add_action ( [ ] ( ) {
dialog : : editNumber ( align_factor , 0 , 2 , .1 , 1.5 , " " , " " ) ;
} ) ;
dialog : : addSelItem ( " alignment range " , fts ( align_range ) , ' A ' ) ;
2018-11-20 11:50:00 +00:00
dialog : : add_action ( [ rangehelp ] ( ) {
dialog : : editNumber ( align_range , 0 , 2 , .1 , .5 , " " , rangehelp ) ;
2018-11-17 19:25:47 +00:00
} ) ;
dialog : : addSelItem ( " cohesion factor " , fts ( coh_factor ) , ' c ' ) ;
dialog : : add_action ( [ ] ( ) {
dialog : : editNumber ( coh_factor , 0 , 2 , .1 , 1.5 , " " , " " ) ;
} ) ;
dialog : : addSelItem ( " cohesion range " , fts ( coh_range ) , ' C ' ) ;
2018-11-20 11:50:00 +00:00
dialog : : add_action ( [ rangehelp ] ( ) {
dialog : : editNumber ( coh_range , 0 , 2 , .1 , .5 , " " , rangehelp ) ;
2018-11-17 19:25:47 +00:00
} ) ;
dialog : : addSelItem ( " check range " , fts ( check_range ) , ' t ' ) ;
dialog : : add_action ( [ ] ( ) {
ld radius = 0 ;
for ( cell * c : currentmap - > allcells ( ) )
for ( int i = 0 ; i < c - > degree ( ) ; i + + ) {
hyperpoint h = nearcorner ( c , i ) ;
radius = max ( radius , hdist0 ( h ) ) ;
}
dialog : : editNumber ( check_range , 0 , 2 , .1 , .5 , " " ,
" Value used in the algorithm: "
" only other boids in cells whose centers are at most 'check range' from the center of the current cell are considered. "
" Should be more than the other ranges by at least double the cell radius (in the current geometry, double the radius is " + fts ( radius * 2 ) + " ); "
2018-11-20 11:50:00 +00:00
" but too large values slow the simulation down. \n \n "
" Restart the simulation to apply the changes to this parameter. In quotient spaces, the simulation may not work correctly when the same cell is in range check_range "
" in multiple directions. "
2018-11-17 19:25:47 +00:00
) ;
} ) ;
dialog : : addSelItem ( " number of boids " , its ( N ) , ' n ' ) ;
dialog : : add_action ( [ ] ( ) {
dialog : : editNumber ( N , 0 , 1000 , 1 , 20 , " " , " " ) ;
} ) ;
2018-11-18 16:31:17 +00:00
dialog : : addSelItem ( " precision " , its ( precision ) , ' p ' ) ;
dialog : : add_action ( [ ] ( ) {
2019-04-21 18:52:10 +00:00
dialog : : editNumber ( precision , 0 , 1000 , 1 , 10 , " " , " smaller number = more precise simulation " ) ;
2018-11-18 16:31:17 +00:00
} ) ;
2019-02-08 16:08:00 +00:00
dialog : : addSelItem ( " change geometry " , XLAT ( ginf [ geometry ] . shortname ) , ' g ' ) ;
2018-11-18 16:52:36 +00:00
hr : : showquotients = true ;
2018-11-17 19:25:47 +00:00
dialog : : add_action ( runGeometryExperiments ) ;
2019-05-03 10:11:40 +00:00
dialog : : addBoolItem_action ( " draw forces " , draw_lines , ' l ' ) ;
2018-11-17 19:25:47 +00:00
2018-11-19 17:59:22 +00:00
dialog : : addSelItem ( " follow " , follow_names [ follow ] , ' f ' ) ;
dialog : : add_action ( [ ] ( ) { follow + + ; follow % = 3 ; } ) ;
2018-11-17 19:25:47 +00:00
dialog : : addBreak ( 100 ) ;
dialog : : addItem ( " restart " , ' r ' ) ;
dialog : : add_action ( init ) ;
dialog : : addBack ( ) ;
dialog : : display ( ) ;
}
named_functionality o_key ( ) {
if ( kind = = kFlocking ) return named_dialog ( " flocking " , show ) ;
return named_functionality ( ) ;
}
auto hooks =
addHook ( hooks_args , 100 , readArgs ) +
addHook ( shmup : : hooks_turn , 100 , turn ) +
addHook ( hooks_frame , 100 , flock_marker ) +
addHook ( hooks_o_key , 80 , o_key ) +
0 ;
# endif
}
}