boxing.cpp 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323
  1. // raboot of atari 2600 Boxing
  2. // TODO: gore (destructable nose, blood bubble)
  3. // TODO: death animation (drift, arms flail)
  4. // TODO: ropes (and some padding)
  5. // TODO: gamepad controls
  6. // TODO: touch controls
  7. // TODO: finish music
  8. // TODO: menu - 1/2 players, audio levels
  9. // TODO: menu - gore level (in case become olympic sport),
  10. //
  11. // TODO: menu - hoboplayer, with broadcast discovery
  12. //
  13. // TODO: zombie mode - if keep punching after win, go again till 200 damage
  14. //
  15. // from game fade away to main menu if no input, form main menu fade away to program.end() if no input
  16. /*
  17. #canvas {
  18. position: absolute;
  19. top: 0px;
  20. left: 0px;
  21. margin: 0px;
  22. width: 100%;
  23. height: 100%;
  24. overflow: hidden;
  25. display: block;
  26. }
  27. */
  28. #include "common/sketchbook.hpp"
  29. #include "common/math.hpp"
  30. float quadratic_out(float x) { return 1.f - motion::quadratic_curve(1.f - x); };
  31. float stairwave(float x)
  32. {
  33. // TODO: must be a way to just parameterize sinus to do less work and be less smooth, instead of doing more work to make it jagged
  34. constexpr auto levels = 12.f;
  35. return int(std::clamp(common::sinus(x) *levels, -levels,levels)) /levels;
  36. }
  37. struct key_vector
  38. {
  39. scancode k;
  40. float2 v;
  41. };
  42. struct keymap
  43. {
  44. std::array<key_vector,4> legwork;
  45. std::array<scancode,3> punch;
  46. };
  47. keymap wasd
  48. {
  49. {
  50. key_vector{scancode::w, -float2::j()},
  51. key_vector{scancode::a, -float2::i()},
  52. key_vector{scancode::s, float2::j()},
  53. key_vector{scancode::d, float2::i()}
  54. },
  55. { scancode::tab, scancode::f, scancode::v }
  56. };
  57. keymap ijkl
  58. {
  59. {
  60. key_vector{scancode::i, -float2::j()},
  61. key_vector{scancode::j, -float2::i()},
  62. key_vector{scancode::k, float2::j()},
  63. key_vector{scancode::l, float2::i()}
  64. },
  65. { scancode::h, scancode::semicolon, scancode::n}
  66. };
  67. keymap arrow_keys
  68. {
  69. {
  70. key_vector{scancode::up, -float2::j()},
  71. key_vector{scancode::left, -float2::i()},
  72. key_vector{scancode::down, float2::j()},
  73. key_vector{scancode::right, float2::i()}
  74. },
  75. { scancode::rctrl, scancode::rshift, scancode::enter }
  76. };
  77. std::vector control_options {wasd, ijkl, arrow_keys}; // FIXME: numpad, gamepad, touchscreen, mouse
  78. rect ring{};
  79. constexpr float2 half = float2::one(0.5);
  80. constexpr float2 starting_pos = float2::one(0.1);
  81. constexpr auto scale(rect ratio, rect world)
  82. {
  83. ratio.size *= world.size;
  84. ratio.position *= world.size;
  85. ratio.position += world.position;
  86. return ratio;
  87. }
  88. constexpr auto faded_wave(float fade_factor, auto&& f)
  89. {
  90. return [fade_factor, f = std::forward<decltype(f)>(f)](auto ratio) mutable
  91. {
  92. auto [freq, ampl] = f(ratio);
  93. float fade = std::min(ratio*fade_factor, (1-ratio)*fade_factor);
  94. return way(0.f,common::sinus(ratio * freq) * ampl, std::min(fade, 1.f));
  95. };
  96. };
  97. struct blood_drop
  98. {
  99. rgb cola;
  100. movement<float2, motion::cubic_curve> veloc;
  101. float2 position;
  102. float size = 0.01f;
  103. void update(frame& frame, auto delta)
  104. {
  105. veloc.advance(delta);
  106. auto oldpos = position;
  107. position += veloc.value();
  108. frame.begin_sketch()
  109. .line(ring.size*oldpos + ring.position, ring.size*position + ring.position)
  110. .line_width(size * ring.size.x())
  111. .line_cap(sketch::cap::round)
  112. .outline(cola)
  113. ;
  114. }
  115. bool done()
  116. {
  117. return veloc.done();
  118. }
  119. };
  120. std::vector<blood_drop> blood;
  121. struct complayer
  122. {
  123. using timer = movement<support::tuple_void_t>;
  124. // parameters
  125. std::array<Program::duration, 3> punch_intervals = {600ms, 300ms, 100ms};
  126. Program::duration legwork_interval = 250ms;
  127. Program::duration stun_interval = 250ms;
  128. // state
  129. timer legwork_timer = {legwork_interval};
  130. timer punch_timer = {punch_intervals[0]};
  131. timer stun_timer = {stun_interval};
  132. float2 legwork_flank = {};
  133. int sidestep = 0;
  134. };
  135. struct vein
  136. {
  137. std::vector<float2> path = {};
  138. float2 velocity = {};
  139. float width = 0.f;
  140. };
  141. std::vector<vein> bloodshot()
  142. {
  143. constexpr float radius = 0.95f;
  144. std::vector<vein> veins;
  145. const auto bursts = trand_int({1, 4});
  146. std::generate_n(std::back_inserter(veins), bursts, [radius]()
  147. {
  148. const auto start_point = common::rotate(float2::j(radius), common::protractor<>::tau(trand_float()));
  149. return vein
  150. {
  151. .path = {start_point},
  152. .velocity = -start_point/10,
  153. .width = 1/30.f
  154. };
  155. });
  156. auto current_veins = range{support::offset_expander(veins), support::offset_expander(veins, veins.end())};
  157. for(int i = 3; i --> 0;)
  158. {
  159. for(vein& vein : current_veins)
  160. {
  161. for(int i = 4; i --> 0;)
  162. {
  163. const auto velocity = common::rotate(vein.velocity,
  164. common::protractor<>::tau(
  165. support::wrap(trand_float({-1/8.f, 1/8.f}), 1.f)
  166. )
  167. );
  168. vein.path.push_back(vein.path.back() + velocity);
  169. if(vein.path.back().quadrance() > radius*radius)
  170. {
  171. auto tangent = common::rotate(-vein.path.back(),
  172. common::protractor<>::tau(1/4.f));
  173. vein.path.back() -= velocity;
  174. vein.velocity = common::reflect(velocity, tangent);
  175. vein.path.back() += vein.velocity;
  176. }
  177. }
  178. }
  179. auto new_veins_end = current_veins.end();
  180. for(vein& v : current_veins)
  181. {
  182. auto split = 2; // = trand_int({1,3});
  183. const auto step = 1/2.f / split;
  184. auto angle_range = range{-1/4.f, -1/4.f + step};
  185. new_veins_end = std::generate_n(new_veins_end, split,
  186. [
  187. root = v.path.back(),
  188. velocity = v.velocity,
  189. width = v.width * 0.6f,
  190. &angle_range, &step
  191. ]()
  192. {
  193. auto v = vein
  194. {
  195. .path = {root},
  196. .velocity = common::rotate(velocity,
  197. common::protractor<>::tau(
  198. support::wrap(trand_float(angle_range), 1.f)
  199. )
  200. ),
  201. .width = width
  202. };
  203. angle_range += step;
  204. return v;
  205. });
  206. }
  207. current_veins.lower() = current_veins.end();
  208. current_veins.upper() = new_veins_end;
  209. }
  210. return veins;
  211. }
  212. struct eye_t
  213. {
  214. constexpr static auto size = float2::one(0.035f);
  215. float2 position = {};
  216. float2 velocity = {};
  217. float2 drift = {};
  218. unsigned pop_damage = 100;
  219. melody<
  220. movement<float, quadratic_out>,
  221. movement<float, motion::quadratic_curve>
  222. >
  223. drifting = { {1s, 0, 1}, {1s, 1, 0}};
  224. melody
  225. <
  226. movement<float>,
  227. movement<float, quadratic_out>,
  228. movement<float>,
  229. movement<float, motion::quadratic_curve>
  230. >
  231. blink
  232. {
  233. {1s, 0.f, 0.f}, // delay
  234. {50ms, 0.f, 1.f}, // close
  235. {100ms, 1.f, 1.f}, // stay
  236. {50ms, 1.f, 0.f}, // open
  237. };
  238. float drift_ratio = 0.f;
  239. melody
  240. <
  241. movement<float>,
  242. movement<float, [](float x) -> float { return quadratic_out(x) * (1.f + common::sinus(x * 4.f))/2.f; } >
  243. >
  244. twitch
  245. {
  246. {1s, 0.f, 0.f}, // delay
  247. {500ms, 0.f, 1.f}, // close
  248. };
  249. float lid_value = 0.f;
  250. std::vector<vein> veins = bloodshot();
  251. movement<float, [](float x){ return common::sinus(x/2); }> fall = {500ms, 1.f, 2.f};
  252. movement<support::tuple_void_t> bleed = {10ms};
  253. support::array<rect, 20> black = support::make_array<20>([](auto)
  254. {
  255. return rect
  256. {
  257. size/4 * trand_float({1.f, 2.f}),
  258. common::protractor<>::tau(
  259. support::wrap(trand_float({-1/4.f, 1/4.f}), 1.f)
  260. ),
  261. float2::one()/2
  262. };
  263. });
  264. };
  265. struct boxa
  266. {
  267. rgb cola;
  268. float2 position = {};
  269. float2 velocity = {};
  270. float speed = 0.5;
  271. movement<float, common::sinus> idle = {500ms, 0.f, 0.005f};
  272. movement<float2, quadratic_out> shovement = {0s, float2::zero(), float2::zero()};
  273. melody<movement<float2>, movement<float2>> squeezement = {};
  274. melody<movement<float2>, movement<float2>> glove_squeezement = {};
  275. boxa* target;
  276. std::optional<keymap> controls{};
  277. std::optional<complayer> brainz;
  278. std::array<eye_t,2> eyes = {{ {position + float2::j() * eye_t::size * 1.2f}, {position - float2::j() * eye_t::size * 1.2f} }};
  279. float punch = 0.0f;
  280. bool hit = false;
  281. unsigned punch_glove = 0;
  282. unsigned hit_glove = 0;
  283. unsigned damage = 0;
  284. Program* program;
  285. auto direction() const
  286. {
  287. const auto distance = target->position - position;
  288. auto clamped_distance = distance;
  289. if(clamped_distance.y() > 0.2)
  290. clamped_distance.y() = 0.2;
  291. else if(clamped_distance.y() < -0.2)
  292. clamped_distance.y() = -0.2;
  293. if(abs(clamped_distance.x()) > 0.18f)
  294. clamped_distance *= float2::i();
  295. else
  296. clamped_distance = support::average(clamped_distance, clamped_distance * float2::i());
  297. return common::normalize(clamped_distance);
  298. }
  299. rect body() const
  300. {
  301. return {float2::one(0.1), position, half};
  302. };
  303. auto elbows() const
  304. {
  305. const auto normal = common::rotate(this->direction(), common::protractor<>::tau(1/4.f));
  306. auto body = this->body();
  307. std::array<float2,2> elbows{};
  308. const auto spread = 1.7f;
  309. auto arm = 1;
  310. for(auto&& elbow : elbows)
  311. {
  312. elbow = body.position;
  313. elbow += body.size.x() * normal * spread * arm;
  314. elbow += - this->direction() * body.size.x()/2 * 1.3;
  315. if(elbow == elbows[punch_glove])
  316. elbow -= body.size.x() * normal * (0.7 * punch) * arm;
  317. arm = -arm;
  318. }
  319. elbows[punch_glove] += this->direction() * punch * 0.1;
  320. return elbows;
  321. };
  322. auto gloves() const
  323. {
  324. const auto normal = common::rotate(this->direction(), common::protractor<>::tau(1/4.f));
  325. auto body = this->body();
  326. std::array<rect,2> gloves{};
  327. for(auto&& glove : gloves)
  328. {
  329. glove = body;
  330. glove.size *= 0.9;
  331. glove.position += body.size.x() * normal;
  332. glove.position += this->direction() * body.size.x()/2;
  333. }
  334. gloves[1].position -= body.size.x() * 2 * normal;
  335. gloves[punch_glove].position += this->direction() * punch * 0.2;
  336. return gloves;
  337. };
  338. void draw(frame& f, auto delta_time)
  339. {
  340. const auto direction = this->direction();
  341. const auto normal = common::rotate(direction, common::protractor<>::tau(1/4.f));
  342. const auto facingdom = float2x2{direction, normal};
  343. auto body = this->body();
  344. {
  345. float2 squeezement = float2::one();
  346. this->squeezement.move(squeezement, delta_time);
  347. const auto squeezed_body_size = body.size * squeezement;
  348. body.position += direction * (squeezed_body_size.x() - body.size.x());
  349. body.size = squeezed_body_size;
  350. }
  351. auto elbows = this->elbows();
  352. auto gloves = this->gloves();
  353. {
  354. float idle_sway = 0.f;
  355. auto idle_normal = normal;
  356. motion::loop(idle_sway, idle, delta_time);
  357. for(auto&& glove : gloves)
  358. {
  359. glove.position += idle_normal * idle_sway;
  360. idle_normal = -idle_normal;
  361. }
  362. body.position += direction * idle_sway;
  363. }
  364. {
  365. gloves[punch_glove].size += gloves[punch_glove].size * float2{0.1f, -0.1f} * punch;
  366. }
  367. {
  368. float2 squeezement = float2::one();
  369. this->glove_squeezement.move(squeezement, delta_time);
  370. const auto squeezed_glove_size = gloves[hit_glove].size * squeezement;
  371. gloves[hit_glove].position += direction * (squeezed_glove_size.x() - gloves[hit_glove].size.x());
  372. gloves[hit_glove].size = squeezed_glove_size;
  373. }
  374. {
  375. auto scaled_body = scale(body,ring);
  376. for(auto&& [elbow, glove] : range{vbegin(elbows, gloves), vend(elbows,gloves)})
  377. {
  378. auto scaled_elbow = elbow * ring.size + ring.position;
  379. auto scaled_glove = scale(glove,ring);
  380. f.begin_sketch()
  381. .move(scaled_body.position)
  382. .bezier(scaled_elbow, scaled_glove.position)
  383. .line_width((0.04f) * ring.size.x()).outline(0x333333_rgb)
  384. ;
  385. }
  386. f.begin_sketch()
  387. .ellipse(scaled_body.position, facingdom * geom::column(scaled_body.size/2))
  388. .fill(rgb::white(0.5))
  389. ;
  390. }
  391. {
  392. auto thumb_offsetdom = geom::vector(
  393. -direction * 0.1f,
  394. -normal * 0.4f
  395. );
  396. auto glove_sketch = f.begin_sketch();
  397. for(auto&& glove : gloves)
  398. {
  399. auto scaled_glove = scale(glove,ring);
  400. glove_sketch
  401. .ellipse(scaled_glove.position, facingdom * geom::column(scaled_glove.size/2))
  402. .ellipse(scaled_glove.position + thumb_offsetdom(scaled_glove.size), facingdom * geom::column(scaled_glove.size/2/2))
  403. ;
  404. thumb_offsetdom[1] = -thumb_offsetdom[1];
  405. }
  406. glove_sketch.fill(cola);
  407. }
  408. auto socket_position = [&](float side) { return body.position + eye_t::size/2 * (direction + side * normal * 1.2f); };
  409. { // move eyes, techincally physics but purely visual and need the visually augmented body geometry
  410. auto eye_difference = eyes[0].position - eyes[1].position;
  411. const auto overlapance = eye_t::size.x() * eye_t::size.x() - eye_difference.quadrance();
  412. for(auto&& eye : eyes)
  413. if(damage < (eye.pop_damage + 20) && motion::loop(eye.drift_ratio, eye.drifting, delta_time))
  414. {
  415. eye.drift = common::rotate(float2::i(trand_float({0.2f, 0.8f})), common::protractor<>::tau(trand_float()));
  416. eye.drifting.total<0>() = eye.drifting.total<1>() = 700ms + trand_float() * 200ms;
  417. }
  418. if(overlapance > 0)
  419. {
  420. auto impact_surface = common::rotate(eye_difference, common::protractor<>::tau(1/4.f));
  421. float side = 1;
  422. for(auto&& eye : eyes)
  423. {
  424. eye.drift = common::reflect(eye.drift, impact_surface);
  425. eye.position += side * common::normalize(eye_difference) * support::root2(overlapance)/2;
  426. side = -side;
  427. }
  428. }
  429. float side = 1;
  430. for(auto&& eye : eyes)
  431. {
  432. if(damage >= (eye.pop_damage + 20) && not eye.fall.done())
  433. {
  434. if(eye.fall.advance(delta_time))
  435. {
  436. for(int i = 3; i --> 0;) blood.push_back({
  437. 0xffbbbb_rgb,
  438. {250ms, (trand_float2() - 0.5f) / 50, float2::zero()},
  439. eye.position,
  440. 0.01f
  441. });
  442. for(int i = 20; i --> 0;) blood.push_back({
  443. rgb::red(0.8f),
  444. {250ms, (trand_float2() - 0.5f) / 50, float2::zero()},
  445. eye.position,
  446. 0.005f
  447. });
  448. blood.push_back({
  449. rgb::white(0), // hmm...
  450. {250ms, (trand_float2() - 0.5f) / 50, float2::zero()},
  451. eye.position,
  452. 0.005f
  453. });
  454. auto spit = [tick = 0u, noise = 1.f](auto ratio) mutable
  455. {
  456. ++tick;
  457. if(tick % 10 == 0)
  458. noise = trand_float({ratio/2, 0.5f + (1-ratio)/2});;
  459. return std::pair
  460. {
  461. common::cosinus(ratio) * 20.f + 20.f,
  462. (1 - ratio) * noise
  463. };
  464. };
  465. program->request_wave({faded_wave(20, std::move(spit)), 50ms});
  466. }
  467. if(motion::loop(eye.bleed, delta_time))
  468. {
  469. const auto velocity = common::rotate(direction,
  470. common::protractor<>::tau(
  471. support::wrap(trand_float({-1/16.f, 1/16.f}), 1.f)
  472. )
  473. );
  474. for(int i = 3; i --> 0;) blood.push_back({
  475. rgb::red(0.8f),
  476. {250ms, velocity / 50 * eye.fall.value()/2, float2::zero()},
  477. socket_position(side),
  478. 0.005f
  479. });
  480. }
  481. }
  482. auto eye_offset = socket_position(side) - eye.position;
  483. eye.velocity = (damage < (eye.pop_damage + 20)) ? 10 * eye_offset : (eye.drift * (1-eye.drift_ratio));
  484. eye.position += (eye.velocity + eye.drift_ratio * eye.drift) * delta_time.count();
  485. if(damage < eye.pop_damage)
  486. eye.position = socket_position(side);
  487. side = -side;
  488. }
  489. }
  490. {
  491. float side = 1;
  492. for(auto&& eye : eyes)
  493. {
  494. if(damage > eye.pop_damage/2)
  495. {
  496. for(auto blackeye : eye.black)
  497. {
  498. blackeye.position = socket_position(side) + common::rotate(direction * eye_t::size/2 * 0.9f, blackeye.position);
  499. f.begin_sketch()
  500. .ellipse(scale(blackeye, ring))
  501. .fill(0x55007733_rgba)
  502. ;
  503. }
  504. }
  505. auto scaled_socket = scale(rect{eye_t::size * 0.8, socket_position(side)}, ring);
  506. f.begin_sketch()
  507. .ellipse(scaled_socket.position, facingdom * geom::column(scaled_socket.size/2))
  508. .fill(0x660000_rgb)
  509. ;
  510. side = -side;
  511. }
  512. }
  513. for(auto&& eye : eyes)
  514. {
  515. if(damage < eye.pop_damage * 0.8f)
  516. {
  517. if(0 != motion::loop(eye.lid_value, eye.blink, delta_time))
  518. {
  519. eye.blink.total<0>() = 500ms + trand_float() * 2s;
  520. float heft = std::max((damage - eye.pop_damage/3.f)/(eye.pop_damage/3.f), 0.f);
  521. eye.blink.total<2>() = eye.blink.total<2>() = 100ms + heft * 500ms;
  522. }
  523. }
  524. else
  525. {
  526. if(0 != motion::loop(eye.lid_value, eye.twitch, delta_time))
  527. {
  528. eye.twitch.total<0>() = 500ms + trand_float() * 2s;
  529. }
  530. }
  531. }
  532. if(damage < std::min(eyes[0].pop_damage, eyes[1].pop_damage)/3)
  533. {
  534. eyes[0].blink.total<0>() = eyes[1].blink.total<0>();
  535. }
  536. { // put the twitching eyelid under the popped eye
  537. float side = 1;
  538. for(auto&& eye : eyes)
  539. {
  540. if(damage >= eye.pop_damage)
  541. {
  542. auto scaled_eyelid = scale(rect{eye_t::size * 0.8f, socket_position(side)}, ring);
  543. auto center_offset = direction * (0.4f + eye.lid_value) * scaled_eyelid.size.x()/2;
  544. f.begin_sketch()
  545. .arc(scaled_eyelid.position, range{normal, -normal} * scaled_eyelid.size.x()/2, 3)
  546. .sector(scaled_eyelid.position + center_offset, range{-normal, normal} * scaled_eyelid.size.x()/2 - center_offset, -3)
  547. .fill(rgb::red(0.8f))
  548. ;
  549. }
  550. side = -side;
  551. }
  552. }
  553. {
  554. float side = 1;
  555. for(auto&& eye : eyes)
  556. {
  557. auto scaled_socket = scale(rect{eye_t::size * 0.8, socket_position(side)}, ring);
  558. auto scaled_eye = scale(rect{eye_t::size, eye.position}, ring);
  559. auto distance = (socket_position(side) - eye.position).quadrance();
  560. auto scaled_root = scale(rect{eye_t::size * 0.4, socket_position(side)}, ring);
  561. f.begin_sketch()
  562. .ellipse(scaled_root.position, facingdom * geom::column(scaled_root.size/2))
  563. .fill(rgb::red(0.8f))
  564. ;
  565. if(damage < (eye.pop_damage + 20))
  566. f.begin_sketch()
  567. .line(scaled_socket.position, scaled_eye.position)
  568. .line_width( scaled_socket.size.x() * std::max(0.2f, 1.f-distance/(eye_t::size.x()*eye_t::size.x()*9))/2 )
  569. .outline(rgb::red(0.8f))
  570. ;
  571. side = -side;
  572. }
  573. }
  574. const auto look_direction = common::normalize(target->position - position);
  575. for(auto&& eye : eyes)
  576. {
  577. bool eye_popped = damage >= eye.pop_damage;
  578. const auto eye_direction = (eye.velocity != float2::zero() && eye_popped) ? -common::normalize(eye.velocity) : look_direction;
  579. const auto eye_normal = common::rotate(eye_direction, common::protractor<>::tau(1/4.f));
  580. const auto eye_stretch = eye_popped ? 1+eye.velocity.quadrance() : 1;
  581. const auto eye_facingdom = float2x2{eye_direction * eye_stretch, eye_normal};
  582. auto scaled_eye = scale(rect{eye_t::size, eye.position}, ring);
  583. const auto pupil_size = eye_t::size * 0.45f * eye.fall.value();
  584. // WISH: make pupil a proper crescent and go up the eye when blinked
  585. const auto pupil_blink_offset = (damage < eye.pop_damage * 0.8f) ? eye_direction * eye.lid_value * pupil_size/4 : float2::zero();
  586. auto scaled_pupil = scale(rect{pupil_size, eye.position + eye_direction * pupil_size - pupil_blink_offset}, ring);
  587. if(not eye.fall.done())
  588. {
  589. f.begin_sketch()
  590. .ellipse(scaled_pupil.position, eye_facingdom * geom::column(scaled_pupil.size/2))
  591. .fill(rgb::white(0.f)) // hmm...
  592. ;
  593. f.begin_sketch()
  594. .ellipse(scaled_eye.position, eye_facingdom * geom::column(scaled_eye.size/2 * eye.fall.value())) // TODO: specify vertex count here and elsewhere
  595. .fill(wayback(0xffffff_rgb, 0xffbbbb_rgb, std::min(1.f, damage/(eye.pop_damage * 0.8f))))
  596. ;
  597. }
  598. // TODO: graduate by bursts and animate em filling up
  599. if(damage > eye.pop_damage * 0.6 && not eye.fall.done())
  600. for(auto&& vein : eye.veins) // this eats up a lot of cpu :( i blame nanovg and C in general who just can't libraries >:(
  601. {
  602. auto sketch = f.begin_sketch();
  603. sketch.move(eye_facingdom(vein.path.front()) * scaled_eye.size/2 + scaled_eye.position);
  604. for(auto&& x : range{vein.path.begin()+1, vein.path.end()})
  605. {
  606. sketch.vertex(eye_facingdom(x) * scaled_eye.size/2 * eye.fall.value() + scaled_eye.position);
  607. }
  608. sketch
  609. .line_width(vein.width * scaled_eye.size.x() * eye.fall.value())
  610. .line_cap(sketch::cap::round)
  611. .line_join(sketch::join::round)
  612. .outline(rgb::red())
  613. ;
  614. }
  615. }
  616. {
  617. float side = 1;
  618. for(auto&& eye : eyes)
  619. {
  620. if(damage < eye.pop_damage * 0.8f)
  621. {
  622. auto scaled_eyelid = scale(rect{eye_t::size * 1.2, socket_position(side)}, ring);
  623. auto center_offset = direction * (1-eye.lid_value) * scaled_eyelid.size.x();
  624. auto color = damage > eye.pop_damage/2 ? rgb(0x554477_rgb) : rgb::white(0.4f);
  625. f.begin_sketch()
  626. .arc(scaled_eyelid.position, range{normal, -normal} * scaled_eyelid.size.x()/2, 3) // backlid
  627. // .sector(scaled_eyelid.position - center_offset, (range{-normal, normal} * scaled_eyelid.size.x()/2) + center_offset, 3) // wtf gcc 11.4.0
  628. .sector(scaled_eyelid.position - center_offset, range{-normal, normal} * scaled_eyelid.size.x()/2 + center_offset, 3) // frontlid
  629. .fill(color)
  630. ;
  631. }
  632. else if(damage < eye.pop_damage)
  633. {
  634. auto scaled_eyelid = scale(rect{eye_t::size, socket_position(side)}, ring);
  635. scaled_eyelid.size += 1; // freakin antialiasing
  636. auto center_offset = direction * (0.4f + eye.lid_value) * (scaled_eyelid.size.x()/2);
  637. f.begin_sketch()
  638. .arc(scaled_eyelid.position, range{normal, -normal} * scaled_eyelid.size.x()/2, 3)
  639. .sector(scaled_eyelid.position + center_offset, range{-normal, normal} * scaled_eyelid.size.x()/2 - center_offset, -3)
  640. .fill(rgb::red(0.8f))
  641. ;
  642. }
  643. side = -side;
  644. }
  645. }
  646. }
  647. void move(auto delta_time)
  648. {
  649. const range2f bounds {position - 0.05, position + 0.05};
  650. constexpr auto world_bounds = range2f{float2::zero(), float2::one()};
  651. const auto lower_overshoot = world_bounds.lower() - bounds.lower();
  652. const auto upper_overshoot = bounds.upper() - world_bounds.upper();
  653. constexpr float springback_factor = 20;
  654. velocity += springback_factor * max(lower_overshoot, float2::zero());
  655. velocity -= springback_factor * max(upper_overshoot, float2::zero());
  656. position += velocity * speed * delta_time.count();
  657. if(not shovement.done())
  658. {
  659. position += shovement.value() * (speed /2) * delta_time.count();
  660. shovement.advance(delta_time);
  661. }
  662. auto diameter = 0.2f;
  663. const auto distance = target->position - position;
  664. if(distance.quadrance() < diameter * diameter)
  665. {
  666. auto direction = common::normalize(distance);
  667. auto force_field = direction * diameter;
  668. auto correction = distance - force_field;
  669. position += correction;
  670. }
  671. if(punch > 0 || hit)
  672. {
  673. if(not hit)
  674. {
  675. punch += 10 * delta_time.count();;
  676. if(punch >= 1)
  677. {
  678. hit = true;
  679. punch = 1;
  680. }
  681. }
  682. else
  683. {
  684. if(punch > 0)
  685. {
  686. punch -= 10 * delta_time.count();
  687. }
  688. else
  689. {
  690. punch = 0;
  691. hit = false;
  692. }
  693. }
  694. }
  695. }
  696. void update(frame& f, auto delta_time)
  697. {
  698. move(delta_time);
  699. draw(f, delta_time);
  700. }
  701. std::optional<rect> punching_glove() const
  702. {
  703. const bool forward = punch > 0 && not hit;
  704. const bool edge_case = punch >= 1; // same frame as hit set to true but still want collision checks
  705. if(forward || edge_case)
  706. return gloves()[punch_glove];
  707. else
  708. return std::nullopt;
  709. }
  710. unsigned closest_glove_index() const
  711. {
  712. return
  713. (position.y() > target->position.y())
  714. ^
  715. (position.x() > target->position.x())
  716. ;
  717. }
  718. rect closest_glove() const
  719. { return gloves()[closest_glove_index()]; }
  720. };
  721. std::array boxas
  722. {
  723. boxa{rgb::red(), starting_pos},
  724. boxa{rgb::blue(), float2::one() - starting_pos}
  725. };
  726. constexpr rect fit_square(float2 size)
  727. {
  728. float2 fit_size = float2::one(*min_element(size));
  729. return {fit_size, (size - fit_size) / 2}; // hmmm, does this sub/2 thing have a name? midpointish but not quite
  730. }
  731. void punch(Program& program, boxa& boxa)
  732. {
  733. if(boxa.punch == 0)
  734. {
  735. auto hiss = [rand = make_trand_float({0,0.1})](auto ratio) mutable
  736. { return std::pair{rand(), ratio/5}; };
  737. program.request_wave({faded_wave(20, std::move(hiss)), 125ms});
  738. // program.request_wave({faded_wave(20, std::move(hiss)), 50ms});
  739. boxa.punch = 0.001;
  740. boxa.punch_glove = boxa.closest_glove_index();
  741. }
  742. }
  743. void start(Program& program)
  744. {
  745. // program.frametime = framerate<30>::frametime;
  746. // program.frametime = framerate<60>::frametime;
  747. // program.frametime = framerate<120>::frametime;
  748. program.resizable = true;
  749. program.size = float2::one(400.f);
  750. ring = fit_square(program.size);
  751. boxas[0].target = &boxas[1];
  752. boxas[1].target = &boxas[0];
  753. boxas[1].brainz = complayer{};
  754. // boxas[0].brainz = complayer{};
  755. for(auto&& boxa : boxas)
  756. {
  757. boxa.idle.advance(trand_float() * boxa.idle.total);
  758. auto last_pop = trand_int({70,91});
  759. auto last_eye = trand_int({0,2});
  760. boxa.eyes[last_eye].pop_damage = last_pop;
  761. boxa.eyes[not last_eye].pop_damage = last_pop - trand_int({10,21});
  762. boxa.program = &program;
  763. }
  764. if(program.argc > 2)
  765. {
  766. using support::ston;
  767. using seed_t = decltype(tiny_rand());
  768. tiny_rand.seed({ ston<seed_t>(program.argv[1]), ston<seed_t>(program.argv[2]) });
  769. }
  770. std::cout << "seed: " << std::hex << std::showbase << tiny_rand << '\n';
  771. program.key_up = [&](scancode code, keycode)
  772. {
  773. switch(code)
  774. {
  775. case scancode::leftbracket:
  776. case scancode::c:
  777. if(pressed(scancode::rctrl) || pressed(scancode::lctrl))
  778. case scancode::escape:
  779. program.end();
  780. break;
  781. default: break;
  782. }
  783. };
  784. program.key_down = [&](scancode code, keycode)
  785. {
  786. for(auto&& boxa : boxas)
  787. if(boxa.controls)
  788. if(support::find(boxa.controls->punch, code) != boxa.controls->punch.end())
  789. punch(program, boxa);
  790. };
  791. program.mouse_down = [&](float2, auto)
  792. {
  793. };
  794. program.size_changed = [&](float2 size)
  795. {
  796. std::cout << size << '\n';
  797. ring = fit_square(size);
  798. std::cout << ring << '\n';
  799. };
  800. program.draw_loop = [&](auto frame, auto delta_time)
  801. {
  802. frame.begin_sketch()
  803. .rectangle(rect{frame.size})
  804. .fill(rgb::white(0.4f))
  805. ;
  806. frame.begin_sketch()
  807. .rectangle(ring)
  808. .fill(rgb(0.3f, 0.4f, 0.0f))
  809. ;
  810. for(auto&& boxa : boxas)
  811. {
  812. boxa.velocity = float2::zero();
  813. if(boxa.controls)
  814. {
  815. for(auto&& kv : boxa.controls->legwork)
  816. if(pressed(kv.k))
  817. boxa.velocity += kv.v;
  818. if(boxa.velocity != float2::zero())
  819. boxa.velocity = common::normalize(boxa.velocity); // here length can only be root(2) or 1 so a bit of an overkill
  820. }
  821. else
  822. {
  823. auto selected = support::find_if(control_options, [](auto option)
  824. {
  825. return support::any_of(option.legwork, [](auto kv) { return pressed(kv.k); });
  826. });
  827. if(selected != control_options.end())
  828. {
  829. boxa.controls = *selected;
  830. control_options.erase(selected);
  831. }
  832. }
  833. if(boxa.brainz)
  834. {
  835. if(boxa.brainz->stun_timer.done())
  836. {
  837. // FIXME: need more randomness for comp v comp demo
  838. const auto get_flank_offset = [&boxa](float2 flank){ return boxa.target->position + flank - boxa.position; };
  839. if(motion::loop(boxa.brainz->legwork_timer, delta_time) != 0)
  840. {
  841. const auto direction = boxa.target->direction();
  842. const auto normal = common::rotate(direction, common::protractor<>::tau(1/4.f));
  843. const auto faceoff = direction * boxa.target->body().size * 2.9;
  844. const auto backstab = -faceoff;
  845. const auto normal_flank = faceoff + normal * boxa.target->body().size;
  846. const auto unnormal_flank = faceoff - normal * boxa.target->body().size;
  847. const auto back_normal_flank = backstab + normal * boxa.target->body().size;
  848. const auto back_unnormal_flank = backstab - normal * boxa.target->body().size;
  849. std::array<float2, 4> candidates {normal_flank, unnormal_flank, back_normal_flank, back_unnormal_flank};
  850. auto ring_candidates_end = std::remove_if(candidates.begin(), candidates.end(),
  851. [&boxa](auto flank){ return not range{float2::zero(),float2::one()}.contains(boxa.target->position + flank); });
  852. using support::transform_arg;
  853. boxa.brainz->legwork_flank = *std::min_element(candidates.begin(), ring_candidates_end,
  854. transform_arg{get_flank_offset, transform_arg{&float2::quadrance}});
  855. if(boxa.direction().y() != 0 && (boxa.position - boxa.target->position).quadrance() < 0.04)
  856. {
  857. auto normal = common::rotate(boxa.direction(), common::protractor<>::tau(1/4.f));
  858. auto diff = boxa.target->position - boxa.position;
  859. boxa.brainz->sidestep = normal(diff) > 0 ? -1 : 1;
  860. }
  861. else
  862. boxa.brainz->sidestep = 0;
  863. }
  864. auto flank_offset = get_flank_offset(boxa.brainz->legwork_flank);
  865. if(flank_offset.quadrance() > 0.0001)
  866. boxa.velocity = common::normalize(flank_offset);
  867. if(boxa.brainz->sidestep != 0)
  868. {
  869. auto normal = common::rotate(boxa.direction(), common::protractor<>::tau(1/4.f));
  870. boxa.velocity = normal * boxa.brainz->sidestep;
  871. }
  872. {
  873. const auto closest_glove_offset = (boxa.target->position - boxa.closest_glove().position);
  874. if(boxa.brainz->punch_timer.done())
  875. {
  876. // make these complayer parameters, along with faceoff distance above close
  877. const float punch_length = 0.26f;
  878. const float punch_width = 0.25f * (0.1f + 0.9f * std::max((1.f - boxa.damage/100.f),0.f));
  879. auto normal = common::rotate(boxa.direction(), common::protractor<>::tau(1/4.f));
  880. if
  881. (
  882. closest_glove_offset(boxa.direction()) < punch_length
  883. && closest_glove_offset(normal) < punch_width
  884. && closest_glove_offset(-normal) < punch_width
  885. )
  886. {
  887. punch(program, boxa);
  888. boxa.brainz->punch_timer.total = boxa.brainz->punch_intervals[trand_int({0, (int)boxa.brainz->punch_intervals.size()})];
  889. boxa.brainz->punch_timer.total *= 0.25f + 0.75f * std::max((1.f - boxa.damage/100.f),0.f);
  890. boxa.brainz->punch_timer.reset();
  891. }
  892. }
  893. else
  894. boxa.brainz->punch_timer.advance(delta_time);
  895. }
  896. // frame.begin_sketch().rectangle(scale(rect{{float2::one(0.1), boxa.position + flank_offset}, float2::one(0.5f)},ring)).fill(0x00000033_rgba);
  897. // frame.begin_sketch().rectangle(scale(rect{{float2::one(0.1), boxa.gloves()[boxa.punch_glove].position}, float2::one(0.5f)},ring)).fill(0x00000033_rgba);
  898. }
  899. else
  900. boxa.brainz->stun_timer.advance(delta_time);
  901. }
  902. }
  903. for(auto&& b : boxas) b.update(frame, delta_time);
  904. for(auto&& b : boxas) // collision checks
  905. {
  906. auto glove = b.punching_glove();
  907. bool collided_target = false;
  908. bool collided_a_glove = false;
  909. bool collided_punching_glove = false;
  910. if(glove)
  911. {
  912. collided_target = (glove->position - b.target->position).quadrance() <= 0.085*0.085;
  913. if(not collided_target)
  914. {
  915. auto other_gloves = b.target->gloves();
  916. auto collided_glove = support::find_if(other_gloves, [pos = glove->position](auto x) {
  917. return (pos - x.position).quadrance() <= x.size.quadrance()/2;
  918. });
  919. collided_a_glove = collided_glove != other_gloves.end();
  920. auto other_punching_glove = b.target->punching_glove();
  921. if(collided_a_glove && other_punching_glove)
  922. {
  923. collided_punching_glove = (*collided_glove) == (*other_punching_glove);
  924. }
  925. if(collided_a_glove)
  926. {
  927. b.target->hit_glove = collided_glove - other_gloves.begin();
  928. }
  929. }
  930. }
  931. if(collided_target)
  932. {
  933. b.target->shovement.total = 250ms;
  934. const auto normal = common::rotate(b.direction(), common::protractor<>::tau(1/4.f));
  935. const auto target_normal = common::rotate(b.target->direction(), common::protractor<>::tau(1/4.f));
  936. const auto target_offset = b.position - b.target->position;
  937. b.target->shovement.start = (b.direction() + (target_normal(target_offset) < 0 ? normal*2 : -normal*2)) * 5;
  938. b.target->shovement.reset();
  939. {
  940. constexpr auto squeezementat = float2{0.5f, 1.5f};
  941. b.target->squeezement = melody{
  942. movement<float2>{125ms, float2::one(), squeezementat},
  943. movement<float2>{125ms, squeezementat, float2::one()}
  944. };
  945. }
  946. ++b.target->damage; // TODO: more damage for combos?
  947. for(auto&& eye : b.target->eyes)
  948. {
  949. if(b.target->damage == eye.pop_damage)
  950. {
  951. auto plok = [](auto ratio)
  952. { return std::pair{common::cosinus(ratio) * 10.f + 10.f, 1}; };
  953. program.request_wave({faded_wave(20, std::move(plok)), 50ms});
  954. }
  955. if(b.target->damage == eye.pop_damage + 20)
  956. {
  957. auto tips = [tick = 0u, noise = 1.f](auto ratio) mutable
  958. {
  959. ratio = 1-ratio;
  960. ++tick;
  961. if(tick % 10 == 0)
  962. noise = trand_float({ratio/2, 0.5f + (1-ratio)/2});;
  963. return std::pair
  964. {
  965. common::cosinus(ratio) * 20.f + 20.f,
  966. (1 - ratio) * noise
  967. };
  968. };
  969. program.request_wave({faded_wave(20, std::move(tips)), 50ms});
  970. }
  971. }
  972. auto boink = [](auto ratio)
  973. { return std::pair{way(40.f, 20.f, ratio), 1}; };
  974. program.request_wave({faded_wave(20, std::move(boink)), 125ms});
  975. const auto splatter_direction = (b.target->position - b.position).signum();
  976. for(int i = 10; i --> 0;) blood.push_back({
  977. wayback(0x55cccc_rgb,0x660000_rgb, std::min(b.target->damage/50.f, 1.f)),
  978. {250ms, trand_float2() / 50 * splatter_direction, float2::zero()},
  979. b.target->position
  980. });
  981. if(b.target->brainz)
  982. {
  983. b.target->brainz->stun_timer.total = b.target->brainz->stun_interval * (0.10f + 0.90f * std::max((1.f - b.target->damage/100.f),0.f));
  984. b.target->brainz->stun_timer.reset();
  985. }
  986. std::cout << std::dec << boxas[0].damage << " vs " << boxas[1].damage << '\n';
  987. }
  988. if(collided_a_glove)
  989. {
  990. constexpr auto squeezementat = float2{0.7f, 1.3f};
  991. b.target->glove_squeezement = melody{
  992. movement<float2>{60ms, float2::one(), squeezementat},
  993. movement<float2>{60ms, squeezementat, float2::one()}
  994. };
  995. auto bunk = [](auto ratio)
  996. { return std::pair{way(25.f, 0.f, ratio), .3f}; };
  997. program.request_wave({faded_wave(20, std::move(bunk)), 125ms});
  998. }
  999. if(collided_target || collided_a_glove)
  1000. b.hit = true;
  1001. if(collided_punching_glove)
  1002. {
  1003. b.target->hit = true;
  1004. break;
  1005. }
  1006. }
  1007. for(auto&& b : blood) b.update(frame, delta_time);
  1008. blood.erase(std::remove_if(blood.begin(),blood.end(), [](auto& b) {return b.done();}), blood.end());
  1009. };
  1010. // program.set_volume_levels(support::filled_array<32>(0.25f));
  1011. // musac
  1012. program.request_wave({
  1013. [
  1014. time = 0.f, freq = 0.f,
  1015. musac = motion::melody
  1016. {
  1017. movement<float>{50ms, 0.f,120.f},
  1018. movement<float>{50ms, 120.f,0.f},
  1019. movement<float>{100ms, 0.f,0.f},
  1020. movement<float>{50ms, 0.f,150.f},
  1021. movement<float>{50ms, 150.f,0.f},
  1022. movement<float>{100ms, 0.f,0.f},
  1023. movement<float>{50ms, 0.f,100.f},
  1024. movement<float>{50ms, 100.f,0.f},
  1025. movement<float>{100ms, 0.f,0.f},
  1026. movement<float>{50ms, 0.f,200.f},
  1027. movement<float>{50ms, 200.f,0.f},
  1028. movement<float>{100ms, 0.f,0.f},
  1029. movement<float>{50ms, 0.f,220.f},
  1030. movement<float>{50ms, 220.f,0.f},
  1031. movement<float>{100ms, 0.f,0.f},
  1032. movement<float>{50ms, 0.f,250.f},
  1033. movement<float>{50ms, 250.f,0.f},
  1034. movement<float>{100ms, 0.f,0.f},
  1035. movement<float>{50ms, 0.f,200.f},
  1036. movement<float>{50ms, 200.f,0.f},
  1037. movement<float>{100ms, 0.f,0.f},
  1038. movement<float>{50ms, 0.f,100.f},
  1039. movement<float>{50ms, 100.f,0.f},
  1040. movement<float>{100ms, 0.f,0.f},
  1041. movement<float>{50ms, 0.f,120.f},
  1042. movement<float>{50ms, 120.f,0.f},
  1043. movement<float>{100ms, 0.f,0.f},
  1044. movement<float>{50ms, 0.f,0.f},
  1045. movement<float>{50ms, 0.f,0.f},
  1046. movement<float>{100ms, 0.f,0.f},
  1047. movement<float>{50ms, 0.f,100.f},
  1048. movement<float>{50ms, 100.f,0.f},
  1049. movement<float>{100ms, 0.f,0.f},
  1050. movement<float>{50ms, 0.f,200.f},
  1051. movement<float>{50ms, 200.f,0.f},
  1052. movement<float>{100ms, 0.f,0.f},
  1053. movement<float>{50ms, 0.f,0.f},
  1054. movement<float>{50ms, 0.f,0.f},
  1055. movement<float>{100ms, 0.f,0.f},
  1056. movement<float>{50ms, 0.f,250.f},
  1057. movement<float>{50ms, 250.f,0.f},
  1058. movement<float>{100ms, 0.f,0.f},
  1059. movement<float>{50ms, 0.f,200.f},
  1060. movement<float>{50ms, 200.f,0.f},
  1061. movement<float>{100ms, 0.f,0.f},
  1062. movement<float>{50ms, 0.f,0.f},
  1063. movement<float>{50ms, 0.f,0.f},
  1064. movement<float>{100ms, 0.f,0.f},
  1065. }
  1066. ](auto tick) mutable
  1067. {
  1068. motion::loop(freq, musac, Program::duration(tick));
  1069. time += tick * freq;
  1070. time = support::wrap(time,1.f);
  1071. return stairwave(time) * 0.2f;
  1072. },
  1073. 0ms});
  1074. program.request_wave({
  1075. [
  1076. time = 0.f, freq = 0.f,
  1077. musac = motion::melody
  1078. {
  1079. movement<float, quadratic_out>{10ms, 0.f, 350.f},
  1080. movement<float, quadratic_out>{90ms, 350.f,420.f},
  1081. movement<float, motion::quadratic_curve>{200ms, 420.f,350.f},
  1082. movement<float, quadratic_out>{100ms, 350.f,350.f},
  1083. movement<float, quadratic_out>{100ms, 350.f,750.f},
  1084. movement<float, motion::quadratic_curve>{200ms, 750.f,350.f},
  1085. movement<float, quadratic_out>{100ms, 350.f,350.f},
  1086. movement<float, motion::quadratic_curve>{200ms, 350.f,400.f},
  1087. movement<float, quadratic_out>{100ms, 400.f,350.f},
  1088. movement<float, quadratic_out>{500ms, 350.f,350.f},
  1089. movement<float, quadratic_out>{100ms, 350.f,420.f},
  1090. movement<float, motion::quadratic_curve>{200ms, 420.f,350.f},
  1091. movement<float, quadratic_out>{100ms, 350.f,350.f},
  1092. movement<float, quadratic_out>{100ms, 350.f,750.f},
  1093. movement<float, motion::quadratic_curve>{200ms, 750.f,350.f},
  1094. movement<float, quadratic_out>{90ms, 350.f,350.f},
  1095. movement<float, quadratic_out>{10ms, 350.f,0.f},
  1096. movement<float>{800ms, 0.f,0.f},
  1097. movement<float>{800ms, 0.f,0.f},
  1098. movement<float>{800ms, 0.f,0.f},
  1099. movement<float>{800ms, 0.f,0.f},
  1100. movement<float>{800ms, 0.f,0.f},
  1101. }
  1102. ](auto tick) mutable
  1103. {
  1104. motion::loop(freq, musac, Program::duration(tick));
  1105. time += tick * (freq/3);
  1106. time = support::wrap(time,1.f);
  1107. return stairwave(time) * 0.3f;
  1108. },
  1109. 0ms});
  1110. program.request_wave({
  1111. [
  1112. time = 0.f, freq = 0.f,
  1113. musac = motion::melody
  1114. {
  1115. movement<float>{800ms, 0.f,0.f},
  1116. movement<float>{800ms, 0.f,0.f},
  1117. movement<float>{800ms, 0.f,0.f},
  1118. movement<float>{800ms, 0.f,0.f},
  1119. movement<float>{800ms, 0.f,0.f},
  1120. movement<float>{800ms, 0.f,0.f},
  1121. movement<float>{800ms, 0.f,0.f},
  1122. movement<float>{800ms, 0.f,0.f},
  1123. movement<float>{800ms, 0.f,0.f},
  1124. movement<float>{800ms, 0.f,0.f},
  1125. movement<float>{800ms, 0.f,0.f},
  1126. movement<float>{800ms, 0.f,0.f},
  1127. movement<float, quadratic_out>{400ms, 0.f, 1350.f},
  1128. movement<float, quadratic_out>{400ms, 1350.f,1000.f},
  1129. movement<float, quadratic_out>{400ms, 1000.f, 1200.f},
  1130. movement<float, quadratic_out>{400ms, 1200.f, 350.f},
  1131. movement<float, quadratic_out>{400ms, 350.f,1000.f},
  1132. movement<float, quadratic_out>{400ms, 1000.f, 0.f},
  1133. movement<float>{800ms, 0.f,0.f},
  1134. }
  1135. ](auto tick) mutable
  1136. {
  1137. motion::loop(freq, musac, Program::duration(tick));
  1138. time += tick * (freq/3);
  1139. time = support::wrap(time,1.f);
  1140. return stairwave(time) * 0.1f;
  1141. },
  1142. 0ms});
  1143. }
  1144. // more than 1000 lines for this? sad...