namespace hr { namespace ads_game { void print(hstream& hs, cross_result cr) { print(hs, cr.h, "@", cr.shift); } void init_textures(); void draw_textures(); vector shape_disk; void set_default_keys(); /* In DS, we also use ads_matrix, but the meaning of the shift parameter is different: * */ vector> rocks; struct rock_generator { ld cshift; ads_object* add(transmatrix T) { auto r = std::make_unique (oRock, nullptr, ads_matrix(T, cshift), 0xFFFFFFFF); r->shape = &shape_disk; auto res = &*r; rocks.emplace_back(std::move(r)); return res; }; void report(string s) { println(hlog, lalign(10, hr::format(tformat, cshift/ds_time_unit)), ": ", s); }; ld rand_range(ld a, ld b) { return lerp(a, b, randd()); }; transmatrix rand_place() { geometry = gSphere; hyperpoint h = random_spin3() * C0; transmatrix T = gpushxto0(h); geometry = gSpace435; for(int i=0; i<4; i++) T[i][3] = T[3][i] = i == 3; return T; }; void death_cross(int qty) { ld rapidity = rand_range(1, 3); cshift += rand_range(0.5, 1); ld alpha = randd() * TAU; report(lalign(0, "Death Cross ", qty)); for(int a=0; a inf */ ld appr = 5; transmatrix T = lorentz(2, 3, -appr) * cspin(0, 2, exp(-appr)) * lorentz(2, 3, appr); /* all the entries happen to be multiples of .125 */ for(int i=0; i<4; i++) for(int j=0; j<4; j++) { auto& b = T[i][j]; b = floor(b * 8 + .5) / 8; } return T; } /* see div_matrix */ transmatrix conv_matrix() { ld appr = 5; transmatrix T = lorentz(2, 3, appr) * cspin(0, 2, exp(-appr)) * lorentz(2, 3, -appr); for(int i=0; i<4; i++) for(int j=0; j<4; j++) { auto& b = T[i][j]; b = floor(b * 8 + .5) / 8; } return T; } void divergent_spiral() { report("Divergent Spiral"); cshift += rand_range(.3, .7); ld alpha = randd() * TAU; ld step = rand_range(0.17, 0.23); for(int i=0; i<45; i++) { cshift += step; add(spin(alpha + i * TAU / 30) * div_matrix()); } cshift += rand_range(.3, .7); } void convergent_spiral() { report("Convergent Spiral"); cshift += rand_range(.3, .7); ld alpha = randd() * TAU; ld step = rand_range(0.17, 0.23); for(int i=0; i<45; i++) { cshift += step; add(spin(alpha + i * TAU / 30) * conv_matrix()); } cshift += rand_range(.3, .7); } void rack() { report("Rack"); int qty = 3 + rand() % 4; ld rapidity = rand_range(1, 3); ld step = rand_range(.45, .75) * ds_scale; ld alpha = rand_range(0, TAU); ld spinv = rand_range(0, TAU); for(int i=0; itype = oResource; r->resource = rt; r->shape = rsrc_shape[rt]; r->col = rsrc_color[rt]; } } }; rock_generator rockgen, rsrcgen; auto future_shown = 5 * TAU; /** start with a fixed good-looking sequence */ bool demo; void init_ds_game() { dynamicval g(geometry, gSpace435); rockgen.cshift = 0; rsrcgen.cshift = 0; /* create the main rock first */ main_rock = rockgen.add(Id); main_rock->col = 0xFFD500FF; main_rock->type = oMainRock; main_rock = rockgen.add(Id); main_rock->col = 0xFF; main_rock->shape = &shape_gold; main_rock->type = oMainRock; /* also create shape_disk */ shape_disk.clear(); for(int d=0; d<=360; d += 15) { shape_disk.push_back(sin(d*degree) * 0.1 * ds_scale); shape_disk.push_back(cos(d*degree) * 0.1 * ds_scale); } rockgen.cshift += 2; if(demo) { rockgen.static_starry_field(); rockgen.hyperboloid(); rockgen.chaotic_starry_field(); rockgen.rack(); } rockgen.add_until(future_shown); rsrcgen.cshift += 1; rsrcgen.add_rsrc_until(future_shown); } void ds_gen_particles(int qty, transmatrix from, ld shift, color_t col, ld spd, ld t, ld spread = 1) { for(int i=0; i(oParticle, nullptr, ads_matrix(from * spin(randd() * TAU * spread) * lorentz(0, 3, (.5 + randd() * .5) * spd), shift), col ); r->shape = &shape_particle; r->life_end = randd() * t; r->life_start = 0; rocks.emplace_back(std::move(r)); } } void ds_crash_ship() { if(ship_pt < invincibility_pt) return; common_crash_ship(); dynamicval g(geometry, gSpace435); ds_gen_particles(rpoisson(crash_particle_qty * 2), inverse(current.T) * spin(ang*degree), current.shift, rsrc_color[rtHull], crash_particle_rapidity, crash_particle_life); } void ds_handle_crashes() { if(paused) return; dynamicval g(geometry, gSphere); vector dmissiles; vector drocks; vector dresources; for(auto m: displayed) { if(m->type == oMissile) dmissiles.push_back(m); if(m->type == oRock || m->type == oMainRock) drocks.push_back(m); if(m->type == oResource) dresources.push_back(m); } for(auto m: dmissiles) { hyperpoint h = kleinize(m->pt_main.h); for(auto r: drocks) { if(pointcrash(h, r->pts)) { m->life_end = m->pt_main.shift; if(r->type != oMainRock) r->life_end = r->pt_main.shift; dynamicval g(geometry, gSpace435); ds_gen_particles(rpoisson(crash_particle_qty), m->at.T * lorentz(2, 3, m->life_end), m->at.shift, missile_color, crash_particle_rapidity, crash_particle_life); if(r->type != oMainRock) ds_gen_particles(rpoisson(crash_particle_qty), r->at.T * lorentz(2, 3, r->life_end), r->at.shift, r->col, crash_particle_rapidity, crash_particle_life); playSound(nullptr, "hit-crush3"); break; } } } if(!game_over) for(int i=0; ipts)) ds_crash_ship(); } for(auto r: dresources) { if(pointcrash(h, r->pts)) { r->life_end = r->pt_main.shift; gain_resource(r->resource); } } } } void ds_fire() { if(!pdata.ammo) return; pdata.ammo--; playSound(nullptr, "fire"); dynamicval g(geometry, gSpace435); transmatrix S0 = inverse(current.T) * spin(ang*degree); transmatrix S1 = S0 * lorentz(0, 3, ads_missile_rapidity); auto r = std::make_unique (oMissile, nullptr, ads_matrix(S1, current.shift), rsrc_color[rtAmmo]); r->shape = &shape_missile; r->life_start = 0; rocks.emplace_back(std::move(r)); } bool ds_turn(int idelta) { multi::handleInput(idelta); ld delta = idelta / 1000.; if(!(cmode & sm::NORMAL)) return false; ds_handle_crashes(); auto& a = multi::actionspressed; auto& la = multi::lactionpressed; if(a[16+4] && !la[16+4] && !paused) ds_fire(); if(a[16+5] && !la[16+5]) switch_pause(); if(a[16+6] && !la[16+6]) view_proper_times = !view_proper_times; if(a[16+7] && !la[16+7]) auto_rotate = !auto_rotate; if(a[16+8] && !la[16+8]) pushScreen(game_menu); if(true) { dynamicval g(geometry, gSpace435); ld pt = delta * ds_simspeed; ld mul = read_movement(); ld dv = pt * ds_accel * mul; if(paused && a[16+11]) { current.T = spin(ang*degree) * cspin(0, 2, mul*delta*-pause_speed) * spin(-ang*degree) * current.T; } else { current.T = spin(ang*degree) * lorentz(0, 3, -dv) * spin(-ang*degree) * current.T; } if(!paused) { pdata.fuel -= dv; ds_gen_particles(rpoisson(dv*fuel_particle_qty), inverse(current.T) * spin(ang*degree+M_PI) * rots::uxpush(0.06 * ds_scale), current.shift, rsrc_color[rtFuel], fuel_particle_rapidity, fuel_particle_life, 0.02); } ld tc = 0; if(!paused) tc = pt; else if(a[16+9]) tc = pt; else if(a[16+10]) tc = -pt; if(!paused && !game_over) { shipstate ss; ss.at.T = inverse(current.T) * spin(ang*degree); ss.at.shift = current.shift; ss.start = ship_pt; ss.current = current; ss.duration = pt; ss.ang = ang; history.emplace_back(ss); } current.T = lorentz(3, 2, -tc) * current.T; auto& mshift = main_rock->pt_main.shift; if(mshift) { current.shift += mshift; current.T = current.T * lorentz(2, 3, mshift); mshift = 0; } fixmatrix(current.T); if(1) { rockgen.add_until(current.shift + future_shown); rsrcgen.add_rsrc_until(current.shift + future_shown); } if(!paused) { ship_pt += pt; pdata.oxygen -= pt; if(pdata.oxygen < 0) { pdata.oxygen = 0; game_over = true; } } else view_pt += tc; if(a[16+4] && !la[16+4] && false) { if(history.size()) history.back().duration = HUGE_VAL; current = random_spin3(); } } return true; } hyperpoint pov = point30(0, 0, 1); cross_result ds_cross0_cone(const transmatrix& T, ld which) { ld a = T[2][2]; ld b = T[2][3]; // a * cosh(t) + b * sinh(t) = 1 // solution: t = log((1 +- sqrt(-a^2 + b^2 + 1))/(a + b)) ld underroot = (1+b*b-a*a); if(underroot < 1e-10) underroot = 0; if(underroot < 0) return cross_result { Hypc, 0}; ld underlog = (1 + which * sqrt(underroot)) / (a + b); if(underlog < 0) return cross_result { Hypc, 0}; ld t = log(underlog); cross_result res; res.shift = t; res.h = T * hyperpoint(0, 0, cosh(t), sinh(t)); if(abs(res.h[2] - 1) > .01) return cross_result{Hypc, 0}; res.h /= hypot_d(3, res.h); res.h[3] = 0; // res.h[2] = sqrt(1 - res.h[3] * res.h[3]); res.h[3] = 0; return res; } cross_result ds_cross0_sim(const transmatrix& T) { // h = T * (0 0 cosh(t) sinh(t)) // T[3][2] * cosh(t) + T[3][3] * sinh(t) = 0 // T[3][2] + T[3][3] * tanh(t) = 0 ld tt = - T[3][2] / T[3][3]; if(tt < -1 || tt > 1) return cross_result{ Hypc, 0 }; cross_result res; ld t = atanh(tt); res.shift = t; res.h = T * hyperpoint(0, 0, cosh(t), sinh(t)); return res; } cross_result ds_cross0(const transmatrix& T) { return which_cross ? ds_cross0_cone(T, which_cross) : ds_cross0_sim(T); } cross_result ds_cross0_light(transmatrix T) { // h = T * (t 0 1 t); h[3] == 0 ld t = T[3][2] / -(T[3][0] + T[3][3]); cross_result res; res.shift = t; res.h = T * hyperpoint(t, 0, 1, t); return res; } transmatrix tpt(ld x, ld y) { return cspin(0, 2, x * ds_scale) * cspin(1, 2, y * ds_scale); } // sometimes the result may be incorrect due to numerical precision -- don't show that then in this case bool invalid(cross_result& res) { ld val = sqhypot_d(3, res.h); if(abs(val-1) > 1e-3 || isnan(val) || abs(res.h[3]) > 1e-3 || isnan(res.h[3])) return true; return false; } void view_ds_game() { displayed.clear(); bool hv = hyperbolic; bool hvrel = among(pmodel, mdRelPerspective, mdRelOrthogonal); sphereflip = hv ? Id : sphere_flipped ? MirrorZ : Id; copyright_shown = ""; if(!hv) draw_textures(); if(1) { for(auto& r: rocks) { auto& rock = *r; poly_outline = 0xFF; if(rock.type == oMainRock) rock.at.shift = current.shift; if(rock.at.shift < current.shift - future_shown) continue; if(rock.at.shift > current.shift + future_shown) continue; if(1) { dynamicval g(geometry, gSpace435); transmatrix at = current.T * lorentz(2, 3, rock.at.shift - current.shift) * rock.at.T; rock.pt_main = ds_cross0(at); if(invalid(rock.pt_main)) continue; if(rock.pt_main.shift < rock.life_start) continue; if(rock.pt_main.shift > rock.life_end) continue; transmatrix at1 = at * lorentz(2, 3, rock.pt_main.shift); rock.pts.clear(); auto& sh = *rock.shape; bool bad = false; for(int i=0; i circle_flat; for(auto c: rock.pts) circle_flat.push_back(c.h / (1 + c.h[2])); ld area = 0; for(int i=0; i 0) continue; if(hv) { ld t = rock.at.shift; if(rock.type == oMainRock) t = floor(t / spacetime_step + .5) * spacetime_step; transmatrix at = current.T * lorentz(2, 3, t - current.shift) * rock.at.T; for(int z0=-spacetime_qty; z0<=spacetime_qty; z0++) { ld z = z0 * spacetime_step; if(t-z < rock.life_start) continue; if(t-z > rock.life_end) continue; transmatrix at1 = at * lorentz(2, 3, z); if((at1 * pov) [2] < 0) continue; auto& sh = *rock.shape; for(int i=0; i 0.1 && rock.life_end == HUGE_VAL) { displayed.push_back(&rock); } } ld delta = paused ? 1e-4 : -1e-4; if(paused) for(auto& ss: history) { if(ss.at.shift < current.shift - 4 * TAU) continue; if(ss.at.shift > current.shift + 4 * TAU) continue; auto& shape = shape_ship; if(hv) { for(int i=0; i g(geometry, gSpace435); cross_result cr = ds_cross0(current.T * lorentz(2, 3, ss.at.shift - current.shift) * ss.at.T); if(cr.shift < delta) continue; if(cr.shift > ss.duration + delta) continue; transmatrix at = current.T * lorentz(2, 3, cr.shift) * ss.at.T; vector pts; for(int i=0; i pts; int ok = 0, bad = 0; for(int i=0; i<=360; i++) { dynamicval g(geometry, gSpace435); auto h = inverse(current_ship.T) * spin(i*degree); auto cr = ds_cross0_light(current.T * lorentz(2, 3, current_ship.shift - current.shift) * h); pts.push_back(cr.h); if(cr.shift > 0) ok++; else bad++; } for(auto h: pts) curvepoint(h); queuecurve(shiftless(sphereflip), 0xFF0000C0, bad == 0 ? 0x00000060 : 0xFFFFFF10, PPR::SUPERLINE); } } view_footer(); } void ds_restart() { if(in_spacetime()) { switch_spacetime(); ds_restart(); switch_spacetime(); return; } main_rock = nullptr; if(true) { dynamicval g(geometry, gSpace435); current = cspin(0, 2, 0.2 * ds_scale); invincibility_pt = ds_how_much_invincibility; } ship_pt = 0; rocks.clear(); history.clear(); init_ds_game(); reset_textures(); pick_textures(); init_rsrc(); } void run_ds_game() { stop_game(); set_geometry(gSphere); start_game(); init_textures(); pick_textures(); ds_restart(); rogueviz::rv_hook(hooks_frame, 100, view_ds_game); rogueviz::rv_hook(shmup::hooks_turn, 0, ds_turn); rogueviz::rv_hook(hooks_prestats, 100, display_rsrc); rogueviz::rv_hook(hooks_handleKey, 0, handleKey); rogueviz::rv_hook(anims::hooks_anim, 100, replay_animation); rogueviz::on_cleanup_or_next([] { main_rock = nullptr; }); } void run_ds_game_std() { lps_enable(&lps_relhell_space); enable_canvas(); run_ds_game(); } auto ds_hooks = arg::add3("-ds-game", run_ds_game); } }