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
}
2020-03-29 14:03:31 +00:00
# include "rogueviz.h"
2018-11-17 19:25:47 +00:00
namespace rogueviz {
namespace flocking {
2021-03-30 22:23:01 +00:00
void init ( ) ;
2018-11-17 19:25:47 +00:00
int N ;
2021-04-23 18:58:49 +00:00
bool draw_lines = false , draw_tails = false ;
2018-11-17 19:25:47 +00:00
2018-11-19 17:59:22 +00:00
int follow = 0 ;
string follow_names [ 3 ] = { " nothing " , " specific boid " , " center of mass " } ;
2021-04-07 12:47:13 +00:00
ld follow_dist = 0 ;
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 ;
2021-04-23 18:58:49 +00:00
bool swarm ;
2019-04-21 18:50:20 +00:00
char shape = ' b ' ;
2020-07-29 21:34:00 +00:00
vector < tuple < shiftpoint , shiftpoint , color_t > > lines ;
2018-11-17 19:25:47 +00:00
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-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 ;
2021-04-23 18:58:49 +00:00
while ( delta > precision & & iter < ( swarm ? 10000 : 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 ) ;
2020-03-20 18:49:59 +00:00
vector < transmatrix > oris ( N ) ;
2018-11-17 19:25:47 +00:00
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 ( ) ;
2021-04-23 18:58:49 +00:00
if ( swarm ) for ( int i = 0 ; i < N ; i + + ) {
vertexdata & vd = vdata [ i ] ;
auto m = vd . m ;
apply_parallel_transport ( m - > at , m - > ori , xtangent ( 0.01 ) ) ; // max_speed * d));
fixmatrix ( m - > at ) ;
2018-11-17 19:25:47 +00:00
2021-04-23 18:58:49 +00:00
virtualRebase ( m ) ;
}
if ( ! swarm ) parallelize ( N , [ & monsat , & d , & vels , & pats , & oris ] ( 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 ;
2020-03-20 18:49:59 +00:00
transmatrix I , Rot ;
2020-03-21 08:34:50 +00:00
bool use_rot = true ;
2020-03-20 18:49:59 +00:00
2020-03-21 08:34:50 +00:00
if ( prod ) {
I = inverse ( m - > at ) ;
Rot = inverse ( m - > ori ) ;
}
else if ( nonisotropic ) {
2020-03-20 18:49:59 +00:00
I = gpushxto0 ( tC0 ( m - > at ) ) ;
Rot = inverse ( I * m - > at ) ;
}
else {
I = inverse ( m - > at ) ;
Rot = Id ;
2020-03-21 08:34:50 +00:00
use_rot = false ;
2020-03-20 18:49:59 +00:00
}
2018-11-17 19:25:47 +00:00
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))
2020-07-29 21:34:00 +00:00
hyperpoint ac = inverse_exp ( shiftless ( tC0 ( at2 ) ) ) ;
2020-03-21 08:34:50 +00:00
if ( use_rot ) ac = Rot * ac ;
2018-11-20 11:50:00 +00:00
// distance and azimuth to m2
2020-03-20 18:49:59 +00:00
ld di = hypot_d ( WDIM , 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
2020-03-20 18:49:59 +00:00
// note: in nonisotropic it is not clear whether we should
// use gpushxto0, or parallel transport along the shortest geodesic
align + = gpushxto0 ( tC0 ( at2 ) ) * at2 * hpxyz ( vel2 , 0 , 0 ) ;
2018-11-17 19:25:47 +00:00
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 ) {
2020-03-20 18:49:59 +00:00
coh + = tangent_length ( ac , di ) ;
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 ) {
2020-03-20 18:49:59 +00:00
sep - = tangent_length ( ac , 1 / di ) ;
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 ;
}
2020-03-20 18:49:59 +00:00
pats [ i ] = m - > at ;
oris [ i ] = m - > ori ;
rotate_object ( pats [ i ] , oris [ i ] , alphaspin ) ;
apply_parallel_transport ( pats [ i ] , oris [ i ] , xtangent ( vels [ i ] * d ) ) ;
2019-04-21 18:48:15 +00:00
fixmatrix ( pats [ i ] ) ;
2020-03-21 08:34:50 +00:00
/* RogueViz does not correctly rotate them */
if ( prod ) {
hyperpoint h = oris [ i ] * xtangent ( 1 ) ;
pats [ i ] = pats [ i ] * spin ( - atan2 ( h [ 1 ] , h [ 0 ] ) ) ;
oris [ i ] = spin ( + atan2 ( h [ 1 ] , h [ 0 ] ) ) * oris [ i ] ;
}
2019-04-21 18:49:32 +00:00
} return 0 ; } ) ;
2021-04-23 18:58:49 +00:00
if ( ! swarm ) for ( int i = 0 ; i < N ; i + + ) {
2018-11-17 19:25:47 +00:00
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 ] ;
2020-03-20 18:49:59 +00:00
m - > ori = oris [ 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 ) {
2020-03-29 13:14:23 +00:00
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 ( ) ;
2020-07-29 21:34:00 +00:00
vdata [ 0 ] . m - > pat = shiftless ( View * calc_relative_matrix ( vdata [ 0 ] . m - > base , centerover , C0 ) * vdata [ 0 ] . m - > at ) ;
View = inverse ( vdata [ 0 ] . m - > pat . T ) * View ;
2020-03-21 08:34:50 +00:00
if ( prod ) {
NLP = inverse ( vdata [ 0 ] . m - > ori ) ;
NLP = hr : : cspin ( 1 , 2 , 90 * degree ) * spin ( 90 * degree ) * NLP ;
if ( NLP [ 0 ] [ 2 ] ) {
auto downspin = - atan2 ( NLP [ 0 ] [ 2 ] , NLP [ 1 ] [ 2 ] ) ;
NLP = spin ( downspin ) * NLP ;
}
}
else {
View = spin ( 90 * degree ) * View ;
if ( GDIM = = 3 ) {
View = hr : : cspin ( 1 , 2 , 90 * degree ) * View ;
}
2021-04-07 12:47:13 +00:00
shift_view ( ztangent ( follow_dist ) ) ;
2020-03-21 08:34:50 +00:00
}
2019-04-21 18:51:47 +00:00
}
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 ;
2020-03-21 08:34:50 +00:00
int cnt = 0 ;
ld lev = 0 ;
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 ;
2020-07-29 21:34:00 +00:00
auto h1 = unshift ( tC0 ( vdata [ i ] . m - > pat ) ) ;
2020-03-21 08:34:50 +00:00
cnt + + ;
if ( prod ) {
auto d1 = product_decompose ( h1 ) ;
lev + = d1 . first ;
h + = d1 . second ;
}
else
h + = h1 ;
2019-04-21 18:51:47 +00:00
}
2020-03-21 08:34:50 +00:00
if ( cnt ) {
h = normalize_flat ( h ) ;
if ( prod ) h = zshift ( h , lev / cnt ) ;
2020-03-20 18:50:15 +00:00
View = inverse ( actual_view_transform ) * gpushxto0 ( h ) * actual_view_transform * View ;
2021-04-07 12:47:13 +00:00
shift_view ( ztangent ( follow_dist ) ) ;
2020-03-20 18:50:15 +00:00
}
2018-11-19 23:21:48 +00:00
}
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 ) ;
2021-04-23 18:58:49 +00:00
shift ( ) ; N = argi ( ) ; swarm = false ;
init ( ) ;
}
else if ( argis ( " -swarming " ) ) {
PHASEFROM ( 2 ) ;
shift ( ) ; N = argi ( ) ; swarm = true ;
init ( ) ;
}
else if ( argis ( " -flocktails " ) ) {
PHASEFROM ( 2 ) ;
draw_tails = true ;
2018-11-17 19:25:47 +00:00
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 ;
}
2020-03-20 18:50:23 +00:00
else if ( argis ( " -flockspd " ) ) {
shift ( ) ; ini_speed = argf ( ) ;
shift ( ) ; max_speed = argf ( ) ;
}
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 ' ) ;
2021-04-23 18:58:49 +00:00
dialog : : addBoolItem_action ( " draw tails " , draw_tails , ' t ' ) ;
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 ; } ) ;
2021-04-07 12:47:13 +00:00
dialog : : addSelItem ( " follow distance " , fts ( follow_dist ) , ' d ' ) ;
dialog : : add_action ( [ ] ( ) {
dialog : : editNumber ( follow_dist , - 1 , 1 , 0.1 , 0 , " follow distance " , " " ) ;
follow + + ; follow % = 3 ;
} ) ;
2018-11-19 17:59:22 +00:00
2018-11-17 19:25:47 +00:00
dialog : : addBreak ( 100 ) ;
dialog : : addItem ( " restart " , ' r ' ) ;
dialog : : add_action ( init ) ;
dialog : : addBack ( ) ;
dialog : : display ( ) ;
}
2020-05-15 13:53:29 +00:00
void o_key ( o_funcs & v ) {
2021-03-30 22:23:01 +00:00
v . push_back ( named_dialog ( " flocking " , show ) ) ;
2018-11-17 19:25:47 +00:00
}
2021-04-23 18:58:49 +00:00
bool drawVertex ( const shiftmatrix & V , cell * c , shmup : : monster * m ) {
if ( draw_tails ) {
int i = m - > pid ;
vertexdata & vd = vdata [ i ] ;
vid . linewidth * = 3 ;
queueline ( V * m - > at * C0 , V * m - > at * xpush0 ( - 3 ) , vd . cp . color2 & 0xFFFFFFF3F , 6 ) ;
vid . linewidth / = 3 ;
}
return false ;
}
2021-03-30 22:23:01 +00:00
void init ( ) {
if ( ! bounded ) {
addMessage ( " Flocking simulation needs a bounded space. " ) ;
return ;
}
stop_game ( ) ;
2021-03-30 23:10:45 +00:00
rogueviz : : init ( RV_GRAPH ) ;
2021-03-30 22:23:01 +00:00
rv_hook ( shmup : : hooks_turn , 100 , turn ) ;
rv_hook ( hooks_frame , 100 , flock_marker ) ;
rv_hook ( hooks_o_key , 80 , o_key ) ;
2021-04-23 18:58:49 +00:00
rv_hook ( shmup : : hooks_draw , 90 , drawVertex ) ;
2021-03-30 22:23:01 +00:00
vdata . resize ( N ) ;
const auto v = currentmap - > allcells ( ) ;
printf ( " computing relmatrices... \n " ) ;
// relmatrices[c1][c2] is the matrix we have to multiply by to
// change from c1-relative coordinates to c2-relative coordinates
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 ( hypot_d ( WDIM , inverse_exp ( shiftless ( tC0 ( T ) ) ) ) < = check_range ) {
relmatrices [ c1 ] [ c2 ] = T ;
forCellEx ( c3 , c2 ) cl . add ( c3 ) ;
}
}
}
2021-04-23 18:58:49 +00:00
ld angle ;
if ( swarm ) angle = hrand ( 1000 ) ;
2021-03-30 22:23:01 +00:00
printf ( " setting up... \n " ) ;
for ( int i = 0 ; i < N ; i + + ) {
vertexdata & vd = vdata [ i ] ;
// set initial base and at to random cell and random position there
2021-04-23 18:58:49 +00:00
createViz ( i , v [ swarm ? 0 : hrand ( isize ( v ) ) ] , Id ) ;
2021-04-07 12:49:06 +00:00
vd . m - > pat . T = Id ;
2021-04-23 18:58:49 +00:00
if ( swarm ) {
rotate_object ( vd . m - > pat . T , vd . m - > ori , spin ( angle ) ) ;
apply_parallel_transport ( vd . m - > pat . T , vd . m - > ori , xtangent ( i * - 0.015 ) ) ;
}
else {
rotate_object ( vd . m - > pat . T , vd . m - > ori , random_spin ( ) ) ;
apply_parallel_transport ( vd . m - > pat . T , vd . m - > ori , xtangent ( hrandf ( ) / 2 ) ) ;
rotate_object ( vd . m - > pat . T , vd . m - > ori , random_spin ( ) ) ;
}
2021-03-30 22:23:01 +00:00
vd . name = its ( i + 1 ) ;
vd . cp = dftcolor ;
2021-04-23 18:58:49 +00:00
if ( swarm )
vd . cp . color2 =
( rainbow_color ( 0.5 , i * 1. / N ) < < 8 ) | 0xFF ;
else
vd . cp . color2 =
( ( hrand ( 0x1000000 ) < < 8 ) + 0xFF ) | 0x808080FF ;
2021-03-30 22:23:01 +00:00
vd . cp . shade = shape ;
vd . m - > vel = ini_speed ;
2021-04-07 11:04:30 +00:00
vd . m - > at = vd . m - > pat . T ;
2021-03-30 22:23:01 +00:00
}
storeall ( ) ;
printf ( " done \n " ) ;
}
2021-04-07 12:49:20 +00:00
void set_follow ( ) {
follow = ( 1 + follow ) % 3 ;
addMessage ( " following: " + follow_names [ follow ] ) ;
}
void flock_slide ( tour : : presmode mode , int _N , reaction_t t ) {
using namespace tour ;
setCanvas ( mode , ' 0 ' ) ;
if ( mode = = pmStart ) {
2021-04-07 18:58:31 +00:00
slide_backup ( mapeditor : : drawplayer ) ;
2021-04-07 12:49:20 +00:00
t ( ) ;
slide_backup ( rogueviz : : vertex_shape , 3 ) ;
N = _N ; start_game ( ) ; init ( ) ;
}
if ( mode = = pmKey ) set_follow ( ) ;
}
auto hooks = addHook ( hooks_args , 100 , readArgs )
+ addHook ( rogueviz : : pres : : hooks_build_rvtour , 187 , [ ] ( string s , vector < tour : : slide > & v ) {
if ( s ! = " mixed " ) return ;
using namespace tour ;
string cap = " flocking simulation/ " ;
string help = " \n \n Press '5' to make the camera follow boids, or 'o' to change more parameters. " ;
v . push_back ( slide {
cap + " Euclidean flocking " , 10 , LEGAL : : NONE | QUICKGEO ,
" This is an Euclidean flocking simulation. Boids move according to the following rules: \n \n "
" - separation: they avoid running into other boids \n "
" - alignment: steer toward the average heading of local flockmates \n "
" - cohesion: steer toward the average position of local flockmates \n \n "
" In the Euclidean space, these rules will cause all the boids to align, and fly in the same direction in a nice flock. " + help
,
[ ] ( presmode mode ) {
2021-04-07 12:51:50 +00:00
slide_url ( mode , ' w ' , " Wikipedia link " , " https://en.wikipedia.org/wiki/Boids " ) ;
2021-04-07 12:49:20 +00:00
flock_slide ( mode , 50 , [ ] {
set_geometry ( gEuclid ) ;
set_variation ( eVariation : : bitruncated ) ;
auto & T0 = euc : : eu_input . user_axes ;
restorers . push_back ( [ ] { euc : : build_torus3 ( ) ; } ) ;
slide_backup ( euc : : eu_input ) ;
T0 [ 0 ] [ 0 ] = T0 [ 1 ] [ 1 ] = 3 ;
T0 [ 1 ] [ 0 ] = T0 [ 0 ] [ 1 ] = 0 ;
euc : : eu_input . twisted = 0 ;
euc : : build_torus3 ( ) ;
} ) ;
} } ) ;
v . push_back ( slide {
cap + " spherical flocking " , 10 , LEGAL : : NONE | QUICKGEO ,
" Same parameters, but in spherical geometry. \n \n "
" Since parallel lines work differently, the boids do not align that nicely. "
" However, the curvature helps them to maintain a coherent flock. "
+ help
,
[ ] ( presmode mode ) {
flock_slide ( mode , 50 , [ ] {
set_geometry ( gSphere ) ;
set_variation ( eVariation : : bitruncated ) ;
} ) ;
} } ) ;
v . push_back ( slide {
cap + " Hyperbolic flocking " , 10 , LEGAL : : NONE | QUICKGEO ,
" Same parameters, but the geometry is hyperbolic. Our boids fly in the Klein quartic. \n "
" This time, negative curvature prevents our boids from maintaining a coherent flock. "
+ help
,
[ ] ( presmode mode ) {
flock_slide ( mode , 50 , [ ] {
set_geometry ( gKleinQuartic ) ;
set_variation ( eVariation : : bitruncated ) ;
} ) ;
} } ) ;
v . push_back ( slide {
cap + " Hyperbolic flocking again " , 10 , LEGAL : : NONE | QUICKGEO ,
" Our boids still fly in the Klein quartic, but now the parameters are changed to "
" make the alignment and cohesion stronger. "
,
[ ] ( presmode mode ) {
slide_url ( mode , ' t ' , " Twitter link " , " https://twitter.com/ZenoRogue/status/1064660283581505536 " ) ;
flock_slide ( mode , 50 , [ ] {
set_geometry ( gKleinQuartic ) ;
set_variation ( eVariation : : bitruncated ) ;
slide_backup ( align_factor , 2 ) ;
slide_backup ( align_range , 2 ) ;
slide_backup ( coh_factor , 2 ) ;
} ) ;
} } ) ;
v . push_back ( slide {
cap + " Hyperbolic flocking in 3D " , 10 , LEGAL : : NONE | QUICKGEO ,
" Let's try a three-dimensional hyperbolic manifold. Alignment and cohesion are strong again. "
,
[ ] ( presmode mode ) {
slide_url ( mode , ' y ' , " YouTube link " , " https://www.youtube.com/watch?v=kng_4lE0uzo " ) ;
flock_slide ( mode , 50 , [ ] {
set_geometry ( gSpace534 ) ;
field_quotient_3d ( 5 , 0x72414D0C ) ;
slide_backup ( align_factor , 2 ) ;
slide_backup ( align_range , 2 ) ;
slide_backup ( coh_factor , 2 ) ;
slide_backup ( vid . grid , true ) ;
slide_backup ( follow_dist , 1 ) ;
} ) ;
} } ) ;
} ) ;
2018-11-17 19:25:47 +00:00
# endif
}
}