2019-08-10 11:43:24 +00:00
// Hyperbolic Rogue -- screenshots and animations
2018-07-19 21:46:58 +00:00
// Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
2019-08-10 11:43:24 +00:00
/** \file screenshot.cpp
* \ brief screenshots , SVG format , animations , start animations
*/
2019-09-05 07:15:40 +00:00
# include "hyper.h"
2018-07-19 21:46:58 +00:00
namespace hr {
2020-09-23 12:56:45 +00:00
EX bool hide_hud = true ;
2019-02-06 15:34:42 +00:00
2019-08-09 23:31:44 +00:00
# if HDR
namespace shot { void default_screenshot_content ( ) ; }
# endif
2018-07-19 21:46:58 +00:00
// svg renderer
2019-08-09 23:31:44 +00:00
EX namespace svg {
2020-04-06 06:37:22 +00:00
# if !CAP_SVG
EX always_false in ;
# endif
# if CAP_SVG
2018-12-13 13:53:44 +00:00
# if ISWEB
shstream f ;
# else
fhstream f ;
# endif
2019-08-09 23:31:44 +00:00
EX bool in = false ;
2023-03-16 13:47:04 +00:00
EX bool remove_out = true ;
2018-07-19 21:46:58 +00:00
2018-09-04 17:53:42 +00:00
ld cta ( color_t col ) {
2018-07-19 21:46:58 +00:00
// col >>= 24;
col & = 0xFF ;
return col / 255.0 ;
}
2018-09-04 17:53:42 +00:00
bool invisible ( color_t col ) { return ( col & 0xFF ) = = 0 ; }
2018-07-19 21:46:58 +00:00
void fixgamma ( unsigned int & color ) {
unsigned char * c = ( unsigned char * ) ( & color ) ;
2018-12-13 12:33:08 +00:00
for ( int i = 1 ; i < 4 ; i + + ) c [ i ] = 255 * pow ( float ( c [ i ] / 255.0 ) , float ( shot : : gamma ) ) ;
2018-07-19 21:46:58 +00:00
}
int svgsize ;
2019-09-06 06:17:02 +00:00
EX int divby = 10 ;
2018-07-19 21:46:58 +00:00
const char * coord ( int val ) {
static char buf [ 10 ] [ 20 ] ;
static int id ;
id + + ; id % = 10 ;
if ( divby = = 1 ) {
sprintf ( buf [ id ] , " %d " , val ) ; return buf [ id ] ;
}
else if ( divby < = 10 ) {
sprintf ( buf [ id ] , " %.1f " , val * 1. / divby ) ; return buf [ id ] ;
}
else {
sprintf ( buf [ id ] , " %.2f " , val * 1. / divby ) ; return buf [ id ] ;
}
}
2019-02-06 17:39:53 +00:00
char * stylestr ( color_t fill , color_t stroke , ld width = 1 ) {
2018-07-19 21:46:58 +00:00
fixgamma ( fill ) ;
fixgamma ( stroke ) ;
static char buf [ 600 ] ;
// printf("fill = %08X stroke = %08x\n", fill, stroke);
2018-11-01 18:03:11 +00:00
if ( stroke = = 0xFF00FF & & false ) {
2018-07-19 21:46:58 +00:00
stroke = 0x000000FF ;
if ( fill = = 0x332a22ff ) fill = 0x000000FF ;
else if ( fill = = 0x686868FF ) fill = 0x000000FF ;
else if ( fill = = 0xd0d0d0FF ) fill = 0x000000FF ;
else fill = 0xFFFFFFFF ;
}
sprintf ( buf , " style= \" stroke:#%06x;stroke-opacity:%.3 " PLDF " ;stroke-width:% " PLDF " px;fill:#%06x;fill-opacity:%.3 " PLDF " \" " ,
( stroke > > 8 ) & 0xFFFFFF , cta ( stroke ) ,
width / divby ,
( fill > > 8 ) & 0xFFFFFF , cta ( fill )
) ;
return buf ;
}
2019-08-09 23:31:44 +00:00
EX void circle ( int x , int y , int size , color_t col , color_t fillcol , double linewidth ) {
2018-11-09 19:50:08 +00:00
if ( ! invisible ( col ) | | ! invisible ( fillcol ) ) {
2020-04-16 22:53:58 +00:00
if ( pconf . stretch = = 1 )
2018-12-13 16:03:39 +00:00
println ( f , " <circle cx=' " , coord ( x ) , " ' cy=' " , coord ( y ) , " ' r=' " , coord ( size ) , " ' " , stylestr ( fillcol , col , linewidth ) , " /> " ) ;
2018-08-01 02:01:16 +00:00
else
2020-04-16 22:53:58 +00:00
println ( f , " <ellipse cx=' " , coord ( x ) , " ' cy=' " , coord ( y ) , " ' rx=' " , coord ( size ) , " ' ry=' " , coord ( size * pconf . stretch ) , " ' " , stylestr ( fillcol , col ) , " /> " ) ;
2018-08-01 02:01:16 +00:00
}
2018-07-19 21:46:58 +00:00
}
2019-08-09 23:31:44 +00:00
EX string link ;
2018-07-19 21:46:58 +00:00
void startstring ( ) {
2018-12-13 13:53:44 +00:00
if ( link ! = " " ) print ( f , " <a xlink:href= \" " , link , " \" xlink:show= \" replace \" > " ) ;
2018-07-19 21:46:58 +00:00
}
void stopstring ( ) {
2018-12-13 13:53:44 +00:00
if ( link ! = " " ) print ( f , " </a> " ) ;
2018-07-19 21:46:58 +00:00
}
string font = " Times " ;
2019-09-03 06:28:01 +00:00
ld text_width_multiplier = 1 / 40. ;
int min_text = 3 ;
2019-08-09 23:31:44 +00:00
EX void text ( int x , int y , int size , const string & str , bool frame , color_t col , int align ) {
2019-09-03 06:28:01 +00:00
if ( size < min_text ) return ;
2018-07-19 21:46:58 +00:00
2018-11-17 18:24:02 +00:00
double dfc = ( x - current_display - > xcenter ) * ( x - current_display - > xcenter ) +
( y - current_display - > ycenter ) * ( y - current_display - > ycenter ) ;
dfc / = current_display - > radius ;
dfc / = current_display - > radius ;
2018-07-19 21:46:58 +00:00
// 0 = center, 1 = edge
dfc = 1 - dfc ;
col = 0xFF + ( col < < 8 ) ;
bool uselatex = font = = " latex " ;
if ( ! invisible ( col ) ) {
startstring ( ) ;
string str2 = " " ;
for ( int i = 0 ; i < ( int ) str . size ( ) ; i + + )
if ( str [ i ] = = ' & ' )
str2 + = " & " ;
else if ( str [ i ] = = ' < ' )
str2 + = " < " ;
else if ( str [ i ] = = ' > ' )
str2 + = " > " ;
else if ( uselatex & & str [ i ] = = ' # ' )
str2 + = " \\ # " ;
else str2 + = str [ i ] ;
if ( uselatex ) str2 = string ( " \\ myfont{ " ) + coord ( size ) + " }{ " + str2 + " } " ;
2018-12-13 13:53:44 +00:00
print ( f , " <text x=' " , coord ( x ) , " ' y=' " , coord ( y + size * .4 ) , " ' text-anchor=' " , align = = 8 ? " middle " :
2018-07-19 21:46:58 +00:00
align < 8 ? " start " :
2018-12-13 13:53:44 +00:00
" end " , " ' " ) ;
2018-07-19 21:46:58 +00:00
if ( ! uselatex )
2018-12-13 13:53:44 +00:00
print ( f , " font-family=' " , font , " ' font-size=' " , coord ( size ) , " ' " ) ;
print ( f ,
2019-09-03 06:28:01 +00:00
stylestr ( col , frame ? 0x0000000FF : 0 , ( 1 < < get_sightrange ( ) ) * dfc * text_width_multiplier ) ,
2018-12-13 13:53:44 +00:00
" > " , str2 , " </text> " ) ;
2018-07-19 21:46:58 +00:00
stopstring ( ) ;
2018-12-13 13:53:44 +00:00
println ( f ) ;
2018-07-19 21:46:58 +00:00
}
}
2019-08-09 23:31:44 +00:00
EX void polygon ( int * polyx , int * polyy , int polyi , color_t col , color_t outline , double linewidth ) {
2018-07-19 21:46:58 +00:00
if ( invisible ( col ) & & invisible ( outline ) ) return ;
if ( polyi < 2 ) return ;
2018-10-28 02:06:31 +00:00
2023-03-16 13:47:04 +00:00
if ( remove_out ) {
int minx = polyx [ 0 ] , miny = polyy [ 0 ] ;
int maxx = minx , maxy = miny ;
for ( int i = 0 ; i < polyi ; i + + ) if ( polyx [ i ] < minx ) minx = polyx [ i ] ; else if ( polyx [ i ] > maxx ) maxx = polyx [ i ] ;
for ( int i = 0 ; i < polyi ; i + + ) if ( polyy [ i ] < miny ) miny = polyy [ i ] ; else if ( polyy [ i ] > maxy ) maxy = polyy [ i ] ;
if ( maxx < 0 | | maxy < 0 | | minx > vid . xres | | miny > vid . yres ) return ;
}
2018-07-19 21:46:58 +00:00
startstring ( ) ;
for ( int i = 0 ; i < polyi ; i + + ) {
if ( i = = 0 )
2018-12-13 13:53:44 +00:00
print ( f , " <path d= \" M " ) ;
2018-07-19 21:46:58 +00:00
else
2018-12-13 13:53:44 +00:00
print ( f , " L " ) ;
print ( f , coord ( polyx [ i ] ) , " " , coord ( polyy [ i ] ) ) ;
2018-07-19 21:46:58 +00:00
}
2018-12-13 13:53:44 +00:00
print ( f , " \" " , stylestr ( col , outline , ( hyperbolic ? current_display - > radius : current_display - > scrsize ) * linewidth / 256 ) , " /> " ) ;
2018-07-19 21:46:58 +00:00
stopstring ( ) ;
2018-12-13 13:53:44 +00:00
println ( f ) ;
2018-07-19 21:46:58 +00:00
}
2019-08-09 23:31:44 +00:00
EX void render ( const string & fname , const function < void ( ) > & what IS ( shot : : default_screenshot_content ) ) {
2018-07-19 21:46:58 +00:00
dynamicval < bool > v2 ( in , true ) ;
2018-12-13 12:33:08 +00:00
dynamicval < bool > v3 ( vid . usingGL , false ) ;
2018-07-19 21:46:58 +00:00
2018-12-13 13:53:44 +00:00
# if ISWEB
f . s = " " ;
# else
f . f = fopen ( fname . c_str ( ) , " wt " ) ;
# endif
println ( f , " <svg xmlns= \" http://www.w3.org/2000/svg \" xmlns:xlink= \" http://www.w3.org/1999/xlink \" width= \" " , coord ( vid . xres ) , " \" height= \" " , coord ( vid . yres ) , " \" > " ) ;
2018-12-13 16:57:26 +00:00
if ( ! shot : : transparent )
2018-12-21 13:44:55 +00:00
println ( f , " <rect width= \" " , coord ( vid . xres ) , " \" height= \" " , coord ( vid . yres ) , " \" " , stylestr ( ( backcolor < < 8 ) | 0xFF , 0 , 0 ) , " /> " ) ;
2018-08-18 15:44:35 +00:00
what ( ) ;
2018-12-13 13:53:44 +00:00
println ( f , " </svg> " ) ;
# if ISWEB
EM_ASM_ ( {
var x = window . open ( ) ;
x . document . open ( ) ;
2020-01-29 17:19:10 +00:00
x . document . write ( UTF8ToString ( $ 0 ) ) ;
2018-12-13 13:53:44 +00:00
x . document . close ( ) ;
} , f . s . c_str ( ) ) ;
# else
2019-01-24 13:48:53 +00:00
fclose ( f . f ) ; f . f = NULL ;
2018-12-13 13:53:44 +00:00
# endif
2018-07-19 21:46:58 +00:00
}
2019-02-17 17:43:39 +00:00
# if CAP_COMMANDLINE && CAP_SHOT
2018-07-19 21:46:58 +00:00
int read_args ( ) {
using namespace arg ;
if ( argis ( " -svgsize " ) ) {
2018-12-13 12:33:08 +00:00
shift ( ) ; sscanf ( argcs ( ) , " %d/%d " , & shot : : shoty , & svg : : divby ) ;
if ( shot : : shotformat = = - 1 ) shot : : shotformat = 0 ;
2018-07-19 21:46:58 +00:00
}
else if ( argis ( " -svgfont " ) ) {
shift ( ) ; svg : : font = args ( ) ;
// note: use '-svgfont latex' to produce text output as: \myfont{size}{text}
// (this is helpful with Inkscape's PDF+TeX output feature; define \myfont yourself)
}
else if ( argis ( " -svggamma " ) ) {
2018-12-13 12:33:08 +00:00
shift_arg_formula ( shot : : gamma ) ;
2018-07-19 21:46:58 +00:00
}
2020-07-03 13:24:36 +00:00
else if ( argis ( " -svgfade " ) ) {
shift_arg_formula ( shot : : fade ) ;
}
2018-07-19 21:46:58 +00:00
else if ( argis ( " -svgshot " ) ) {
PHASE ( 3 ) ; shift ( ) ; start_game ( ) ;
printf ( " saving SVG screenshot to %s \n " , argcs ( ) ) ;
2020-04-16 12:36:45 +00:00
shot : : format = shot : : screenshot_format : : svg ;
2019-01-24 13:48:53 +00:00
shot : : take ( argcs ( ) ) ;
2018-07-19 21:46:58 +00:00
}
2019-09-03 06:28:01 +00:00
else if ( argis ( " -svgtwm " ) ) {
shift_arg_formula ( svg : : text_width_multiplier ) ;
}
else if ( argis ( " -svgmt " ) ) {
shift ( ) ; svg : : min_text = argi ( ) ;
}
2018-07-19 21:46:58 +00:00
else return 1 ;
return 0 ;
}
auto ah = addHook ( hooks_args , 0 , read_args ) ;
# endif
2021-02-04 13:58:39 +00:00
auto ah2 = addHook ( hooks_configfile , 100 , [ ] {
2021-02-07 19:42:09 +00:00
# if CAP_CONFIG
2021-01-31 17:53:52 +00:00
addsaver ( shot : : shotx , " shotx " ) ;
addsaver ( shot : : shoty , " shoty " ) ;
addsaverenum ( shot : : format , " shotsvg " ) ;
addsaver ( shot : : transparent , " shottransparent " ) ;
2021-02-01 00:45:10 +00:00
param_f ( shot : : gamma , " shotgamma " ) ;
2021-01-31 17:53:52 +00:00
addsaver ( shot : : caption , " shotcaption " ) ;
2021-02-01 00:45:10 +00:00
param_f ( shot : : fade , " shotfade " ) ;
2021-02-07 19:42:09 +00:00
# endif
2021-01-31 17:53:52 +00:00
} ) ;
2018-07-19 21:46:58 +00:00
# endif
2020-04-06 06:37:22 +00:00
EX }
2020-04-15 09:49:29 +00:00
/** wrl renderer */
EX namespace wrl {
# if !CAP_WRL
EX always_false in ;
# endif
# if CAP_WRL
EX bool in ;
2020-04-15 14:21:02 +00:00
EX bool print ;
2020-04-16 12:36:45 +00:00
EX bool textures = true ;
2020-04-15 14:21:02 +00:00
2020-04-15 15:07:39 +00:00
EX ld rug_width = .01 ;
2020-04-15 09:49:29 +00:00
fhstream f ;
2020-04-15 16:09:27 +00:00
string filename ;
2020-04-15 09:49:29 +00:00
string coord ( ld val ) {
char buf [ 100 ] ;
snprintf ( buf , 100 , " %f " , val ) ;
return buf ;
}
string coord ( const hyperpoint & v , int q ) {
char buf [ 100 ] ;
if ( q = = 3 ) snprintf ( buf , 100 , " %f, %f, %f " , v [ 0 ] , v [ 1 ] , v [ 2 ] ) ;
if ( q = = 2 ) snprintf ( buf , 100 , " %f, %f " , v [ 0 ] , v [ 1 ] ) ;
return buf ;
}
string color ( color_t col , ld v ) {
char buf [ 100 ] ;
2020-04-16 12:36:45 +00:00
ld cols [ 4 ] ;
for ( int i = 0 ; i < 4 ; i + + ) {
cols [ i ] = part ( col , i ) ;
cols [ i ] / = 255 ;
cols [ i ] = pow ( cols [ i ] , shot : : gamma ) * shot : : fade * v ;
}
snprintf ( buf , 100 , " %.3f %.3f %.3f " , cols [ 3 ] , cols [ 2 ] , cols [ 1 ] ) ;
2020-04-15 09:49:29 +00:00
return buf ;
}
2020-04-15 15:07:39 +00:00
typedef unsigned long long hashtype ;
hashtype hash ( ld x ) { return hashtype ( x * 1000000 + .5 ) ; }
hashtype hash ( hyperpoint h ) {
return hash ( h [ 0 ] ) + 7 * hash ( h [ 1 ] ) + 13 * hash ( h [ 2 ] ) ;
}
2020-04-15 16:09:27 +00:00
EX void fatten ( vector < hyperpoint > & data , vector < glvertex > & tdata ) {
2020-04-15 15:07:39 +00:00
map < hashtype , hyperpoint > normals ;
for ( int i = 0 ; i < isize ( data ) ; i + + )
normals [ hash ( data [ i ] ) ] = Hypc ;
for ( int i = 0 ; i < isize ( data ) ; i + + ) {
int j = i % 3 ? i - 1 : i + 2 ;
int k = j % 3 ? j - 1 : j + 2 ;
hyperpoint normal = ( data [ j ] - data [ i ] ) ^ ( data [ k ] - data [ i ] ) ;
2021-03-30 09:27:48 +00:00
# if MAXMDIM >= 4
2020-04-15 15:07:39 +00:00
normal [ 3 ] = 0 ;
2021-03-30 09:27:48 +00:00
# endif
2020-04-15 15:07:39 +00:00
if ( sqhypot_d ( 3 , normal ) < 1e-6 ) {
println ( hlog , " bug " , tie ( data [ i ] , data [ j ] , data [ k ] ) ) ;
}
normal / = hypot_d ( 3 , normal ) ;
auto & res = normals [ hash ( data [ i ] ) ] ;
ld q = res [ 3 ] ;
if ( ( res | normal ) < 0 ) res - = normal ;
else res + = normal ;
res [ 3 ] = q + 1 ;
}
for ( auto & p : normals ) {
auto w = hypot_d ( 3 , p . second ) ;
if ( w = = 0 ) println ( hlog , " width is 0, " , p . second , " appeared " , p . second [ 3 ] , " times " ) ;
if ( isnan ( w ) ) println ( hlog , " width is NAN, " , p . second , " appeared " , p . second [ 3 ] , " times " ) ;
p . second = p . second * ( rug_width / w ) ;
}
vector < hyperpoint > data2 ;
2020-04-15 16:09:27 +00:00
vector < glvertex > tdata2 ;
2020-04-15 15:07:39 +00:00
for ( int i = 0 ; i < isize ( data ) ; i + = 3 ) {
auto a = data [ i ] , b = data [ i + 1 ] , c = data [ i + 2 ] ;
hyperpoint normal = ( b - a ) ^ ( c - a ) ;
auto na = normals [ hash ( a ) ] ;
auto nb = normals [ hash ( b ) ] ;
auto nc = normals [ hash ( c ) ] ;
if ( ( normal | na ) > 0 ) na = - na ;
if ( ( normal | nb ) > 0 ) nb = - nb ;
if ( ( normal | nc ) > 0 ) nc = - nc ;
bool bad = false ;
for ( int i = 0 ; i < 3 ; i + + ) {
if ( isnan ( na [ i ] ) | | isnan ( nb [ i ] ) | | isnan ( nc [ i ] ) ) bad = true ;
}
if ( bad ) {
println ( hlog , " bad vertex " ) ;
continue ;
}
data2 . push_back ( a + na ) ; data2 . push_back ( b + nb ) ; data2 . push_back ( c + nc ) ;
data2 . push_back ( b + nb ) ; data2 . push_back ( a + na ) ; data2 . push_back ( a - na ) ;
data2 . push_back ( b + nb ) ; data2 . push_back ( a - na ) ; data2 . push_back ( b - nb ) ;
data2 . push_back ( c + nc ) ; data2 . push_back ( b + nb ) ; data2 . push_back ( b - nb ) ;
data2 . push_back ( c + nc ) ; data2 . push_back ( b - nb ) ; data2 . push_back ( c - nc ) ;
data2 . push_back ( a + na ) ; data2 . push_back ( c + nc ) ; data2 . push_back ( c - nc ) ;
data2 . push_back ( a + na ) ; data2 . push_back ( c - nc ) ; data2 . push_back ( a - na ) ;
data2 . push_back ( b - nb ) ; data2 . push_back ( a - na ) ; data2 . push_back ( c - nc ) ;
2020-04-15 16:09:27 +00:00
if ( isize ( tdata ) ) {
auto ta = tdata [ i ] , tb = tdata [ i + 1 ] , tc = tdata [ i + 2 ] ;
for ( auto p : { ta , tb , tc , tb , ta , ta , tb , ta , tb , tc , tb , tb , tc , tb , tc , ta , tc , tc , ta , tc , ta , tb , ta , tc } )
tdata2 . push_back ( p ) ;
}
2020-04-15 15:07:39 +00:00
}
data = data2 ;
2020-04-15 16:09:27 +00:00
tdata = tdata2 ;
2020-04-15 15:07:39 +00:00
}
2020-04-15 16:09:27 +00:00
bool used_rug ;
2020-04-16 01:36:48 +00:00
map < pair < color_t , glvertex > , int > texture_position ;
map < color_t , int > gradient_position ;
pair < color_t , glvertex > texid ( dqi_poly & p ) {
return make_pair ( p . color , p . tinf - > tvertices [ 0 ] ) ;
}
2020-04-16 12:36:45 +00:00
/** 0 = no/unknown/disabled texture, 1 = rug, 2 = gradient, 3 = floor texture */
2020-04-16 01:36:48 +00:00
EX int texture_type ( dqi_poly & p ) {
if ( ! p . tinf ) return 0 ;
2020-05-04 00:57:34 +00:00
# if CAP_PNG
2020-04-16 12:36:45 +00:00
if ( ! textures ) return 0 ;
2022-05-06 10:40:48 +00:00
# if CAP_RUG
2020-04-16 01:36:48 +00:00
if ( p . tinf = = & rug : : tinf ) return 1 ;
2022-05-06 10:40:48 +00:00
# endif
2020-04-17 13:36:47 +00:00
# if MAXMDIM >= 4
2020-04-16 01:36:48 +00:00
if ( p . tinf - > texture_id = = ( int ) floor_textures - > renderedTexture )
return ( p . tinf - > tvertices [ 0 ] [ 0 ] = = 0 ) ? 2 : 3 ;
2020-04-17 13:36:47 +00:00
# endif
2020-05-04 00:57:34 +00:00
# endif
2020-04-16 01:36:48 +00:00
return 0 ;
}
EX void prepare ( dqi_poly & p ) {
if ( print & & ! ( p . flags & POLY_PRINTABLE ) ) return ;
if ( ! ( p . flags & POLY_TRIANGLES ) ) return ;
int tt = texture_type ( p ) ;
if ( tt = = 2 ) gradient_position [ p . color ] = 0 ;
if ( tt = = 3 ) texture_position [ texid ( p ) ] = 0 ;
}
2020-04-17 13:36:47 +00:00
# if MAXMDIM >= 4
2020-04-16 01:36:48 +00:00
int fts_int , fts , fts_row ;
2020-04-17 13:36:47 +00:00
# endif
2020-04-16 01:36:48 +00:00
map < string , pair < vector < hyperpoint > , vector < glvertex > > > all_data ;
2020-04-15 09:49:29 +00:00
EX void polygon ( dqi_poly & p ) {
2020-04-15 14:21:02 +00:00
if ( print & & ! ( p . flags & POLY_PRINTABLE ) ) return ;
2020-04-15 09:49:29 +00:00
if ( ! ( p . flags & POLY_TRIANGLES ) ) return ;
2020-04-16 01:36:48 +00:00
int tt = texture_type ( p ) ;
2020-04-15 16:09:27 +00:00
2020-04-15 14:21:02 +00:00
vector < hyperpoint > data ;
2020-04-15 16:09:27 +00:00
vector < glvertex > tdata ;
2020-04-15 09:49:29 +00:00
for ( int i = 0 ; i < p . cnt ; i + + ) {
glvertex v = p . tab [ 0 ] [ p . offset + i ] ;
2020-04-15 14:21:02 +00:00
data . push_back ( glhr : : gltopoint ( v ) ) ;
2020-04-15 16:09:27 +00:00
if ( p . tinf )
tdata . push_back ( p . tinf - > tvertices [ p . offset_texture + i ] ) ;
2020-04-15 14:21:02 +00:00
}
for ( auto & d : data ) {
2020-07-27 16:49:04 +00:00
shiftpoint h = p . V * d ;
2020-04-15 14:21:02 +00:00
applymodel ( h , d ) ;
}
2020-04-15 15:07:39 +00:00
if ( print & & ( p . flags & POLY_FAT ) ) {
2020-04-15 16:09:27 +00:00
fatten ( data , tdata ) ;
2020-04-15 15:07:39 +00:00
p . cnt = isize ( data ) ;
}
else if ( print ) {
2020-04-15 14:21:02 +00:00
hyperpoint ctr1 ;
applymodel ( p . V * p . intester , ctr1 ) ;
2020-04-16 01:36:48 +00:00
println ( hlog , " intester = " , p . intester ) ;
2020-04-15 14:21:02 +00:00
ld sdet = 0 ;
if ( 1 ) {
dynamicval < eGeometry > g ( geometry , gEuclid ) ;
for ( int i = 0 ; i < p . cnt ; i + = 3 ) {
transmatrix T ;
2020-04-16 01:36:48 +00:00
T [ 0 ] = data [ i ] - ctr1 ;
T [ 1 ] = data [ i + 1 ] - ctr1 ;
T [ 2 ] = data [ i + 2 ] - ctr1 ;
2020-04-15 14:21:02 +00:00
sdet + = det ( T ) ;
}
2020-04-16 01:36:48 +00:00
println ( hlog , " sdet = " , sdet ) ;
2020-04-15 14:21:02 +00:00
if ( sdet > 0 )
2020-04-16 01:36:48 +00:00
for ( int i = 0 ; i < p . cnt ; i + = 3 ) {
swap ( data [ i + 1 ] , data [ i + 2 ] ) ;
if ( ! tdata . empty ( ) )
swap ( tdata [ i + 1 ] , tdata [ i + 2 ] ) ;
}
2020-04-15 14:21:02 +00:00
}
2020-04-15 09:49:29 +00:00
}
2020-04-16 01:36:48 +00:00
shstream app ;
println ( app , " material Material { " ) ;
if ( ! tt ) println ( app , " diffuseColor " , color ( p . color , .8 ) ) ;
if ( part ( p . color , 0 ) ! = 255 ) println ( app , " transparency " , ( 255 - part ( p . color , 0 ) ) / 255. ) ;
println ( app , " } " ) ;
if ( tt = = 1 ) {
println ( f , " texture ImageTexture { " ) ;
println ( app , " url \" " , filename , " -rug.png \" " ) ;
println ( app , " } " ) ;
used_rug = true ;
}
if ( tt = = 2 | | tt = = 3 ) {
println ( app , " texture ImageTexture { " ) ;
println ( app , " url \" " , filename , " -floors.png \" " ) ;
println ( app , " } " ) ;
2020-04-15 16:09:27 +00:00
}
2020-04-16 01:36:48 +00:00
auto & ad = all_data [ app . s ] ;
for ( auto & d : data ) ad . first . push_back ( d ) ;
2020-04-15 16:09:27 +00:00
2020-04-17 13:36:47 +00:00
# if MAXMDIM >= 4
2020-04-16 01:36:48 +00:00
if ( tt = = 2 ) {
ld x = ( fts - .5 - gradient_position [ p . color ] ) / fts ;
for ( auto & d : tdata ) d [ 0 ] = x ;
2020-04-15 09:49:29 +00:00
}
2020-04-16 01:36:48 +00:00
2020-07-03 12:42:33 +00:00
# if CAP_GL
2020-04-16 01:36:48 +00:00
if ( tt = = 3 ) {
int tp = texture_position [ texid ( p ) ] ;
auto xy = make_array < int > ( tp % fts_row , tp / fts_row ) ;
auto zero = p . tinf - > tvertices [ 0 ] ;
ld sca = FLOORTEXTURESIZE * 1. / fts_int ;
for ( auto & d : tdata )
for ( int c : { 0 , 1 } )
d [ c ] = ( ( d [ c ] - zero [ c ] ) * sca + xy [ c ] + .5 ) * fts_int / fts ;
}
2020-04-17 13:36:47 +00:00
# endif
2020-07-03 12:42:33 +00:00
# endif
2020-04-16 01:36:48 +00:00
for ( auto & d : tdata ) ad . second . push_back ( d ) ;
}
2020-04-15 09:49:29 +00:00
EX void render ( ) {
2020-04-17 13:36:47 +00:00
# if MAXMDIM >= 4
2020-04-16 01:36:48 +00:00
for ( auto & p : ptds ) {
auto p2 = dynamic_cast < dqi_poly * > ( & * p ) ;
if ( p2 )
prepare ( * p2 ) ;
}
int tps = 0 ;
for ( auto & p : texture_position ) p . second = tps + + ;
int gps = 0 ;
for ( auto & p : gradient_position ) p . second = gps + + ;
2020-07-03 12:42:33 +00:00
# if CAP_TEXTURE
2020-04-16 01:36:48 +00:00
fts_int = floor_texture_square_size * FLOORTEXTURESIZE + 4 ;
fts = 64 ;
while ( fts < gps | | ( fts - gps ) / fts_int * fts / fts_int < tps )
fts * = 2 ;
fts_row = ( fts - gps ) / fts_int ;
2020-04-17 13:36:47 +00:00
# endif
2020-07-03 12:42:33 +00:00
# endif
2020-04-16 01:36:48 +00:00
2020-04-15 09:49:29 +00:00
for ( auto & p : ptds ) {
auto p2 = dynamic_cast < dqi_poly * > ( & * p ) ;
if ( p2 )
polygon ( * p2 ) ;
}
}
EX void take ( const string & fname , const function < void ( ) > & what IS ( shot : : default_screenshot_content ) ) {
dynamicval < bool > v2 ( in , true ) ;
2020-04-15 16:08:55 +00:00
dynamicval < bool > v3 ( noshadow , true ) ;
2020-04-15 16:09:27 +00:00
filename = fname ;
2020-04-15 09:49:29 +00:00
2020-04-16 01:36:48 +00:00
ptds . clear ( ) ;
2020-04-16 12:36:45 +00:00
all_data . clear ( ) ;
2020-04-16 01:36:48 +00:00
what ( ) ;
2020-04-15 09:49:29 +00:00
f . f = fopen ( fname . c_str ( ) , " wt " ) ;
println ( f , " #VRML V2.0 utf8 " ) ;
println ( f , " WorldInfo { title \" 3D model exported from HyperRogue \" info [ \" 3D models exported from HyperRogue are public domain \" ] } " ) ;
2020-04-16 01:36:48 +00:00
for ( auto & p : all_data ) {
const string & app = p . first ;
auto & data = p . second . first ;
auto & tdata = p . second . second ;
println ( f , " Shape { " ) ;
println ( f , " appearance Appearance { " ) ;
println ( f , app ) ;
println ( f , " } " ) ;
// println(f, "# V = ", p.V);
println ( f , " geometry IndexedFaceSet { " ) ;
println ( f , " coord Coordinate { " ) ;
println ( f , " point [ " ) ;
for ( auto & d : data ) println ( f , " " , coord ( d , 3 ) , " , " ) ;
println ( f , " ] " ) ;
println ( f , " } " ) ;
if ( ! tdata . empty ( ) ) {
println ( f , " texCoord TextureCoordinate { " ) ;
println ( f , " point [ " ) ;
for ( auto & d : tdata )
println ( f , " " , coord ( glhr : : gltopoint ( d ) , 2 ) , " , " ) ;
println ( f , " ] " ) ;
println ( f , " } " ) ;
}
println ( f , " coordIndex [ " ) ;
for ( int i = 0 ; i < isize ( data ) ; i + = 3 ) {
println ( f , " " , i , " " , i + 1 , " " , i + 2 , " -1, " ) ;
}
println ( f , " ] " ) ;
if ( print )
println ( f , " creaseAngle 0.0 convex FALSE solid TRUE ccw FALSE " ) ;
else
println ( f , " creaseAngle 0.0 convex FALSE solid FALSE " ) ;
println ( f , " } " ) ;
println ( f , " } " ) ;
}
2020-04-15 16:09:27 +00:00
2020-04-16 19:00:28 +00:00
# if CAP_PNG
2022-05-06 10:40:48 +00:00
# if CAP_RUG
2020-04-15 16:09:27 +00:00
if ( used_rug ) {
resetbuffer rb ;
rug : : glbuf - > enable ( ) ;
SDL_Surface * s = rug : : glbuf - > render ( ) ;
dynamicval < int > dx ( shot : : shotx , rug : : texturesize ) ;
dynamicval < int > dy ( shot : : shoty , rug : : texturesize ) ;
shot : : postprocess ( filename + " -rug.png " , s , s ) ;
}
2022-05-06 10:40:48 +00:00
# endif
2020-04-16 01:36:48 +00:00
2020-04-17 13:36:47 +00:00
# if MAXMDIM >= 4
2020-04-16 01:36:48 +00:00
if ( isize ( texture_position ) | | isize ( gradient_position ) ) {
SDL_Surface * s = shot : : empty_surface ( fts , fts , false ) ;
for ( auto & p : gradient_position ) {
int x = fts - p . second - 1 ;
for ( int y = 0 ; y < fts ; y + + ) {
qpixel ( s , x , fts - y - 1 ) = gradient ( 0 , p . first , 0 , y , fts - 1 ) > > 8 ;
part ( qpixel ( s , x , y ) , 3 ) = 0xFF ;
}
}
SDL_Surface * floor = floor_textures - > render ( ) ;
for ( auto & p : texture_position ) {
int nx = p . second % fts_row ;
int ny = p . second / fts_row ;
color_t col = p . first . first ;
int xs = p . first . second [ 0 ] * FLOORTEXTURESIZE - fts_int / 2 ;
int ys = p . first . second [ 1 ] * FLOORTEXTURESIZE - fts_int / 2 ;
2020-04-16 12:36:45 +00:00
swap ( xs , ys ) ; // I do not understand why
2020-04-16 01:36:48 +00:00
for ( int y = 0 ; y < fts_int ; y + + )
for ( int x = 0 ; x < fts_int ; x + + ) {
auto & tgt = qpixel ( s , nx * fts_int + x , fts - 1 - ( ny * fts_int + y ) ) ;
2020-04-16 12:36:45 +00:00
auto & src = qpixel ( floor , xs + x , FLOORTEXTURESIZE - 1 - ( ys + y ) ) ;
2020-04-16 01:36:48 +00:00
for ( int p = 0 ; p < 3 ; p + + )
part ( tgt , p ) = ( part ( src , p ) * part ( col , p + 1 ) + 127 ) / 255 ;
part ( tgt , 3 ) = 0xFF ;
}
}
IMAGESAVE ( s , ( filename + " -floors.png " ) . c_str ( ) ) ;
SDL_FreeSurface ( s ) ;
}
2020-04-16 19:00:28 +00:00
# endif
2020-04-17 13:36:47 +00:00
# endif
2020-04-16 12:36:45 +00:00
fclose ( f . f ) ;
f . f = nullptr ;
2020-04-15 09:49:29 +00:00
}
# endif
EX }
2018-07-19 21:46:58 +00:00
# if CAP_PNG
void IMAGESAVE ( SDL_Surface * s , const char * fname ) {
SDL_Surface * s2 = SDL_PNGFormatAlpha ( s ) ;
SDL_SavePNG ( s2 , fname ) ;
2022-09-14 14:17:53 +00:00
if ( s ! = s2 ) SDL_FreeSurface ( s2 ) ;
2018-07-19 21:46:58 +00:00
}
# endif
2018-12-13 12:33:08 +00:00
# if CAP_SHOT
2019-08-09 23:31:44 +00:00
EX namespace shot {
2018-07-19 21:46:58 +00:00
2018-12-13 12:33:08 +00:00
purehookset hooks_hqshot ;
2018-07-19 21:46:58 +00:00
2020-04-16 12:36:45 +00:00
# if HDR
2020-05-22 12:30:45 +00:00
enum screenshot_format { png , svg , wrl , rawfile } ;
2020-04-16 12:36:45 +00:00
# endif
2020-05-22 12:30:45 +00:00
EX int rawfile_handle ;
2019-08-09 23:31:44 +00:00
EX int shotx = 2000 ;
EX int shoty = 2000 ;
2020-04-16 12:36:45 +00:00
EX screenshot_format format ;
2019-08-09 23:31:44 +00:00
EX bool transparent = true ;
EX ld gamma = 1 ;
EX int shotformat = - 1 ;
2019-09-05 10:00:55 +00:00
EX string caption ;
2019-08-09 23:31:44 +00:00
EX ld fade = 1 ;
2018-07-19 21:46:58 +00:00
2018-12-13 12:33:08 +00:00
void set_shotx ( ) {
if ( shotformat = = - 1 ) return ;
shotx = shoty ;
if ( shotformat = = 1 ) shotx = shotx * 4 / 3 ;
if ( shotformat = = 2 ) shotx = shotx * 16 / 9 ;
if ( shotformat = = 3 ) {
shotx = shotx * 22 / 16 ;
while ( shotx & 15 ) shotx + + ;
}
}
2018-07-19 21:46:58 +00:00
2019-09-10 07:23:28 +00:00
EX int shot_aa = 1 ;
2018-07-19 21:46:58 +00:00
2019-08-09 23:31:44 +00:00
EX void default_screenshot_content ( ) {
2018-07-19 21:46:58 +00:00
2022-07-05 09:51:06 +00:00
gamescreen ( ) ;
2020-05-08 19:21:42 +00:00
2018-12-13 12:33:08 +00:00
if ( caption ! = " " )
displayfr ( vid . xres / 2 , vid . fsize + vid . fsize / 4 , 3 , vid . fsize * 2 , caption , forecolor , 8 ) ;
callhooks ( hooks_hqshot ) ;
drawStats ( ) ;
}
2018-07-19 21:46:58 +00:00
2020-07-03 12:42:33 +00:00
# if CAP_SDL
2020-04-16 01:36:48 +00:00
EX SDL_Surface * empty_surface ( int x , int y , bool alpha ) {
return SDL_CreateRGBSurface ( SDL_SWSURFACE , x , y , 32 , 0xFF < < 16 , 0xFF < < 8 , 0xFF , ( alpha ) ? ( 0xFF < < 24 ) : 0 ) ;
}
2020-07-03 12:42:33 +00:00
# endif
2020-04-16 01:36:48 +00:00
2018-12-13 12:33:08 +00:00
# if CAP_PNG
2020-05-22 12:30:45 +00:00
2022-07-17 10:49:25 +00:00
EX void output ( SDL_Surface * s , const string & fname ) {
2020-05-22 12:30:45 +00:00
if ( format = = screenshot_format : : rawfile ) {
for ( int y = 0 ; y < shoty ; y + + )
2020-05-22 12:50:10 +00:00
ignore ( write ( rawfile_handle , & qpixel ( s , 0 , y ) , 4 * shotx ) ) ;
2020-05-22 12:30:45 +00:00
}
else
IMAGESAVE ( s , fname . c_str ( ) ) ;
}
2022-07-17 10:49:25 +00:00
EX hookset < bool ( string , SDL_Surface * , SDL_Surface * ) > hooks_postprocess ;
2020-04-15 16:09:27 +00:00
EX void postprocess ( string fname , SDL_Surface * sdark , SDL_Surface * sbright ) {
2022-07-17 10:49:25 +00:00
if ( callhandlers ( false , hooks_postprocess , fname , sdark , sbright ) ) return ;
2018-12-21 13:44:43 +00:00
if ( gamma = = 1 & & shot_aa = = 1 & & sdark = = sbright ) {
2020-05-22 12:30:45 +00:00
output ( sdark , fname ) ;
2018-12-13 12:33:08 +00:00
return ;
}
2020-04-16 01:36:48 +00:00
SDL_Surface * sout = empty_surface ( shotx , shoty , sdark ! = sbright ) ;
2018-12-13 12:33:08 +00:00
for ( int y = 0 ; y < shoty ; y + + )
for ( int x = 0 ; x < shotx ; x + + ) {
int val [ 2 ] [ 4 ] ;
for ( int a = 0 ; a < 2 ; a + + ) for ( int b = 0 ; b < 3 ; b + + ) val [ a ] [ b ] = 0 ;
for ( int ax = 0 ; ax < shot_aa ; ax + + ) for ( int ay = 0 ; ay < shot_aa ; ay + + )
for ( int b = 0 ; b < 2 ; b + + ) for ( int p = 0 ; p < 3 ; p + + )
val [ b ] [ p ] + = part ( qpixel ( ( b ? sbright : sdark ) , x * shot_aa + ax , y * shot_aa + ay ) , p ) ;
int transparent = 0 ;
int maxval = 255 * 3 * shot_aa * shot_aa ;
for ( int p = 0 ; p < 3 ; p + + ) transparent + = val [ 1 ] [ p ] - val [ 0 ] [ p ] ;
color_t & pix = qpixel ( sout , x , y ) ;
pix = 0 ;
part ( pix , 3 ) = 255 - ( 255 * transparent + ( maxval / 2 ) ) / maxval ;
if ( transparent < maxval ) for ( int p = 0 ; p < 3 ; p + + ) {
ld v = ( val [ 0 ] [ p ] * 3. / maxval ) / ( 1 - transparent * 1. / maxval ) ;
v = pow ( v , gamma ) * fade ;
v * = 255 ;
if ( v > 255 ) v = 255 ;
part ( pix , p ) = v ;
}
2018-07-19 21:46:58 +00:00
}
2020-05-22 12:30:45 +00:00
output ( sout , fname ) ;
2019-08-06 19:09:50 +00:00
SDL_FreeSurface ( sout ) ;
2018-12-13 12:33:08 +00:00
}
# endif
2018-07-19 21:46:58 +00:00
2019-12-23 19:52:41 +00:00
EX purehookset hooks_take ;
2020-04-16 12:36:45 +00:00
# if CAP_PNG
void render_png ( string fname , const function < void ( ) > & what ) {
resetbuffer rb ;
renderbuffer glbuf ( vid . xres , vid . yres , vid . usingGL ) ;
glbuf . enable ( ) ;
current_display - > set_viewport ( 0 ) ;
dynamicval < color_t > v8 ( backcolor , transparent ? 0xFF000000 : backcolor ) ;
# if CAP_RUG
if ( rug : : rugged & & ! rug : : renderonce ) rug : : prepareTexture ( ) ;
# endif
glbuf . clear ( backcolor ) ;
what ( ) ;
SDL_Surface * sdark = glbuf . render ( ) ;
if ( transparent ) {
renderbuffer glbuf1 ( vid . xres , vid . yres , vid . usingGL ) ;
backcolor = 0xFFFFFFFF ;
# if CAP_RUG
if ( rug : : rugged & & ! rug : : renderonce ) rug : : prepareTexture ( ) ;
# endif
glbuf1 . enable ( ) ;
glbuf1 . clear ( backcolor ) ;
current_display - > set_viewport ( 0 ) ;
what ( ) ;
postprocess ( fname , sdark , glbuf1 . render ( ) ) ;
}
else postprocess ( fname , sdark , sdark ) ;
}
# endif
2019-08-09 23:31:44 +00:00
EX void take ( string fname , const function < void ( ) > & what IS ( default_screenshot_content ) ) {
2018-07-19 21:46:58 +00:00
2018-12-13 12:33:08 +00:00
if ( cheater ) doOvergenerate ( ) ;
2020-04-16 12:36:45 +00:00
2019-02-21 17:47:48 +00:00
# if CAP_SVG
2020-04-16 12:36:45 +00:00
int multiplier = ( format = = screenshot_format : : svg ) ? svg : : divby : shot_aa ;
2019-02-21 17:47:48 +00:00
# else
int multiplier = shot_aa ;
# endif
2021-03-06 10:47:54 +00:00
vector < bool > chg ;
2023-08-08 23:01:32 +00:00
for ( auto & ap : anims : : aps ) chg . push_back ( ap . par - > anim_unchanged ( ) ) ;
2021-03-06 10:47:54 +00:00
finalizer f ( [ & ] {
for ( int i = 0 ; i < isize ( anims : : aps ) ; i + + )
2023-08-08 23:01:32 +00:00
if ( chg [ i ] ) anims : : aps [ i ] . par - > anim_restore ( ) ;
2021-03-06 10:47:54 +00:00
} ) ;
2021-10-07 11:26:24 +00:00
if ( intra : : in ) what ( ) ;
2018-12-13 12:33:08 +00:00
dynamicval < videopar > v ( vid , vid ) ;
dynamicval < bool > v2 ( inHighQual , true ) ;
dynamicval < bool > v6 ( auraNOGL , true ) ;
2019-02-06 15:34:42 +00:00
dynamicval < bool > vn ( nohud , nohud | | hide_hud ) ;
2018-12-13 12:33:08 +00:00
vid . smart_range_detail * = multiplier ;
2018-07-19 21:46:58 +00:00
darken = 0 ;
2018-12-13 12:33:08 +00:00
set_shotx ( ) ;
vid . xres = shotx * multiplier ;
vid . yres = shoty * multiplier ;
2022-04-12 10:55:11 +00:00
vid . fsize * = multiplier ;
2018-12-13 12:33:08 +00:00
calcparam ( ) ;
2019-08-09 22:58:50 +00:00
models : : configure ( ) ;
2019-12-23 19:52:41 +00:00
callhooks ( hooks_take ) ;
2018-12-13 12:33:08 +00:00
2020-04-16 12:36:45 +00:00
switch ( format ) {
case screenshot_format : : wrl :
# if CAP_WRL
wrl : : take ( fname ) ;
# endif
2021-09-16 19:30:26 +00:00
break ;
2018-07-19 21:46:58 +00:00
2020-04-16 12:36:45 +00:00
case screenshot_format : : svg :
# if CAP_SVG
svg : : render ( fname , what ) ;
2018-12-13 12:33:08 +00:00
# endif
2021-09-16 19:30:26 +00:00
break ;
2020-04-16 12:36:45 +00:00
case screenshot_format : : png :
2020-05-22 12:30:45 +00:00
case screenshot_format : : rawfile :
2020-04-16 12:36:45 +00:00
# if CAP_PNG
render_png ( fname , what ) ;
# endif
2021-09-16 19:30:26 +00:00
break ;
2020-04-16 12:36:45 +00:00
}
2021-09-16 19:30:26 +00:00
v . backup . plevel_factor = vid . plevel_factor ;
2018-07-19 21:46:58 +00:00
}
# if CAP_COMMANDLINE
int png_read_args ( ) {
using namespace arg ;
if ( argis ( " -pngshot " ) ) {
PHASE ( 3 ) ; shift ( ) ; start_game ( ) ;
printf ( " saving PNG screenshot to %s \n " , argcs ( ) ) ;
2020-04-16 12:36:45 +00:00
format = screenshot_format : : png ;
2018-12-13 12:33:08 +00:00
shot : : take ( argcs ( ) ) ;
2018-07-19 21:46:58 +00:00
}
else if ( argis ( " -pngsize " ) ) {
2018-12-13 12:33:08 +00:00
shift ( ) ; shoty = argi ( ) ; if ( shotformat = = - 1 ) shotformat = 0 ;
2018-07-19 21:46:58 +00:00
}
else if ( argis ( " -pngformat " ) ) {
2018-12-13 12:33:08 +00:00
shift ( ) ; shotformat = argi ( ) ;
2018-07-19 21:46:58 +00:00
}
2019-01-24 13:48:53 +00:00
else if ( argis ( " -shotxy " ) ) {
shift ( ) ; shotformat = - 1 ; shotx = argi ( ) ; shift ( ) ; shoty = argi ( ) ;
}
2020-07-03 13:24:36 +00:00
else if ( argis ( " -shothud " ) ) {
shift ( ) ; hide_hud = ! argi ( ) ;
}
2018-12-21 13:45:09 +00:00
else if ( argis ( " -shott " ) ) {
shift ( ) ; shot : : transparent = argi ( ) ;
}
2020-05-22 12:50:23 +00:00
else if ( argis ( " -shot-fhd " ) ) {
shot : : shotformat = - 1 ;
shot : : shotx = 1920 ;
2020-05-25 00:32:06 +00:00
shot : : shoty = 1080 ;
2020-05-22 12:50:23 +00:00
shot : : transparent = false ;
}
else if ( argis ( " -shot-hd " ) ) {
shot : : shotformat = - 1 ;
shot : : shotx = 1280 ;
2020-05-25 00:32:06 +00:00
shot : : shoty = 720 ;
2020-05-22 12:50:23 +00:00
shot : : transparent = false ;
}
2021-08-05 11:22:29 +00:00
else if ( argis ( " -shot-qfhd " ) ) {
shot : : shotformat = - 1 ;
shot : : shotx = 960 ;
shot : : shoty = 540 ;
shot : : transparent = false ;
}
else if ( argis ( " -shot-qhd " ) ) {
shot : : shotformat = - 1 ;
shot : : shotx = 640 ;
shot : : shoty = 360 ;
shot : : transparent = false ;
}
2020-05-22 12:50:23 +00:00
else if ( argis ( " -shot-1000 " ) ) {
shot : : shotformat = - 1 ;
shot : : shotx = 1000 ;
2020-05-25 00:32:06 +00:00
shot : : shoty = 1000 ;
2020-05-22 12:50:23 +00:00
shot : : transparent = false ;
}
2021-04-07 16:30:33 +00:00
else if ( argis ( " -shot-500 " ) ) {
shot : : shotformat = - 1 ;
shot : : shotx = 500 ;
shot : : shoty = 500 ;
shot : : transparent = false ;
}
2021-03-06 10:48:15 +00:00
else if ( argis ( " -shot-vertical " ) ) {
shot : : shotformat = - 1 ;
shot : : shotx = 720 ;
shot : : shoty = 1080 ;
shot : : transparent = false ;
}
2019-06-11 08:20:13 +00:00
else if ( argis ( " -shotaa " ) ) {
shift ( ) ; shot_aa = argi ( ) ;
}
2020-04-16 19:00:28 +00:00
# if CAP_WRL
2020-04-15 14:21:02 +00:00
else if ( argis ( " -modelshot " ) ) {
PHASE ( 3 ) ; shift ( ) ; start_game ( ) ;
printf ( " saving WRL model to %s \n " , argcs ( ) ) ;
2020-04-16 12:36:45 +00:00
shot : : format = screenshot_format : : wrl ; wrl : : print = false ;
2020-04-15 14:21:02 +00:00
shot : : take ( argcs ( ) ) ;
}
else if ( argis ( " -printshot " ) ) {
2020-04-15 09:49:29 +00:00
PHASE ( 3 ) ; shift ( ) ; start_game ( ) ;
2020-04-15 14:21:02 +00:00
printf ( " saving 3D printable model to %s \n " , argcs ( ) ) ;
2020-04-16 12:36:45 +00:00
shot : : format = screenshot_format : : wrl ; wrl : : print = true ;
2020-04-15 09:49:29 +00:00
shot : : take ( argcs ( ) ) ;
}
2020-04-16 19:00:28 +00:00
# endif
2018-07-19 21:46:58 +00:00
else return 1 ;
return 0 ;
}
auto ah_png = addHook ( hooks_args , 0 , png_read_args ) ;
# endif
2018-09-10 15:58:36 +00:00
2020-04-16 12:36:45 +00:00
EX string format_name ( ) {
if ( format = = screenshot_format : : svg ) return " SVG " ;
if ( format = = screenshot_format : : wrl ) return " WRL " ;
if ( format = = screenshot_format : : png ) return " PNG " ;
return " ? " ;
}
EX string format_extension ( ) {
if ( format = = screenshot_format : : svg ) return " .svg " ;
if ( format = = screenshot_format : : wrl ) return " .wrl " ;
if ( format = = screenshot_format : : png ) return " .png " ;
return " ? " ;
}
EX void choose_screenshot_format ( ) {
cmode = sm : : SIDE ;
2022-07-05 09:51:06 +00:00
gamescreen ( ) ;
2020-04-16 12:36:45 +00:00
dialog : : init ( XLAT ( " screenshots " ) , iinf [ itPalace ] . color , 150 , 100 ) ;
# if CAP_PNG
dialog : : addItem ( XLAT ( " PNG " ) , ' p ' ) ;
dialog : : add_action ( [ ] { format = screenshot_format : : png ; popScreen ( ) ; } ) ;
# endif
# if CAP_SVG
dialog : : addItem ( XLAT ( " SVG " ) , ' s ' ) ;
dialog : : add_action ( [ ] { format = screenshot_format : : svg ; popScreen ( ) ; } ) ;
# endif
# if CAP_WRL
dialog : : addItem ( XLAT ( " WRL " ) , ' w ' ) ;
dialog : : add_action ( [ ] { format = screenshot_format : : wrl ; popScreen ( ) ; } ) ;
# endif
dialog : : addBack ( ) ;
dialog : : display ( ) ;
}
2019-08-09 23:31:44 +00:00
EX void menu ( ) {
2018-12-13 12:33:08 +00:00
cmode = sm : : SIDE ;
2022-07-05 09:51:06 +00:00
gamescreen ( ) ;
2020-04-16 12:36:45 +00:00
if ( format = = screenshot_format : : svg & & ! CAP_SVG )
format = screenshot_format : : png ;
if ( format = = screenshot_format : : png & & ! CAP_PNG )
format = screenshot_format : : svg ;
2018-12-13 12:33:08 +00:00
dialog : : init ( XLAT ( " screenshots " ) , iinf [ itPalace ] . color , 150 , 100 ) ;
2020-04-16 12:36:45 +00:00
dialog : : addSelItem ( XLAT ( " format " ) , format_name ( ) , ' f ' ) ;
dialog : : add_action_push ( choose_screenshot_format ) ;
bool dowrl = format = = screenshot_format : : wrl ;
if ( ! dowrl ) {
dialog : : addSelItem ( XLAT ( " pixels (X) " ) , its ( shotx ) , ' x ' ) ;
dialog : : add_action ( [ ] { shotformat = - 1 ; dialog : : editNumber ( shotx , 500 , 8000 , 100 , 2000 , XLAT ( " pixels (X) " ) , " " ) ; } ) ;
dialog : : addSelItem ( XLAT ( " pixels (Y) " ) , its ( shoty ) , ' y ' ) ;
dialog : : add_action ( [ ] { shotformat = - 1 ; dialog : : editNumber ( shoty , 500 , 8000 , 100 , 2000 , XLAT ( " pixels (Y) " ) , " " ) ; } ) ;
2018-12-13 12:33:08 +00:00
}
2020-04-16 12:36:45 +00:00
switch ( format ) {
case screenshot_format : : svg : {
# if CAP_SVG
using namespace svg ;
dialog : : addSelItem ( XLAT ( " precision " ) , " 1/ " + its ( divby ) , ' p ' ) ;
dialog : : add_action ( [ ] { divby * = 10 ; if ( divby > 1000000 ) divby = 1 ; } ) ;
# endif
2020-04-16 22:53:58 +00:00
if ( models : : is_3d ( vpconf ) | | rug : : rugged ) {
2020-04-16 12:36:45 +00:00
dialog : : addInfo ( " SVG screenshots do not work in this 3D mode " , 0xFF0000 ) ;
2021-02-01 14:10:03 +00:00
if ( GDIM = = 2 & & ! rug : : rugged )
menuitem_projection ( ' 1 ' ) ;
2020-04-16 12:36:45 +00:00
# if CAP_WRL
else {
dialog : : addItem ( XLAT ( " WRL " ) , ' w ' ) ;
dialog : : add_action ( [ ] { format = screenshot_format : : wrl ; } ) ;
}
# endif
}
# if CAP_TEXTURE
if ( texture : : config . tstate = = texture : : tsActive )
dialog : : addInfo ( " SVG screenshots do not work with textures " , 0xFF0000 ) ;
# endif
break ;
}
2020-05-22 12:30:45 +00:00
case screenshot_format : : rawfile :
2020-04-16 12:36:45 +00:00
case screenshot_format : : png : {
# if CAP_PNG
dialog : : addSelItem ( XLAT ( " supersampling " ) , its ( shot_aa ) , ' s ' ) ;
dialog : : add_action ( [ ] { shot_aa * = 2 ; if ( shot_aa > 16 ) shot_aa = 1 ; } ) ;
# endif
break ;
}
case screenshot_format : : wrl : {
# if CAP_WRL
2020-04-16 22:53:58 +00:00
if ( ! models : : is_3d ( vpconf ) & & ! rug : : rugged ) {
2020-04-16 12:36:45 +00:00
dialog : : addInfo ( " this format is for 3D projections " , 0xFF0000 ) ;
2020-07-03 12:42:33 +00:00
# if CAP_RUG
2020-04-16 12:36:45 +00:00
if ( GDIM = = 2 ) {
dialog : : addItem ( XLAT ( " hypersian rug mode " ) , ' u ' ) ;
dialog : : add_action_push ( rug : : show ) ;
}
2020-07-03 12:42:33 +00:00
# endif
2020-04-16 12:36:45 +00:00
}
2020-07-03 12:42:33 +00:00
# if CAP_RUG
2020-04-16 22:53:58 +00:00
else if ( rug : : rugged ? rug : : perspective ( ) : models : : is_perspective ( vpconf . model ) ) {
2020-07-03 12:42:33 +00:00
# else
else if ( models : : is_perspective ( vpconf . model ) ) {
# endif
2020-04-16 12:36:45 +00:00
dialog : : addInfo ( " this does not work well in perspective projections " , 0xFF8000 ) ;
2021-02-01 14:10:03 +00:00
menuitem_projection ( ' 1 ' ) ;
2020-04-16 12:36:45 +00:00
}
dialog : : addBoolItem_action ( " generate a model for 3D printing " , wrl : : print , ' p ' ) ;
2020-04-16 19:00:28 +00:00
# if CAP_PNG
2020-04-16 12:36:45 +00:00
dialog : : addBoolItem_action ( " use textures " , wrl : : textures , ' u ' ) ;
# endif
2020-04-16 19:00:28 +00:00
# endif
2020-04-16 12:36:45 +00:00
}
2018-12-13 12:33:08 +00:00
}
2020-04-16 12:36:45 +00:00
if ( ! dowrl ) dialog : : addBoolItem_action ( XLAT ( " transparent " ) , transparent , ' t ' ) ;
2018-12-13 12:33:08 +00:00
dialog : : addSelItem ( XLAT ( " gamma " ) , fts ( gamma ) , ' g ' ) ;
dialog : : add_action ( [ ] { dialog : : editNumber ( gamma , 0 , 2 , .1 , .5 , XLAT ( " gamma " ) , " higher value = darker " ) ; } ) ;
dialog : : addSelItem ( XLAT ( " brightness " ) , fts ( fade ) , ' b ' ) ;
dialog : : add_action ( [ ] { dialog : : editNumber ( fade , 0 , 2 , .1 , 1 , XLAT ( " brightness " ) , " higher value = lighter " ) ; } ) ;
2020-04-16 12:36:45 +00:00
if ( ! dowrl ) dialog : : addBoolItem_action ( XLAT ( " disable the HUD " ) , hide_hud , ' h ' ) ;
2020-04-01 10:01:55 +00:00
2020-04-16 12:36:45 +00:00
dialog : : addBoolItem_action_neg ( XLAT ( " hide the player " ) , mapeditor : : drawplayer , ' H ' ) ;
2020-04-16 19:00:28 +00:00
# if CAP_WRL
2020-04-16 12:36:45 +00:00
if ( dowrl & & wrl : : print ) dialog : : lastItem ( ) . value = XLAT ( " N/A " ) ;
2020-04-16 19:00:28 +00:00
# endif
2020-04-16 12:36:45 +00:00
2020-04-01 10:01:55 +00:00
if ( WDIM = = 2 ) {
2020-04-26 09:07:06 +00:00
dialog : : addItem ( XLAT ( " centering " ) , ' C ' ) ;
2020-04-01 10:01:55 +00:00
dialog : : add_action ( [ ] {
dialog : : editNumber ( vid . fixed_facing_dir , 0 , 360 , 15 , 90 , XLAT ( " centering " ) ,
XLAT ( " You can pick the angle. Note: the direction the PC is facing matters. " ) ) ;
2023-08-09 12:01:24 +00:00
dialog : : get_di ( ) . reaction = fullcenter ;
dialog : : get_di ( ) . extra_options = [ ] ( ) {
2020-04-26 09:07:06 +00:00
dialog : : addBoolItem ( XLAT ( " rotate PC " ) , centering = = eCentering : : face , ' R ' ) ;
dialog : : add_action ( [ ] {
flipplayer = false ;
cwt + + ;
mirror : : act ( 1 , mirror : : SPINSINGLE ) ;
cwt . at - > mondir + + ;
cwt . at - > mondir % = cwt . at - > type ;
fullcenter ( ) ;
} ) ;
2020-04-01 10:01:55 +00:00
dialog : : addBoolItem ( XLAT ( " face " ) , centering = = eCentering : : face , ' F ' ) ;
dialog : : add_action ( [ ] { centering = eCentering : : face ; fullcenter ( ) ; } ) ;
dialog : : addBoolItem ( XLAT ( " edge " ) , centering = = eCentering : : edge , ' E ' ) ;
dialog : : add_action ( [ ] { centering = eCentering : : edge ; fullcenter ( ) ; } ) ;
dialog : : addBoolItem ( XLAT ( " vertex " ) , centering = = eCentering : : vertex , ' V ' ) ;
dialog : : add_action ( [ ] { centering = eCentering : : vertex ; fullcenter ( ) ; } ) ;
} ;
} ) ;
}
2018-12-13 12:33:08 +00:00
2019-03-30 22:59:51 +00:00
dialog : : addItem ( XLAT ( " colors & aura " ) , ' c ' ) ;
dialog : : add_action_push ( show_color_dialog ) ;
2018-12-13 12:33:08 +00:00
menuitem_sightrange ( ' r ' ) ;
dialog : : addBreak ( 100 ) ;
dialog : : addItem ( XLAT ( " take screenshot " ) , ' z ' ) ;
dialog : : add_action ( [ ] ( ) {
2018-12-13 13:53:44 +00:00
# if ISWEB
shot : : take ( " new window " ) ;
# else
2018-12-13 12:33:08 +00:00
static string pngfile = " hqshot.png " ;
static string svgfile = " svgshot.svg " ;
2020-04-16 12:36:45 +00:00
static string wrlfile = " model.wrl " ;
string & file =
format = = screenshot_format : : png ? pngfile :
format = = screenshot_format : : svg ? svgfile :
wrlfile ;
dialog : : openFileDialog ( file , XLAT ( " screenshot " ) , format_extension ( ) , [ & file ] ( ) {
2019-08-06 19:10:52 +00:00
dynamicval < int > cgl ( vid . cells_generated_limit , 9999999 ) ;
2018-12-13 12:33:08 +00:00
shot : : take ( file ) ;
return true ;
} ) ;
2018-12-13 13:53:44 +00:00
# endif
2018-12-13 12:33:08 +00:00
} ) ;
dialog : : addBack ( ) ;
dialog : : display ( ) ;
}
2019-08-09 23:31:44 +00:00
EX }
2018-09-10 15:58:36 +00:00
# endif
# if CAP_ANIMATIONS
2019-08-09 19:00:52 +00:00
EX namespace anims {
2018-09-10 15:58:36 +00:00
2020-04-07 15:17:45 +00:00
# if HDR
2018-09-10 15:58:36 +00:00
enum eMovementAnimation {
2019-07-03 03:01:27 +00:00
maNone , maTranslation , maRotation , maCircle , maParabolic , maTranslationRotation
2018-09-10 15:58:36 +00:00
} ;
2020-04-07 15:17:45 +00:00
# endif
2018-09-10 15:58:36 +00:00
2020-04-07 15:17:45 +00:00
EX eMovementAnimation ma ;
2018-09-10 15:58:36 +00:00
2023-08-13 11:38:36 +00:00
EX trans23 movement_angle ;
2019-08-09 19:00:52 +00:00
EX ld normal_angle = 90 ;
EX ld period = 10000 ;
EX int noframes = 30 ;
2022-11-12 21:38:45 +00:00
EX ld cycle_length = TAU ;
2019-08-09 19:00:52 +00:00
EX ld parabolic_length = 1 ;
2022-04-21 10:33:37 +00:00
EX string time_formula = " - " ;
2018-09-10 15:58:36 +00:00
int lastticks , bak_turncount ;
2023-08-13 11:38:36 +00:00
EX ld rug_rotation1 , rug_rotation2 , rug_forward , env_ocean , env_volcano ;
2019-08-09 19:00:52 +00:00
EX bool env_shmup ;
2023-08-13 11:38:36 +00:00
EX transmatrix rug_angle = Id , rug_movement_angle = cspin ( 0 , 1 , 90. _deg ) ;
2018-09-10 15:58:36 +00:00
2020-07-08 13:49:29 +00:00
EX ld rotation_distance ;
2019-11-13 23:26:50 +00:00
cell * rotation_center ;
2018-09-10 15:58:36 +00:00
transmatrix rotation_center_View ;
2023-08-13 11:38:36 +00:00
EX void ma_reaction ( ) {
println ( hlog , " ma_reaction called " ) ;
if ( ma = = maCircle ) start_game ( ) ;
rotation_center = centerover ;
rotation_center_View = View ;
}
EX color_t circle_display_color = 0x00FF00FF ;
2018-09-10 15:58:36 +00:00
2019-09-05 10:00:55 +00:00
EX ld circle_radius = acosh ( 2. ) ;
EX ld circle_spins = 1 ;
2018-09-10 15:58:36 +00:00
2020-01-02 15:57:12 +00:00
EX void moved ( ) {
2018-09-10 15:58:36 +00:00
optimizeview ( ) ;
2018-09-23 21:55:03 +00:00
if ( cheater | | autocheat ) {
2019-11-13 23:26:50 +00:00
if ( hyperbolic & & memory_saving_mode & & centerover & & gmatrix . size ( ) & & cwt . at ! = centerover & & ! quotient ) {
if ( isNeighbor ( cwt . at , centerover ) ) {
cwt . spin = neighborId ( centerover , cwt . at ) ;
2018-09-23 21:55:03 +00:00
flipplayer = true ;
}
2019-11-22 17:48:51 +00:00
animateMovement ( match ( cwt . at , centerover ) , LAYER_SMALL ) ;
2019-11-13 23:26:50 +00:00
cwt . at = centerover ;
2018-09-23 21:55:03 +00:00
save_memory ( ) ;
return ;
}
2019-11-13 23:26:50 +00:00
setdist ( centerover , 7 - getDistLimit ( ) - genrange_bonus , NULL ) ;
2018-09-23 21:55:03 +00:00
}
2018-09-10 15:58:36 +00:00
playermoved = false ;
}
2021-02-01 12:42:12 +00:00
# if HDR
2018-11-09 19:41:55 +00:00
struct animated_parameter {
2023-08-08 23:01:32 +00:00
setting * par ;
2018-11-09 19:41:55 +00:00
string formula ;
2018-09-10 15:58:36 +00:00
} ;
2021-02-01 12:42:12 +00:00
# endif
2018-09-10 15:58:36 +00:00
2021-02-01 12:42:12 +00:00
EX vector < animated_parameter > aps ;
2018-11-09 19:41:55 +00:00
2023-08-08 23:01:32 +00:00
EX setting * find_param ( void * x ) {
for ( auto & fs : params )
if ( fs . second - > affects ( x ) )
return & * fs . second ;
return nullptr ;
}
EX void deanimate ( setting * p ) {
2018-11-09 19:41:55 +00:00
for ( int i = 0 ; i < isize ( aps ) ; i + + )
2023-08-08 23:01:32 +00:00
if ( aps [ i ] . par = = p )
2018-11-09 19:41:55 +00:00
aps . erase ( aps . begin ( ) + ( i - - ) ) ;
}
2023-08-08 23:01:32 +00:00
EX void get_parameter_animation ( setting * p , string & s ) {
2018-11-09 19:41:55 +00:00
for ( auto & ap : aps )
2023-08-08 23:01:32 +00:00
if ( ap . par = = p & & ap . par - > anim_unchanged ( ) )
2018-11-09 19:41:55 +00:00
s = ap . formula ;
}
2023-08-08 23:01:32 +00:00
EX void animate_parameter ( ld & x , string f ) {
auto par = find_param ( & x ) ;
if ( ! par ) { println ( hlog , " parameter not animatable " ) ; return ; }
deanimate ( par ) ;
aps . emplace_back ( animated_parameter { par , f } ) ;
}
EX void animate_setting ( setting * par , string f ) {
if ( ! par ) { println ( hlog , " parameter not animatable " ) ; return ; }
deanimate ( par ) ;
aps . emplace_back ( animated_parameter { par , f } ) ;
2018-11-09 19:41:55 +00:00
}
2018-09-10 15:58:36 +00:00
2018-11-09 19:41:55 +00:00
int ap_changes ;
void apply_animated_parameters ( ) {
ap_changes = 0 ;
for ( auto & ap : aps ) {
2019-12-23 20:44:51 +00:00
try {
2023-08-08 23:01:32 +00:00
if ( ap . par - > load_from_animation ( ap . formula ) )
ap_changes + + ;
2019-12-23 20:44:51 +00:00
}
catch ( hr_parse_exception & ) {
continue ;
}
2018-11-09 19:41:55 +00:00
}
}
2018-09-10 15:58:36 +00:00
bool needs_highqual ;
2019-09-06 06:17:02 +00:00
EX void reflect_view ( ) {
2019-11-13 23:26:50 +00:00
if ( centerover ) {
2020-07-27 16:49:04 +00:00
shiftmatrix T = shiftless ( Id ) ;
2019-11-13 23:26:50 +00:00
cell * mbase = centerover ;
cell * c = centerover ;
2018-09-23 21:55:03 +00:00
if ( shmup : : reflect ( c , mbase , T ) )
2020-09-16 03:57:05 +00:00
View = iso_inverse ( T . T ) * View ;
2018-09-23 21:55:03 +00:00
}
}
2023-08-13 11:38:36 +00:00
EX bool clearup ;
2018-11-08 15:28:17 +00:00
2020-01-02 15:57:18 +00:00
EX purehookset hooks_anim ;
2020-11-08 11:43:07 +00:00
EX void animate_rug_movement ( ld t ) {
rug : : using_rugview urv ;
shift_view (
2023-08-13 11:38:36 +00:00
rug_movement_angle * xtangent ( t )
2020-11-08 11:43:07 +00:00
) ;
}
2020-11-14 12:11:01 +00:00
vector < reaction_t > on_rollback ;
2019-08-09 19:00:52 +00:00
EX void apply ( ) {
2018-09-10 15:58:36 +00:00
int t = ticks - lastticks ;
lastticks = ticks ;
2020-01-02 15:57:18 +00:00
callhooks ( hooks_anim ) ;
2018-09-23 21:55:03 +00:00
2018-09-10 15:58:36 +00:00
switch ( ma ) {
case maTranslation :
2019-08-09 22:58:50 +00:00
if ( history : : on ) {
history : : phase = ( isize ( history : : v ) - 1 ) * ticks * 1. / period ;
history : : movetophase ( ) ;
2018-09-10 15:58:36 +00:00
}
2019-11-13 23:26:50 +00:00
else if ( centerover ) {
2018-09-23 21:55:03 +00:00
reflect_view ( ) ;
if ( ( hyperbolic & & ! quotient & &
2019-11-13 23:26:50 +00:00
( centerover - > land ! = cwt . at - > land | | memory_saving_mode ) & & among ( centerover - > land , laHaunted , laIvoryTower , laDungeon , laEndorian ) & & centerover - > landparam > = 10
2018-09-10 17:39:44 +00:00
) ) {
2018-09-23 21:55:03 +00:00
if ( memory_saving_mode ) {
activateSafety ( laIce ) ;
return ;
}
else {
fullcenter ( ) ; View = spin ( rand ( ) % 1000 ) * View ;
}
2018-09-10 17:39:44 +00:00
}
2019-08-24 16:14:38 +00:00
shift_view (
2023-08-13 11:38:36 +00:00
movement_angle . get ( ) * xtangent ( cycle_length * t / period )
2019-08-24 16:14:38 +00:00
) ;
2018-09-10 15:58:36 +00:00
moved ( ) ;
2019-07-28 09:12:52 +00:00
if ( clearup ) {
2019-11-13 23:26:50 +00:00
centerover - > wall = waNone ;
forCellEx ( c1 , centerover ) c1 - > wall = waNone ;
2019-07-28 09:12:52 +00:00
}
2018-09-10 15:58:36 +00:00
}
break ;
2019-07-25 10:14:18 +00:00
2018-09-10 15:58:36 +00:00
case maRotation :
2023-08-13 11:38:36 +00:00
if ( GDIM = = 3 ) shift_view ( ztangent ( - rotation_distance ) ) ;
rotate_view ( rot_inverse ( movement_angle . get ( ) ) ) ;
2023-08-14 08:52:17 +00:00
rotate_view ( spin ( TAU * t / period ) ) ;
rotate_view ( movement_angle . get ( ) ) ;
2023-08-13 11:38:36 +00:00
if ( GDIM = = 3 ) shift_view ( ztangent ( rotation_distance ) ) ;
2020-07-08 13:49:29 +00:00
moved ( ) ;
2018-09-10 15:58:36 +00:00
break ;
2019-07-03 03:01:27 +00:00
case maTranslationRotation :
2019-08-24 16:14:38 +00:00
shift_view (
2023-08-14 08:52:17 +00:00
movement_angle . get ( ) * xtangent ( cycle_length * t / period )
2019-08-24 16:14:38 +00:00
) ;
2019-07-03 03:01:27 +00:00
moved ( ) ;
2022-11-12 21:38:45 +00:00
rotate_view ( cspin ( 0 , GDIM - 1 , TAU * t / period ) ) ;
2019-07-28 09:12:52 +00:00
if ( clearup ) {
2019-11-13 23:26:50 +00:00
centerover - > wall = waNone ;
2019-07-28 09:12:52 +00:00
}
2019-07-03 03:01:27 +00:00
break ;
2019-02-17 17:28:20 +00:00
# if CAP_BT
2018-09-10 15:58:36 +00:00
case maParabolic :
2018-09-23 21:55:03 +00:00
reflect_view ( ) ;
2023-08-14 08:52:17 +00:00
rotate_view ( rot_inverse ( movement_angle . get ( ) ) ) ;
2019-08-15 13:05:43 +00:00
if ( GDIM = = 2 )
2019-12-14 11:05:01 +00:00
View = bt : : parabolic ( parabolic_length * t / period ) * View ;
2019-03-21 02:23:06 +00:00
else
2019-12-14 11:05:01 +00:00
View = bt : : parabolic3 ( parabolic_length * t / period , 0 ) * View ;
2023-08-14 08:52:17 +00:00
rotate_view ( movement_angle . get ( ) ) ;
2018-09-10 15:58:36 +00:00
moved ( ) ;
break ;
2019-02-17 17:28:20 +00:00
# endif
2018-09-10 15:58:36 +00:00
case maCircle : {
2023-08-14 08:52:17 +00:00
rotate_view ( rot_inverse ( movement_angle . get ( ) ) ) ;
2019-11-13 23:26:50 +00:00
centerover = rotation_center ;
2022-11-12 21:38:45 +00:00
ld alpha = circle_spins * TAU * ticks / period ;
2018-09-10 15:58:36 +00:00
View = spin ( - cos_auto ( circle_radius ) * alpha ) * xpush ( circle_radius ) * spin ( alpha ) * rotation_center_View ;
2023-08-14 08:52:17 +00:00
rotate_view ( movement_angle . get ( ) ) ;
2018-09-10 15:58:36 +00:00
moved ( ) ;
break ;
}
default :
break ;
}
if ( env_ocean ) {
turncount + = env_ocean * ticks * tidalsize / period ;
calcTidalPhase ( ) ;
for ( auto & p : gmatrix ) if ( p . first - > land = = laOcean ) checkTide ( p . first ) ;
turncount - = ticks * tidalsize / period ;
}
if ( env_volcano ) {
2020-11-14 12:11:01 +00:00
auto bak_turncount = turncount ;
on_rollback . push_back ( [ bak_turncount ] { turncount = bak_turncount ; } ) ;
2018-09-10 15:58:36 +00:00
turncount + = env_volcano * ticks * 64 / period ;
for ( auto & p : gmatrix ) if ( p . first - > land = = laVolcano ) checkTide ( p . first ) ;
}
2019-02-17 17:45:42 +00:00
# if CAP_RUG
2018-09-10 15:58:36 +00:00
if ( rug : : rugged ) {
if ( rug_rotation1 ) {
2021-04-23 18:37:15 +00:00
rug : : using_rugview rv ;
2023-08-13 11:38:36 +00:00
rotate_view ( inverse ( rug_angle ) * cspin ( 0 , 2 , rug_rotation1 * TAU * t / period ) * rug_angle ) ;
2018-09-10 15:58:36 +00:00
}
if ( rug_rotation2 ) {
2021-04-23 18:37:15 +00:00
rug : : using_rugview rv ;
2022-11-12 21:38:45 +00:00
View = View * cspin ( 0 , 1 , rug_rotation2 * TAU * t / period ) ;
2018-09-10 15:58:36 +00:00
}
2020-11-08 11:43:07 +00:00
if ( rug_forward )
animate_rug_movement ( rug_forward * t / period ) ;
2018-09-10 15:58:36 +00:00
}
2019-02-17 17:45:42 +00:00
# endif
2023-08-13 11:37:20 +00:00
2018-11-09 19:41:55 +00:00
apply_animated_parameters ( ) ;
calcparam ( ) ;
2018-09-10 15:58:36 +00:00
}
2019-08-09 19:00:52 +00:00
EX void rollback ( ) {
2020-11-14 12:11:01 +00:00
while ( ! on_rollback . empty ( ) ) {
on_rollback . back ( ) ( ) ;
on_rollback . pop_back ( ) ;
2018-09-10 15:58:36 +00:00
}
}
2018-12-15 14:20:27 +00:00
# if CAP_FILES && CAP_SHOT
2019-08-09 19:00:52 +00:00
EX string animfile = " animation-%04d.png " ;
2018-09-10 15:58:36 +00:00
2020-05-22 12:30:45 +00:00
EX string videofile = " animation.mp4 " ;
2019-06-11 08:20:21 +00:00
int min_frame = 0 , max_frame = 999999 ;
2020-04-26 09:03:49 +00:00
int numturns = 0 ;
2021-02-18 14:53:39 +00:00
EX hookset < void ( int , int ) > hooks_record_anim ;
2020-11-14 13:18:57 +00:00
EX bool record_animation_of ( reaction_t content ) {
2018-11-11 15:35:27 +00:00
lastticks = 0 ;
2018-11-18 16:32:13 +00:00
ticks = 0 ;
2020-04-26 09:03:49 +00:00
int oldturn = - 1 ;
2018-09-10 15:58:36 +00:00
for ( int i = 0 ; i < noframes ; i + + ) {
2019-06-11 08:20:21 +00:00
if ( i < min_frame | | i > max_frame ) continue ;
2023-05-15 00:26:41 +00:00
println ( hlog , " record frame " , i , " / " , noframes , " of " , videofile ) ;
2021-02-18 14:53:39 +00:00
callhooks ( hooks_record_anim , i , noframes ) ;
2018-11-18 16:32:13 +00:00
int newticks = i * period / noframes ;
2022-04-21 10:33:37 +00:00
if ( time_formula ! = " - " ) {
dynamicval < int > t ( ticks , newticks ) ;
exp_parser ep ;
ep . s = time_formula ;
try {
newticks = ep . iparse ( ) ;
}
2022-04-21 11:29:12 +00:00
catch ( hr_parse_exception & e ) {
println ( hlog , " warning: failed to parse time_formula, " , e . s ) ;
2022-04-21 10:33:37 +00:00
}
}
2019-05-06 23:23:47 +00:00
cmode = ( env_shmup ? sm : : NORMAL : 0 ) ;
2018-11-18 16:32:13 +00:00
while ( ticks < newticks ) shmup : : turn ( 1 ) , ticks + + ;
2020-04-26 09:03:49 +00:00
if ( cheater & & numturns ) {
int nturn = numturns * i / noframes ;
if ( nturn ! = oldturn ) monstersTurn ( ) ;
oldturn = nturn ;
}
2019-05-06 23:23:47 +00:00
if ( playermoved ) centerpc ( INF ) , optimizeview ( ) ;
2019-04-07 01:09:52 +00:00
dynamicval < bool > v2 ( inHighQual , true ) ;
2019-08-09 22:58:50 +00:00
models : : configure ( ) ;
if ( history : : on ) {
ld len = ( isize ( history : : v ) - 1 ) + 2 * history : : extra_line_steps ;
history : : phase = len * i / ( noframes - 1 ) ;
if ( history : : lvspeed < 0 ) history : : phase = len - history : : phase ;
history : : phase - = history : : extra_line_steps ;
history : : movetophase ( ) ;
2018-10-23 14:58:19 +00:00
}
2018-09-10 15:58:36 +00:00
char buf [ 1000 ] ;
snprintf ( buf , 1000 , animfile . c_str ( ) , i ) ;
2020-11-14 12:17:55 +00:00
shot : : take ( buf , content ) ;
2018-09-10 15:58:36 +00:00
}
2018-11-11 15:43:43 +00:00
lastticks = ticks = SDL_GetTicks ( ) ;
2018-09-10 15:58:36 +00:00
return true ;
}
2020-11-14 13:18:57 +00:00
EX bool record_animation ( ) {
return record_animation_of ( shot : : default_screenshot_content ) ;
}
2018-09-10 15:58:36 +00:00
# endif
2020-05-25 00:32:15 +00:00
EX purehookset hooks_after_video ;
2020-05-22 12:30:45 +00:00
# if CAP_VIDEO
EX bool record_video ( string fname IS ( videofile ) , bool_reaction_t rec IS ( record_animation ) ) {
array < int , 2 > tab ;
if ( pipe ( & tab [ 0 ] ) ) {
addMessage ( format ( " Error: %s " , strerror ( errno ) ) ) ;
return false ;
}
println ( hlog , " tab = " , tab ) ;
int pid = fork ( ) ;
if ( pid = = 0 ) {
close ( 0 ) ;
2020-05-22 12:50:10 +00:00
if ( dup ( tab [ 0 ] ) ! = 0 ) exit ( 1 ) ;
if ( close ( tab [ 1 ] ) ! = 0 ) exit ( 1 ) ;
if ( close ( tab [ 0 ] ) ! = 0 ) exit ( 1 ) ;
2023-05-15 00:26:41 +00:00
string fformat = " ffmpeg -hide_banner -loglevel error -y -f rawvideo -pix_fmt bgra -s " + its ( shot : : shotx ) + " x " + its ( shot : : shoty ) + " -r 60 -i - -pix_fmt yuv420p -codec:v libx264 \" " + fname + " \" " ;
2020-05-22 12:50:10 +00:00
ignore ( system ( fformat . c_str ( ) ) ) ;
2020-05-22 12:30:45 +00:00
exit ( 0 ) ;
}
close ( tab [ 0 ] ) ;
shot : : rawfile_handle = tab [ 1 ] ;
dynamicval < shot : : screenshot_format > sf ( shot : : format , shot : : screenshot_format : : rawfile ) ;
rec ( ) ;
close ( tab [ 1 ] ) ;
wait ( nullptr ) ;
2020-05-25 00:32:15 +00:00
callhooks ( hooks_after_video ) ;
2020-05-22 12:30:45 +00:00
return true ;
}
EX bool record_video_std ( ) {
return record_video ( videofile , record_animation ) ;
}
# endif
2018-09-10 15:58:36 +00:00
void display_animation ( ) {
if ( ma = = maCircle & & ( circle_display_color & 0xFF ) ) {
for ( int s = 0 ; s < 10 ; s + + ) {
2020-07-27 16:49:04 +00:00
if ( s = = 0 ) curvepoint ( xpush0 ( circle_radius - .1 ) ) ;
2022-11-12 21:38:45 +00:00
for ( int z = 0 ; z < 100 ; z + + ) curvepoint ( xspinpush0 ( ( z + s * 100 ) * 2 * A_PI / 1000. , circle_radius ) ) ;
2020-07-27 16:49:04 +00:00
queuecurve ( ggmatrix ( rotation_center ) , circle_display_color , 0 , PPR : : LINE ) ;
2018-09-10 15:58:36 +00:00
}
if ( sphere ) for ( int s = 0 ; s < 10 ; s + + ) {
2020-07-27 16:49:04 +00:00
if ( s = = 0 ) curvepoint ( xpush0 ( circle_radius - .1 ) ) ;
2022-11-12 21:38:45 +00:00
for ( int z = 0 ; z < 100 ; z + + ) curvepoint ( xspinpush0 ( ( z + s * 100 ) * 2 * A_PI / 1000. , circle_radius ) ) ;
2020-07-27 16:49:04 +00:00
queuecurve ( ggmatrix ( rotation_center ) * centralsym , circle_display_color , 0 , PPR : : LINE ) ;
2018-09-10 15:58:36 +00:00
}
}
}
void animator ( string caption , ld & param , char key ) {
dialog : : addBoolItem ( caption , param , key ) ;
if ( param ) dialog : : lastItem ( ) . value = fts ( param ) ;
dialog : : add_action ( [ & param , caption ] ( ) {
if ( param = = 0 ) {
param = 1 ;
2018-09-10 17:31:10 +00:00
string s =
XLAT (
" The value of 1 means that the period of this animation equals the period set in the animation menu. "
" Larger values correspond to faster animations. " ) ;
2018-11-09 19:41:55 +00:00
2018-09-10 17:31:10 +00:00
dialog : : editNumber ( param , 0 , 10 , 1 , 1 , caption , s ) ;
2018-09-10 15:58:36 +00:00
}
else param = 0 ;
} ) ;
}
2019-08-09 20:07:03 +00:00
EX ld a , b ;
2018-11-09 20:18:58 +00:00
2018-09-10 15:58:36 +00:00
ld animation_period ;
2020-11-08 11:43:07 +00:00
EX void rug_angle_options ( ) {
2023-08-13 11:38:36 +00:00
add_edit ( rug_angle ) ;
add_edit ( rug_movement_angle ) ;
2020-11-08 11:43:07 +00:00
}
2019-08-09 19:00:52 +00:00
EX void show ( ) {
2018-09-10 15:58:36 +00:00
cmode = sm : : SIDE ; needs_highqual = false ;
animation_lcm = 1 ;
2022-07-05 09:51:06 +00:00
gamescreen ( ) ;
2022-11-12 21:38:45 +00:00
animation_period = TAU * animation_lcm / animation_factor ;
2018-09-10 15:58:36 +00:00
dialog : : init ( XLAT ( " animations " ) , iinf [ itPalace ] . color , 150 , 100 ) ;
2018-09-10 17:31:10 +00:00
dialog : : addSelItem ( XLAT ( " period " ) , fts ( period ) + " ms " , ' p ' ) ;
dialog : : add_action ( [ ] ( ) { dialog : : editNumber ( period , 0 , 10000 , 1000 , 200 , XLAT ( " period " ) ,
XLAT ( " This is the period of the whole animation, though in some settings the animation can have a different period or be aperiodic. "
" Changing the value will make the whole animation slower or faster. "
) ) ; } ) ;
2018-09-10 15:58:36 +00:00
if ( animation_lcm > 1 ) {
2018-09-10 17:31:10 +00:00
dialog : : addSelItem ( XLAT ( " game animation period " ) , fts ( animation_period ) + " ms " , ' G ' ) ;
2018-09-10 15:58:36 +00:00
dialog : : add_action ( [ ] ( ) {
2018-09-10 17:31:10 +00:00
dialog : : editNumber ( animation_period , 0 , 10000 , 1000 , 1000 , XLAT ( " game animation period " ) ,
XLAT ( " Least common multiple of the animation periods of all the game objects on screen, such as rotating items. " )
) ;
2023-08-09 12:01:24 +00:00
dialog : : get_di ( ) . reaction = [ ] ( ) { animation_factor = TAU * animation_lcm / animation_period ; } ;
dialog : : get_di ( ) . extra_options = [ ] ( ) {
2018-09-10 15:58:36 +00:00
dialog : : addItem ( " default " , ' D ' ) ;
dialog : : add_action ( [ ] ( ) {
animation_factor = 1 ;
popScreen ( ) ;
} ) ;
} ;
} ) ;
}
2019-05-03 10:11:40 +00:00
dialog : : addBoolItem_choice ( XLAT ( " no movement animation " ) , ma , maNone , ' 0 ' ) ;
dialog : : addBoolItem_choice ( XLAT ( " translation " ) , ma , maTranslation , ' 1 ' ) ;
dialog : : addBoolItem_choice ( XLAT ( " rotation " ) , ma , maRotation , ' 2 ' ) ;
2018-09-10 15:58:36 +00:00
if ( hyperbolic ) {
2019-05-03 10:11:40 +00:00
dialog : : addBoolItem_choice ( XLAT ( " parabolic " ) , ma , maParabolic , ' 3 ' ) ;
2018-09-10 15:58:36 +00:00
}
2022-12-08 18:38:06 +00:00
if ( ! mproduct ) {
2019-08-19 12:29:25 +00:00
dialog : : addBoolItem ( XLAT ( " circle " ) , ma = = maCircle , ' 4 ' ) ;
dialog : : add_action ( [ ] ( ) { ma = maCircle ;
2019-11-13 23:26:50 +00:00
rotation_center = centerover ;
2019-08-19 12:29:25 +00:00
rotation_center_View = View ;
} ) ;
}
2019-07-03 03:01:27 +00:00
dialog : : addBoolItem_choice ( XLAT ( " translation " ) + " + " + XLAT ( " rotation " ) , ma , maTranslationRotation , ' 5 ' ) ;
2018-09-10 15:58:36 +00:00
switch ( ma ) {
case maCircle : {
2019-06-01 18:20:36 +00:00
animator ( XLAT ( " circle spins " ) , circle_spins , ' C ' ) ;
2018-09-10 17:31:10 +00:00
dialog : : addSelItem ( XLAT ( " circle radius " ) , fts ( circle_radius ) , ' c ' ) ;
2018-09-10 15:58:36 +00:00
dialog : : add_action ( [ ] ( ) {
2018-09-10 17:31:10 +00:00
dialog : : editNumber ( circle_radius , 0 , 10 , 0.1 , acosh ( 1. ) , XLAT ( " circle radius " ) , " " ) ;
2023-08-09 12:01:24 +00:00
dialog : : get_di ( ) . extra_options = [ ] ( ) {
2018-09-10 15:58:36 +00:00
if ( hyperbolic ) {
// area = 2pi (cosh(r)-1)
2018-11-09 21:18:18 +00:00
dialog : : addSelItem ( XLAT ( " double spin " ) , fts ( acosh ( 2. ) ) , ' A ' ) ;
2018-09-10 15:58:36 +00:00
dialog : : add_action ( [ ] ( ) { circle_radius = acosh ( 2. ) ; } ) ;
2018-11-09 21:18:18 +00:00
dialog : : addSelItem ( XLAT ( " triple spin " ) , fts ( acosh ( 3. ) ) , ' B ' ) ;
2018-09-10 15:58:36 +00:00
dialog : : add_action ( [ ] ( ) { circle_radius = acosh ( 3. ) ; } ) ;
}
if ( sphere ) {
2018-11-09 21:18:18 +00:00
dialog : : addSelItem ( XLAT ( " double spin " ) , fts ( acos ( 1 / 2. ) ) , ' A ' ) ;
2018-09-10 15:58:36 +00:00
dialog : : add_action ( [ ] ( ) { circle_radius = acos ( 1 / 2. ) ; } ) ;
2018-11-09 21:18:18 +00:00
dialog : : addSelItem ( XLAT ( " triple spin " ) , fts ( acos ( 1 / 3. ) ) , ' B ' ) ;
2018-09-10 15:58:36 +00:00
dialog : : add_action ( [ ] ( ) { circle_radius = acos ( 1 / 3. ) ; } ) ;
}
} ;
} ) ;
2018-09-10 17:31:10 +00:00
dialog : : addColorItem ( XLAT ( " draw the circle " ) , circle_display_color , ' d ' ) ;
2018-09-10 15:58:36 +00:00
dialog : : add_action ( [ ] ( ) {
dialog : : openColorDialog ( circle_display_color , NULL ) ;
} ) ;
dialog : : addBreak ( 100 ) ;
2019-06-01 18:20:36 +00:00
break ;
2018-09-10 15:58:36 +00:00
}
case maTranslation :
2019-07-03 03:01:27 +00:00
case maTranslationRotation :
2018-09-10 15:58:36 +00:00
case maParabolic : {
2019-08-09 22:58:50 +00:00
if ( ma = = maTranslation & & history : : on )
2018-09-10 15:58:36 +00:00
dialog : : addBreak ( 300 ) ;
else if ( ma = = maTranslation ) {
2018-09-10 17:31:10 +00:00
dialog : : addSelItem ( XLAT ( " cycle length " ) , fts ( cycle_length ) , ' c ' ) ;
2018-09-10 15:58:36 +00:00
dialog : : add_action ( [ ] ( ) {
2022-11-12 21:38:45 +00:00
dialog : : editNumber ( cycle_length , 0 , 10 , 0.1 , TAU , " shift " , " " ) ;
2023-08-09 12:01:24 +00:00
dialog : : get_di ( ) . extra_options = [ ] ( ) {
2022-11-12 21:38:45 +00:00
dialog : : addSelItem ( XLAT ( " full circle " ) , fts ( TAU ) , ' A ' ) ;
dialog : : add_action ( [ ] ( ) { cycle_length = TAU ; } ) ;
2018-11-09 21:18:18 +00:00
dialog : : addSelItem ( XLAT ( " Zebra period " ) , fts ( 2.898149445355172 ) , ' B ' ) ;
2018-09-10 15:58:36 +00:00
dialog : : add_action ( [ ] ( ) { cycle_length = 2.898149445355172 ; } ) ;
2018-11-09 21:18:18 +00:00
dialog : : addSelItem ( XLAT ( " Bolza period " ) , fts ( 2 * 1.528571 ) , ' C ' ) ;
2018-09-10 16:06:27 +00:00
dialog : : add_action ( [ ] ( ) { cycle_length = 2 * 1.528571 ; } ) ;
2018-09-10 15:58:36 +00:00
} ;
} ) ;
}
2021-01-31 18:55:33 +00:00
else
add_edit ( parabolic_length ) ;
2023-08-13 11:38:36 +00:00
add_edit ( movement_angle . get ( ) ) ;
2018-09-10 15:58:36 +00:00
break ;
}
2019-03-21 02:23:06 +00:00
case maRotation :
2019-08-15 13:05:43 +00:00
if ( GDIM = = 3 ) {
2019-06-01 18:20:36 +00:00
dialog : : addSelItem ( XLAT ( " angle to screen normal " ) , fts ( normal_angle ) + " ° " , ' C ' ) ;
2019-03-21 02:23:06 +00:00
dialog : : add_action ( [ ] ( ) {
dialog : : editNumber ( normal_angle , 0 , 360 , 15 , 0 , XLAT ( " angle to screen normal " ) , " " ) ;
} ) ;
2023-08-13 11:38:36 +00:00
add_edit ( movement_angle ) ;
2019-03-21 02:23:06 +00:00
dialog : : addBreak ( 100 ) ;
2020-07-08 13:49:29 +00:00
dialog : : addSelItem ( XLAT ( " distance from rotation center " ) , fts ( rotation_distance ) , ' r ' ) ;
dialog : : add_action ( [ ] ( ) {
dialog : : editNumber ( rotation_distance , 0 , 10 , .1 , 0 , XLAT ( " distance from rotation center " ) , " " ) ;
} ) ;
dialog : : addBreak ( 100 ) ;
2019-03-21 02:23:06 +00:00
}
else
dialog : : addBreak ( 300 ) ;
break ;
2018-09-10 15:58:36 +00:00
default : {
dialog : : addBreak ( 300 ) ;
}
}
2018-09-10 17:31:10 +00:00
animator ( XLATN ( " Ocean " ) , env_ocean , ' o ' ) ;
animator ( XLATN ( " Volcanic Wasteland " ) , env_volcano , ' v ' ) ;
2019-05-06 23:23:47 +00:00
if ( shmup : : on ) dialog : : addBoolItem_action ( XLAT ( " shmup action " ) , env_shmup , ' T ' ) ;
2020-10-15 14:33:52 +00:00
# if CAP_FILES && CAP_SHOT
2020-04-26 09:03:49 +00:00
if ( cheater ) {
dialog : : addSelItem ( XLAT ( " monster turns " ) , its ( numturns ) , ' n ' ) ;
dialog : : add_action ( [ ] {
dialog : : editNumber ( numturns , 0 , 100 , 1 , 0 , XLAT ( " monster turns " ) , XLAT ( " Number of turns to pass. Useful when simulating butterflies or cellular automata. " ) ) ;
} ) ;
}
2020-10-15 14:33:52 +00:00
# endif
2018-09-10 15:58:36 +00:00
# if CAP_RUG
if ( rug : : rugged ) {
2018-09-10 17:31:10 +00:00
animator ( XLAT ( " screen-relative rotation " ) , rug_rotation1 , ' r ' ) ;
2023-08-13 11:38:36 +00:00
if ( rug_rotation1 ) add_edit ( rug_angle ) ;
2018-09-10 15:58:36 +00:00
else dialog : : addBreak ( 100 ) ;
2018-09-10 17:31:10 +00:00
animator ( XLAT ( " model-relative rotation " ) , rug_rotation2 , ' r ' ) ;
2020-11-08 11:43:07 +00:00
animator ( XLAT ( " automatic move speed " ) , rug_forward , ' M ' ) ;
2018-09-10 16:06:27 +00:00
dialog : : add_action ( [ ] ( ) {
2020-11-08 11:43:07 +00:00
dialog : : editNumber ( rug_forward , 0 , 10 , 1 , 1 , XLAT ( " automatic move speed " ) , XLAT ( " Move automatically without pressing any keys. " ) ) ;
2023-08-09 12:01:24 +00:00
dialog : : get_di ( ) . extra_options = [ ] ( ) {
2020-11-08 11:43:07 +00:00
if ( among ( rug : : gwhere , gSphere , gElliptic ) ) {
2018-09-10 16:06:27 +00:00
dialog : : addItem ( XLAT ( " synchronize " ) , ' S ' ) ;
2022-11-12 21:38:45 +00:00
dialog : : add_action ( [ ] ( ) { rug_forward = TAU ; popScreen ( ) ; } ) ;
2020-11-08 11:43:07 +00:00
}
rug_angle_options ( ) ;
} ;
2018-09-10 16:06:27 +00:00
} ) ;
2018-09-10 15:58:36 +00:00
}
# endif
2018-11-09 20:18:58 +00:00
dialog : : addSelItem ( XLAT ( " animate parameters " ) , fts ( a ) , ' a ' ) ;
dialog : : add_action ( [ ] ( ) {
dialog : : editNumber ( a , - 100 , 100 , 1 , 0 , XLAT ( " animate parameters " ) , " " ) ;
} ) ;
dialog : : addSelItem ( XLAT ( " animate parameters " ) , fts ( b ) , ' b ' ) ;
dialog : : add_action ( [ ] ( ) {
dialog : : editNumber ( b , - 100 , 100 , 1 , 0 , XLAT ( " animate parameters " ) , " " ) ;
} ) ;
2018-09-10 15:58:36 +00:00
2019-08-09 22:58:50 +00:00
dialog : : addBoolItem ( XLAT ( " history mode " ) , ( history : : on | | history : : includeHistory ) , ' h ' ) ;
dialog : : add_action_push ( history : : history_menu ) ;
2018-09-10 15:58:36 +00:00
2018-12-15 14:20:27 +00:00
# if CAP_FILES && CAP_SHOT
2018-12-13 12:33:08 +00:00
dialog : : addItem ( XLAT ( " shot settings " ) , ' s ' ) ;
2019-03-30 22:59:51 +00:00
dialog : : add_action_push ( shot : : menu ) ;
2018-12-13 12:33:08 +00:00
2018-09-10 15:58:36 +00:00
if ( needs_highqual )
2018-09-10 17:31:10 +00:00
dialog : : addInfo ( XLAT ( " some parameters will only change in recorded animation " ) ) ;
2018-09-10 15:58:36 +00:00
else
dialog : : addBreak ( 100 ) ;
2018-09-10 17:31:10 +00:00
dialog : : addSelItem ( XLAT ( " frames to record " ) , its ( noframes ) , ' n ' ) ;
dialog : : add_action ( [ ] ( ) { dialog : : editNumber ( noframes , 0 , 300 , 30 , 5 , XLAT ( " frames to record " ) , " " ) ; } ) ;
2020-05-22 12:30:45 +00:00
dialog : : addSelItem ( XLAT ( " record to sequence of image files " ) , animfile , ' R ' ) ;
dialog : : add_action ( [ ] ( ) {
dialog : : openFileDialog ( animfile , XLAT ( " record to sequence of image files " ) ,
2020-04-16 12:36:45 +00:00
shot : : format_extension ( ) , record_animation ) ;
2018-09-10 15:58:36 +00:00
} ) ;
# endif
2020-05-22 12:30:45 +00:00
# if CAP_VIDEO
dialog : : addSelItem ( XLAT ( " record to video file " ) , videofile , ' M ' ) ;
dialog : : add_action ( [ ] ( ) {
dialog : : openFileDialog ( videofile , XLAT ( " record to video file " ) ,
" .mp4 " , record_video_std ) ;
} ) ;
# endif
2018-09-10 15:58:36 +00:00
dialog : : addBack ( ) ;
dialog : : display ( ) ;
}
2018-12-15 14:17:06 +00:00
# if CAP_COMMANDLINE
2018-09-10 15:58:36 +00:00
int readArgs ( ) {
using namespace arg ;
if ( 0 ) ;
2019-02-28 14:06:42 +00:00
# if CAP_ANIMATIONS
2018-09-10 15:58:36 +00:00
else if ( argis ( " -animmenu " ) ) {
2018-09-10 17:28:46 +00:00
PHASE ( 3 ) ; showstartmenu = false ; pushScreen ( show ) ;
2018-09-10 15:58:36 +00:00
}
2018-09-10 17:28:46 +00:00
else if ( argis ( " -animperiod " ) ) {
2019-03-30 16:54:43 +00:00
PHASEFROM ( 2 ) ; shift_arg_formula ( period ) ;
2018-09-10 17:28:46 +00:00
}
2022-04-21 10:33:37 +00:00
else if ( argis ( " -animformula " ) ) {
PHASEFROM ( 2 ) ; shift ( ) ; time_formula = args ( ) ;
2022-04-16 12:00:10 +00:00
}
2019-02-28 14:06:42 +00:00
# if CAP_SHOT
2019-05-05 15:38:28 +00:00
else if ( argis ( " -animrecordf " ) ) {
2022-10-11 18:02:20 +00:00
PHASEFROM ( 2 ) ; shift ( ) ; noframes = argi ( ) ? argi ( ) : noframes ;
2019-05-05 15:38:28 +00:00
shift ( ) ; animfile = args ( ) ;
}
2019-02-28 14:06:42 +00:00
else if ( argis ( " -animrecord " ) | | argis ( " -animrec " ) ) {
2022-10-11 18:02:20 +00:00
PHASE ( 3 ) ; shift ( ) ; noframes = argi ( ) ? argi ( ) : noframes ;
2018-09-10 17:28:46 +00:00
shift ( ) ; animfile = args ( ) ; record_animation ( ) ;
}
2019-06-11 08:20:21 +00:00
else if ( argis ( " -record-only " ) ) {
PHASEFROM ( 2 ) ;
shift ( ) ; min_frame = argi ( ) ;
shift ( ) ; max_frame = argi ( ) ;
}
2020-05-22 12:52:12 +00:00
# endif
# if CAP_VIDEO
else if ( argis ( " -animvideo " ) ) {
2021-02-07 13:55:46 +00:00
start_game ( ) ;
2022-10-11 18:02:20 +00:00
PHASE ( 3 ) ; shift ( ) ; noframes = argi ( ) ? argi ( ) : noframes ;
2020-05-22 12:52:12 +00:00
shift ( ) ; videofile = args ( ) ; record_video ( ) ;
}
2019-02-28 14:06:42 +00:00
# endif
2023-08-13 11:38:36 +00:00
# endif
2018-09-10 15:58:36 +00:00
else return 1 ;
return 0 ;
}
2018-12-15 14:17:06 +00:00
# endif
2018-09-10 15:58:36 +00:00
2018-12-15 14:17:06 +00:00
auto animhook = addHook ( hooks_frame , 100 , display_animation )
# if CAP_COMMANDLINE
+ addHook ( hooks_args , 100 , readArgs )
# endif
2021-05-29 10:15:50 +00:00
+ addHook ( hooks_configfile , 100 , [ ] {
2021-02-07 19:42:09 +00:00
# if CAP_CONFIG
2021-02-01 00:45:10 +00:00
param_f ( anims : : period , " aperiod " , " animation period " ) ;
2021-01-31 17:53:52 +00:00
addsaver ( anims : : noframes , " animation frames " ) ;
2021-02-01 00:45:10 +00:00
param_f ( anims : : cycle_length , " acycle " , " animation cycle length " ) ;
param_f ( anims : : parabolic_length , " aparabolic " , " animation parabolic length " )
2021-01-31 19:44:34 +00:00
- > editable ( 0 , 10 , 1 , " cells to go " , " " , ' c ' ) ;
2023-08-13 11:38:36 +00:00
param_matrix ( anims : : rug_angle , " arugangle " , 3 )
- > editable ( " animation rug angle " , " " , ' a ' ) ;
2021-02-01 00:45:10 +00:00
param_f ( anims : : circle_radius , " acradius " , " animation circle radius " ) ;
param_f ( anims : : circle_spins , " acspins " , " animation circle spins " ) ;
2023-08-13 11:38:36 +00:00
param_matrix ( anims : : rug_movement_angle , " rug forward movement angle " , 3 )
- > editable ( " rug forward movement angle " , " " , ' b ' ) ;
param_matrix ( anims : : movement_angle . v2 , " movement_angle " , 2 ) - > editable ( " movement angle " , " " , ' m ' ) ;
param_matrix ( anims : : movement_angle . v3 , " movement_angle_3 " , 3 ) - > editable ( " movement angle " , " " , ' m ' ) ;
param_f ( rug_rotation1 , " rug_rotation1 " ) ;
param_f ( rug_rotation2 , " rug_rotation2 " ) ;
param_f ( rotation_distance , " rotation_distance " ) ;
param_f ( cycle_length , " cycle_length " ) ;
param_f ( env_ocean , " env_ocean " ) ;
param_f ( env_volcano , " env_volcano " ) ;
param_b ( wallopt , " wallopt " ) ;
param_b ( clearup , " anim_clearup " ) ;
param_color ( circle_display_color , " circle_display_color " , true ) ;
param_enum ( anims : : ma , " ma " , " movement_animation " , maNone )
- > editable ( { { " none " , " " } , { " translation " , " " } , { " rotation " , " " } , { " circle " , " " } , { " parabolic " , " " } , { " translation+rotation " , " " } } , " movement animation " , ' a ' )
- > set_reaction ( ma_reaction ) ;
2023-01-04 22:31:55 +00:00
param_f ( anims : : a , " a " , 0 ) ;
param_f ( anims : : b , " b " , 0 ) ;
2021-02-07 19:42:09 +00:00
# endif
2021-01-31 17:53:52 +00:00
} ) ;
2018-09-10 15:58:36 +00:00
2019-08-09 19:00:52 +00:00
EX bool any_animation ( ) {
2019-08-09 22:58:50 +00:00
if ( history : : on ) return true ;
2018-09-10 15:58:36 +00:00
if ( ma ) return true ;
2023-08-13 11:37:20 +00:00
if ( rug_rotation1 | | rug_rotation2 ) return true ;
2018-11-09 19:41:55 +00:00
if ( ap_changes ) return true ;
2018-09-10 15:58:36 +00:00
return false ;
}
2019-08-09 19:00:52 +00:00
EX bool any_on ( ) {
2019-08-09 22:58:50 +00:00
return any_animation ( ) | | history : : includeHistory ;
2018-09-10 15:58:36 +00:00
}
2019-08-09 19:00:52 +00:00
EX bool center_music ( ) {
2018-09-10 15:58:36 +00:00
return among ( ma , maParabolic , maTranslation ) ;
}
2019-08-09 19:00:52 +00:00
EX }
2018-07-19 21:46:58 +00:00
# endif
2018-12-17 10:33:52 +00:00
2019-08-09 19:00:52 +00:00
EX namespace startanims {
2018-12-17 10:33:52 +00:00
2022-07-05 09:51:49 +00:00
EX bool enabled = true ;
2018-12-17 10:33:52 +00:00
int ticks_start = 0 ;
2019-12-14 13:10:34 +00:00
# if HDR
struct startanim {
string name ;
reaction_t init ;
reaction_t render ;
} ;
const int EXPLORE_START_ANIMATION = 2003 ;
# endif
reaction_t exploration ;
void explorable ( reaction_t ee ) {
if ( displayButtonS ( 4 , vid . yres - 4 - vid . fsize / 2 , XLAT ( " explore this animation " ) , 0x202020 , 0 , vid . fsize / 2 ) )
getcstat = EXPLORE_START_ANIMATION ;
exploration = ee ;
2018-12-17 10:33:52 +00:00
}
2019-12-14 13:10:34 +00:00
void no_init ( ) { }
2022-07-05 09:51:06 +00:00
startanim null_animation { " " , no_init , [ ] { gamescreen ( ) ; } } ;
2019-12-14 13:10:34 +00:00
2020-10-15 14:33:52 +00:00
# if CAP_STARTANIM
2019-12-14 13:10:34 +00:00
startanim joukowsky { " Joukowsky transform " , no_init , [ ] {
2018-12-17 10:33:52 +00:00
dynamicval < eModel > dm ( pmodel , mdJoukowskyInverted ) ;
2023-08-08 14:27:52 +00:00
dynamicval < trans23 > dt ( pconf . mori ( ) , spin ( ticks / 25. ) ) ;
2018-12-17 10:33:52 +00:00
dynamicval < int > dv ( vid . use_smart_range , 2 ) ;
2020-04-16 22:53:58 +00:00
dynamicval < ld > ds ( pconf . scale , 1 / 4. ) ;
2019-08-09 22:58:50 +00:00
models : : configure ( ) ;
2018-12-17 10:33:52 +00:00
dynamicval < color_t > dc ( ringcolor , 0 ) ;
2022-07-05 09:51:06 +00:00
gamescreen ( ) ;
2019-12-14 13:10:34 +00:00
explorable ( [ ] { pmodel = mdJoukowskyInverted ; pushScreen ( models : : model_menu ) ; } ) ;
} } ;
2018-12-17 10:33:52 +00:00
2019-12-14 13:10:34 +00:00
startanim bandspin { " spinning in the band model " , no_init , [ ] {
2018-12-17 10:33:52 +00:00
dynamicval < eModel > dm ( pmodel , mdBand ) ;
2023-08-08 14:27:52 +00:00
dynamicval < trans23 > dt ( pconf . mori ( ) , spin ( ticks / 25. ) ) ;
2018-12-17 10:33:52 +00:00
dynamicval < int > dv ( vid . use_smart_range , 2 ) ;
2019-08-09 22:58:50 +00:00
models : : configure ( ) ;
2022-07-05 09:51:06 +00:00
gamescreen ( ) ;
2019-12-14 13:10:34 +00:00
explorable ( [ ] { pmodel = mdJoukowskyInverted ; pushScreen ( models : : model_menu ) ; } ) ;
} } ;
2018-12-17 10:33:52 +00:00
2019-12-14 13:10:34 +00:00
startanim perspective { " projection distance " , no_init , [ ] {
2018-12-17 10:33:52 +00:00
ld x = sin ( ticks / 1500. ) ;
x + = 1 ;
x / = 2 ;
x * = 1.5 ;
x = tan ( x ) ;
2020-04-16 22:53:58 +00:00
dynamicval < ld > da ( pconf . alpha , x ) ;
dynamicval < ld > ds ( pconf . scale , ( 1 + x ) / 2 ) ;
2018-12-17 10:33:52 +00:00
calcparam ( ) ;
2022-07-05 09:51:06 +00:00
gamescreen ( ) ;
2019-12-14 13:10:34 +00:00
explorable ( projectionDialog ) ;
} } ;
2018-12-17 10:33:52 +00:00
2019-12-14 13:10:34 +00:00
startanim rug { " Hypersian Rug " , [ ] {
2020-05-04 00:57:34 +00:00
# if CAP_RUG
rug : : init ( ) ;
rug : : rugged = false ;
# else
pick ( ) ;
# endif
} , [ ] {
2020-07-03 12:42:33 +00:00
# if CAP_RUG
2018-12-17 10:33:52 +00:00
dynamicval < bool > b ( rug : : rugged , true ) ;
rug : : physics ( ) ;
2020-04-14 15:46:40 +00:00
dynamicval < transmatrix > t ( rug : : rugView , cspin ( 1 , 2 , ticks / 3000. ) * rug : : rugView ) ;
2022-07-05 09:51:06 +00:00
gamescreen ( ) ;
2019-12-14 13:10:34 +00:00
if ( ! rug : : rugged ) current = & null_animation ;
explorable ( [ ] { rug : : rugged = true ; pushScreen ( rug : : show ) ; } ) ;
2020-07-03 12:42:33 +00:00
# endif
2019-12-14 13:10:34 +00:00
} } ;
2018-12-17 10:33:52 +00:00
2019-12-14 13:10:34 +00:00
startanim spin_around { " spinning around " , no_init , [ ] {
2020-04-16 22:53:58 +00:00
dynamicval < ld > da ( pconf . alpha , 999 ) ;
dynamicval < ld > ds ( pconf . scale , 500 ) ;
2022-11-12 21:38:45 +00:00
ld alpha = TAU * ticks / 10000. ;
2018-12-17 10:33:52 +00:00
ld circle_radius = acosh ( 2. ) ;
dynamicval < transmatrix > dv ( View , spin ( - cos_auto ( circle_radius ) * alpha ) * xpush ( circle_radius ) * spin ( alpha ) * View ) ;
2022-07-05 09:51:06 +00:00
gamescreen ( ) ;
2019-12-14 13:10:34 +00:00
} } ;
2020-10-15 14:33:52 +00:00
# endif
2018-12-17 10:33:52 +00:00
reaction_t add_to_frame ;
2019-02-17 17:43:39 +00:00
# if CAP_STARTANIM
2018-12-21 13:45:22 +00:00
void draw_ghost ( const transmatrix V , int id ) {
2020-07-27 16:49:04 +00:00
auto sV = shiftless ( V ) ;
2018-12-21 13:45:22 +00:00
if ( id % 13 = = 0 ) {
2020-07-27 16:49:04 +00:00
queuepoly ( sV , cgi . shMiniGhost , 0xFFFF00C0 ) ;
queuepoly ( sV , cgi . shMiniEyes , 0xFF ) ;
2018-12-21 13:45:22 +00:00
}
else {
2020-07-27 16:49:04 +00:00
queuepoly ( sV , cgi . shMiniGhost , 0xFFFFFFC0 ) ;
queuepoly ( sV , cgi . shMiniEyes , 0xFF ) ;
2018-12-21 13:45:22 +00:00
}
2018-12-17 10:33:52 +00:00
}
2019-12-14 13:10:34 +00:00
startanim row_of_ghosts { " row of ghosts " , no_init , [ ] {
2018-12-17 10:33:52 +00:00
dynamicval < reaction_t > r ( add_to_frame , [ ] {
2018-12-21 13:45:22 +00:00
int t = ticks / 400 ;
ld mod = ( ticks - t * 400 ) / 400. ;
for ( int x = - 25 ; x < = 25 ; x + + )
for ( int y = - 25 ; y < = 25 ; y + + ) {
ld ay = ( y + mod ) / 5. ;
2022-11-12 21:38:45 +00:00
draw_ghost ( xpush ( x / 5. ) * spin90 ( ) * xpush ( ay ) , int ( y - t ) ) ;
2018-12-17 10:33:52 +00:00
}
} ) ;
dynamicval < bool > rd ( mapeditor : : drawplayer , false ) ;
2022-07-05 09:51:06 +00:00
gamescreen ( ) ;
2019-12-14 13:10:34 +00:00
} } ;
2018-12-17 10:33:52 +00:00
2019-12-14 13:10:34 +00:00
startanim army_of_ghosts { " army of ghosts " , no_init , [ ] {
2018-12-17 10:33:52 +00:00
dynamicval < bool > rd ( mapeditor : : drawplayer , false ) ;
dynamicval < reaction_t > r ( add_to_frame , [ ] {
2018-12-21 13:45:22 +00:00
int tt = ticks - ticks_start + 1200 ;
int t = tt / 400 ;
ld mod = ( tt - t * 400 ) / 400. ;
for ( int x = - 12 ; x < = 12 ; x + + ) {
ld ax = x / 4. ;
2022-11-12 21:38:45 +00:00
transmatrix T = spin270 ( ) * xpush ( ax ) * spin90 ( ) ;
2018-12-21 13:45:22 +00:00
for ( int y = 0 ; ; y + + ) {
ld ay = ( mod - y ) / 4. ;
2022-11-12 21:38:45 +00:00
transmatrix U = spin90 ( ) * xpush ( ay / cosh ( ax ) ) * T ;
2020-07-27 16:49:04 +00:00
if ( ! in_smart_range ( shiftless ( U ) ) ) break ;
2018-12-21 13:45:22 +00:00
draw_ghost ( U , ( - y - t ) ) ;
if ( y ) {
ay = ( mod + y ) / 4. ;
2022-11-12 21:38:45 +00:00
transmatrix U = spin90 ( ) * xpush ( ay / cosh ( ax ) ) * T ;
2018-12-21 13:45:22 +00:00
draw_ghost ( U , ( y - t ) ) ;
}
2018-12-17 10:33:52 +00:00
}
}
} ) ;
2022-07-05 09:51:06 +00:00
gamescreen ( ) ;
2019-12-14 13:10:34 +00:00
} } ;
2018-12-17 10:33:52 +00:00
2019-12-14 13:10:34 +00:00
startanim ghost_spiral { " ghost spiral " , no_init , [ ] {
2018-12-17 10:33:52 +00:00
dynamicval < reaction_t > r ( add_to_frame , [ ] {
ld t = ( ticks - ticks_start - 2000 ) / 150000. ;
for ( ld i = 3 ; i < = 40 ; i + + ) {
2022-11-12 21:38:45 +00:00
draw_ghost ( spin ( t * i * TAU ) * xpush ( asinh ( 15. / i ) ) * spin90 ( ) , 1 ) ;
2018-12-21 13:45:22 +00:00
}
} ) ;
2022-07-05 09:51:06 +00:00
gamescreen ( ) ;
2019-12-14 13:10:34 +00:00
} } ;
2018-12-21 13:45:22 +00:00
2019-12-14 13:10:34 +00:00
startanim fib_ghosts { " Fibonacci ghosts " , no_init , [ ] {
2018-12-21 13:45:22 +00:00
dynamicval < bool > rd ( mapeditor : : drawplayer , false ) ;
dynamicval < reaction_t > r ( add_to_frame , [ ] {
ld phase = ( ticks - ticks_start - 2000 ) / 1000. ;
for ( int i = 0 ; i < = 500 ; i + + ) {
2022-11-12 21:38:45 +00:00
ld step = A_PI * ( 3 - sqrt ( 5 ) ) ;
2018-12-21 13:45:22 +00:00
ld density = 0.01 ;
ld area = 1 + ( i + .5 ) * density ;
ld r = acosh ( area ) ;
ld length = sinh ( r ) ;
2022-11-12 21:38:45 +00:00
transmatrix T = spin ( i * step + phase / length ) * xpush ( r ) * spin90 ( ) ;
2018-12-21 13:45:22 +00:00
draw_ghost ( T , i ) ;
2018-12-17 10:33:52 +00:00
}
} ) ;
2022-07-05 09:51:06 +00:00
gamescreen ( ) ;
2019-12-14 13:10:34 +00:00
} } ;
2018-12-17 10:33:52 +00:00
2019-12-14 13:17:10 +00:00
startanim fpp { " first-person perspective " , no_init , [ ] {
2019-12-27 13:03:17 +00:00
if ( MAXMDIM = = 3 ) { current = & null_animation ; return ; }
2019-12-14 13:17:10 +00:00
geom3 : : switch_fpp ( ) ;
View = cspin ( 0 , 2 , ticks / 5000. ) * View ;
2022-07-05 09:51:06 +00:00
gamescreen ( ) ;
2019-12-14 13:17:10 +00:00
View = cspin ( 0 , 2 , - ticks / 5000. ) * View ;
geom3 : : switch_fpp ( ) ;
} } ;
2018-12-17 10:33:52 +00:00
// more start animations:
// - fly a ghost around center, in Gans model
// - triangle edges?
2019-12-14 13:10:34 +00:00
EX startanim * current = & null_animation ;
2018-12-17 10:33:52 +00:00
2019-08-09 19:00:52 +00:00
EX void pick ( ) {
2022-07-05 09:51:06 +00:00
if ( ( ( gold ( ) > 0 | | tkills ( ) > 0 ) & & canmove ) | | geometry ! = gNormal | | ISWEB | | ISMOBILE | | vid . always3 | | pmodel | | rug : : rugged | | vid . wallmode < 2 | | vid . monmode < 2 | | glhr : : noshaders | | ! vid . usingGL | | ! enabled ) {
2019-12-14 13:10:34 +00:00
current = & null_animation ;
2018-12-17 10:33:52 +00:00
return ;
}
2019-12-14 13:17:10 +00:00
vector < startanim * > known = { & null_animation , & perspective , & joukowsky , & bandspin , & rug , & spin_around , & row_of_ghosts , & ghost_spiral , & army_of_ghosts , & fib_ghosts , & fpp } ;
2019-12-27 13:03:17 +00:00
int id = rand ( ) % 11 ;
2018-12-17 10:33:52 +00:00
current = known [ id ] ;
ticks_start = ticks ;
2019-12-14 13:10:34 +00:00
current - > init ( ) ;
2018-12-17 10:33:52 +00:00
}
auto sanimhook = addHook ( hooks_frame , 100 , [ ] ( ) { if ( add_to_frame ) add_to_frame ( ) ; } ) ;
2019-12-14 13:10:34 +00:00
EX void display ( ) {
current - > render ( ) ;
int z = vid . fsize / 2 + 2 ;
if ( displayButtonS ( 4 , vid . yres - 4 - z * 3 , VER , 0x202020 , 0 , vid . fsize / 2 ) )
getcstat = SDLK_F5 ;
if ( displayButtonS ( 4 , vid . yres - 4 - z * 2 , XLAT ( current - > name ) , 0x202020 , 0 , vid . fsize / 2 ) )
getcstat = SDLK_F5 ;
}
EX void explore ( ) { exploration ( ) ; }
2019-02-17 17:43:39 +00:00
# endif
2018-12-17 10:33:52 +00:00
2019-08-09 19:00:52 +00:00
EX }
2018-07-19 21:46:58 +00:00
}