// Hyperbolic Rogue -- Irregular (Voronoi) tilings // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details /** \file irregular.cpp * \brief Irregular (Voronoi) tilings */ #include "hyper.h" namespace hr { EX namespace irr { EX int irrid; #if CAP_IRR EX ld density = 2; EX ld quality = .2; EX int place_attempts = 10; EX int rearrange_max_attempts = 50; EX int rearrange_less = 10; EX int cellcount; #if HDR struct cellinfo { cell *owner; map relmatrices; vector jpoints; hyperpoint p; transmatrix pusher, rpusher; vector neid; vector spin; vector vertices; int localindex; bool is_pseudohept; int patterndir; int generation; }; #endif EX map cellindex; EX vector cells; EX map > cells_of_heptagon; int runlevel; vector edgelens, distlens; void make_cells_of_heptagon() { cells_of_heptagon.clear(); for(int i=0; imaster]; p1.localindex = isize(vc); vc.push_back(i); } } string status[5]; EX hrmap *base; EX euc::torus_config_full base_config; bool gridmaking; int rearrange_index; bool cell_sorting; EX int bitruncations_requested = 1; EX int bitruncations_performed = 0; int black_adjacent, white_three; void set_relmatrices(cellinfo& ci) { auto& all = base->allcells(); ci.relmatrices.clear(); for(auto c0: all) ci.relmatrices[c0] = calc_relative_matrix(c0, ci.owner, ci.p); } void rebase(cellinfo& ci) { cell *cx = ci.owner; virtualRebase(ci.owner, ci.p); if(ci.owner != cx) { printf("rebased %p to %p\n", hr::voidp(cx), hr::voidp(ci.owner)); set_relmatrices(ci); } } void compute_jpoints() { for(int i=0; i, int> bitruncated_id; for(int i=0; i newnei; for(int j=0; jallcells(); auto t = SDL_GetTicks(); while(SDL_GetTicks() < t + 250) switch(runlevel) { case 0: { cells.clear(); cells_of_heptagon.clear(); cellindex.clear(); if(0) if(cellcount <= isize(all) * 2) { for(auto h: all) { cells.emplace_back(); cellinfo& s = cells.back(); s.patterndir = -1; s.owner = h, s.p = xspinpush0(hrand(1000), .01); s.generation = 0; set_relmatrices(s); } } runlevel++; break; } case 1: { while(isize(cells) < cellcount) { if(SDL_GetTicks() > t + 250) { make_cells_of_heptagon(); status[0] = its(isize(cells)) + " cells"; return false; } cells.emplace_back(); cellinfo& s = cells.back(); s.patterndir = -1; ld bestval = 0; for(int j=0; j relmatrices; hyperpoint h = randomPointIn(c->type); for(auto c0: all) relmatrices[c0] = calc_relative_matrix(c0, c, h); ld mindist = 1e6; for(auto p: cells) { if(!relmatrices.count(p.owner)) continue; ld val = hdist(h, relmatrices[p.owner] * p.p); if(val < mindist) mindist = val; } if(mindist > bestval) bestval = mindist, s.owner = c, s.p = h, s.relmatrices = std::move(relmatrices); } } make_cells_of_heptagon(); cell_sorting = true; bitruncations_performed = 0; runlevel++; status[0] = "all " + its(isize(cells)) + " cells"; break; } case 2: { if(cell_sorting) sort(cells.begin(), cells.end(), [] (const cellinfo &s1, const cellinfo &s2) { return hdist0(s1.p) < hdist0(s2.p); }); make_cells_of_heptagon(); edgelens.clear(); distlens.clear(); int stats[16]; for(int k=0; k<16; k++) stats[k] = 0; compute_jpoints(); for(int i=0; i 8 || v< 3) { if(v < 3 || v >= 15) errors++; else toobig++; cells[i] = cells.back(); i--; cells.pop_back(); } } if(errors > 0) status[1] = XLAT("bad cells: %1", its(errors)); else status[1] = " "; if(toobig > 0) status[2] = XLAT("too many edges: %1", its(toobig)); else status[2] = " "; if(isize(cells) < cellcount*3/4) runlevel = 0; else if(isize(cells) < cellcount) runlevel = 1; else { rearrange_index = 0; runlevel++; } break; } case 4: { ld median = edgelens[isize(edgelens) / 2]; ld minedge = median * quality; status[3] = XLAT("median edge: %1 minimum: %2", fts(median), fts(edgelens[0])); if(!bitruncations_performed && edgelens[0] < minedge) { if(rearrange_index >= rearrange_max_attempts) { runlevel = 0; break; } int tooshort = rearrange(rearrange_index < rearrange_less, minedge); status[3] += XLAT(" (edges too short: %1)", its(tooshort)); runlevel = 2; rearrange_index++; break; } runlevel++; break; } case 5: { if(bitruncations_performed < bitruncations_requested) bitruncate(); else runlevel = 6; break; } case 6: { int notfound = 0; for(int i=0; i= isize(cells)) { runlevel = 0; return false; } bool found = false; for(int k=0; k < isize(cells[i1].vertices); k++) if(cells[i1].neid[k] == i) found = true, p1.spin[j] = k; if(!found) notfound++; } } if(notfound) { status[4] = XLAT("cells badly paired: %1", its(notfound)); runlevel = 0; break; } int heptas = 0; for(auto p: cells_of_heptagon) heptas++; if(heptas != isize(all)) { status[4] = XLAT("cells not covered: %1", its(isize(all) - heptas)); printf("heptas = %d\n", heptas); runlevel = 0; break; } int faredge = 0; for(int i=0; i dists[(d+S7-1) % S7]) d = (d + S7 - 1) % S7; s.patterndir = d; } break; } case 10: return false; } return false; } EX void compute_geometry() { if(IRREGULAR) { ld scale = sqrt(isize(cells_of_heptagon) * 1. / isize(cells)); cgi.crossf *= scale; cgi.hepvdist *= scale; cgi.rhexf *= scale; cgi.hexhexdist *= scale; cgi.hexvdist *= scale; cgi.base_distlimit = (cgi.base_distlimit + log(scale) / log(2.618)) / scale; if(cgi.base_distlimit > 25) cgi.base_distlimit = 25; } } bool draw_cell_schematics(cell *c, const shiftmatrix& V) { if(gridmaking) { heptagon *h = c->master; for(int i: cells_of_heptagon[h]) { auto& p = cells[i]; if(p.owner == c) { queuestr(V * rgpushxto0(p.p), .1, its(i), isize(p.vertices) > 8 ? 0xFF0000 : 0xFFFFFF); int N = isize(p.vertices); for(int j=0; jmaster->move(p.patterndir)->c7, c, p.p) * C0, 0x00FF00FF, 0); } } } return false; } #if HDR struct heptinfo { heptspin base; vector subcells; vector celldists[2]; }; #endif EX map periodmap; EX void link_to_base(heptagon *h, heptspin base) { // printf("linking %p to %p/%d\n", hr::voidp(h), hr::voidp(base.at), base.spin); auto &hi = periodmap[h]; hi.base = base; for(int k: cells_of_heptagon[base.at]) { cell *c = newCell(isize(cells[k].vertices), h); hi.subcells.push_back(c); cellindex[c] = k; } h->c7 = hi.subcells[0]; } EX void clear_links(heptagon *h) { auto& hi = periodmap[h]; for(cell *c: hi.subcells) { for(int i=0; itype; i++) if(c->move(i)) c->move(i)->move(c->c.spin(i)) = NULL; cellindex.erase(c); delete c; } h->c7 = NULL; periodmap.erase(h); } EX void link_start(heptagon *h) { link_to_base(h, heptspin(cells[0].owner->master, 0)); } EX void link_next(heptagon *parent, int d) { if(!periodmap.count(parent)) link_to_base(parent, heptspin(cells[0].owner->master, 0)); // printf("linking next: %p direction %d [s%d]\n", hr::voidp(parent), d, parent->c.spin(d)); auto *h = parent->move(d); heptspin hs = periodmap[parent].base + d + wstep - parent->c.spin(d); link_to_base(h, hs); } EX void may_link_next(heptagon *parent, int d) { if(!periodmap.count(parent->move(d))) link_next(parent, d); } EX void link_cell(cell *c, int d) { // printf("linking cell: %p direction %d\n", hr::voidp(c), d); int ci = cellindex[c]; auto& sc = cells[ci]; int ci2 = sc.neid[d]; auto& sc2 = cells[ci2]; heptagon *master2 = NULL; if(sc2.owner == sc.owner) { master2 = c->master; // printf("local\n"); } else { int dirs = 0; int os = periodmap[c->master].base.spin; for(int d=0; dmaster == sc.owner->master->modmove(os+d)) { heptspin hss(c->master, d); hss += wstep; master2 = hss.at; // printf("master2 is %p; base = %p; should be = %p\n", hr::voidp(master2), hr::voidp(periodmap[master2].base.at), hr::voidp(sc2.owner->master)); dirs++; } if(dirs != 1) { printf("dirs error\n"); exit(1); } } cell *c2 = periodmap[master2].subcells[sc2.localindex]; c->c.connect(d, c2, sc.spin[d], false); } int hdist(heptagon *h1, heptagon *h2) { if(h1 == h2) return 0; for(int i=0; imove(i) == h2) return 1; return 2; } // compute celldist or celldistalt for all the subcells of h. // We use the following algorithm: // - assume that everything is computed for all the adjacent heptagons of h which are closer to the origin // - consider h and its two neighbors which are in the same distance to the origin ('siblings') // - compute celldists for all the cells in these three heptagons, by bfs, based on the 'parent' heptagons adjacent to h // - record the computed distances for h, but not for its siblings static constexpr int NODISTANCE = 2000000000; map last_on_horocycle; void compute_horocycle(heptagon *); void compute_distances(heptagon *h, bool alts) { /* if(alts) printf("[%p] compute_distances %p\n", hr::voidp(h->alt->alt), hr::voidp(h)); printf("neighbors:"); for(int i=0; ialt->alt]) last_on_horocycle[h->alt->alt] = h; if(h->alt->alt->s != hsOrigin) while(h->alt->distance <= last_on_horocycle[h->alt->alt]->alt->distance) compute_horocycle(h->alt->alt); } auto dm4 = [alts, h] (heptagon *h1) -> unsigned { if(!alts) return h1->dm4; if(alts && !h1->alt) return 100; // error if(alts && h1->alt->alt != h->alt->alt) return 100; // error return h1->alt->dm4; }; unsigned cdm = dm4(h), pdm = (cdm-1)&3; vector hs; hs.push_back(h); for(int i=0; imove(i)); vector*> to_clear; for(auto hx: hs) { auto &hi = periodmap[hx]; int ct = isize(hi.subcells); auto& cd = hi.celldists[alts]; if(cd.empty() && hx != h) to_clear.push_back(&cd); cd.resize(ct, NODISTANCE); if(h == hx && (alts ? h->alt->s == hsOrigin : h->s == hsOrigin)) cd[0] = 0; } while(true) { bool changed = false; for(auto hx: hs) { auto& hi = periodmap[hx]; auto& cd = hi.celldists[alts]; for(int i=0; imaster), cdm, pdm) && hdist(h, c2->master) < 2) { int d = irr::celldist(c2, alts) + 1; if(d < cd[i]) cd[i] = d, changed = true; } } if(!changed) break; } /* for(auto hx: hs) { auto& hi = periodmap[hx]; auto& cd = hi.celldists[alts]; // for(int i: cd) if(i == NODISTANCE) printf("distances not computed\n"); } */ for(auto x: to_clear) x->clear(); // for(int i: cd) printf(" %d", i); printf("\n"); } void erase_alt(heptagon *alt) { last_on_horocycle.erase(alt); } void compute_horocycle(heptagon *alt) { heptagon *master = last_on_horocycle[alt]; // printf("computing horocycle, master distance = %d [M=%p, A=%p]\n", master->alt->distance, hr::voidp(master), hr::voidp(alt)); static constexpr int LOOKUP = 16; set hs[LOOKUP]; hs[0].insert(master); set region; for(int i=0; iextend_altmap(h); for(int j=0; jmove(j)->alt->alt != master->alt->alt) continue; region.insert(h->move(j)); if(h->move(j)->alt->distance < h->alt->distance) hs[i+1].insert(h->move(j)); } } if(hs[i+1].empty()) { printf("error: hs[%d] not found\n", i+1); exit(1); } } /* printf("[%p] compute_horocycle "); for(int i=0; i ", isize(hs[i])); printf("%p\n", isize(hs[LOOKUP-1])); */ map xdist; vector xdqueue; cell *orig = periodmap[*(hs[LOOKUP-1].begin())].subcells[0]; xdist[orig] = 0; xdqueue.push_back(orig); for(int i=0; imaster)) { xdist[c1] = xdist[xdqueue[i]] + 1; xdqueue.push_back(c1); } } int delta = NODISTANCE; for(int i=0; imove(i); if(h->alt->alt != master->alt->alt) continue; heptinfo& hi = periodmap[h]; if(!isize(hi.celldists[1])) continue; for(int c=0; calt->distance - xdist[periodmap[master].subcells[0]]; // printf("delta not found, set to %d\n", delta); } // printf("using delta = %d\n", delta); for(int i=0; imove(j)]; hi.celldists[1].resize(isize(hi.subcells)); for(int c=0; cmaster; auto &hi = periodmap[master]; /* if(alts && master->alt->alt->s != hsOrigin && isize(hi.celldists[alts]) == 0) { int doalts = 0; for(int i=0; imove(i)->alt == master->alt->move[0]) { doalts = 1; if(periodmap[master->move(i)].celldists[true].empty()) { compute_horocycle(master); doalts = 2; } } if(doalts == 0) { currentmap->extend_altmap(master); for(int i=0; imove(i)->alt == master->alt->move[0] && periodmap[master->move(i)].celldists[true].empty()) compute_horocycle(master); } } */ if(isize(hi.celldists[alts]) == 0) compute_distances(master, alts); return hi.celldists[alts][cells[cellindex[c]].localindex]; } eGeometry orig_geometry, base_geometry; void start_game_on_created_map() { popScreen(); for(hrmap *& hm : allmaps) if(hm == base) hm = NULL; stop_game(); geometry = orig_geometry; variation = eVariation::irregular; irrid++; gridmaking = false; start_game(); } bool save_map(const string& fname) { fhstream f(fname, "wt"); if(!f.f) return false; auto& all = base->allcells(); int origcells = 0; for(cellinfo& ci: cells) if(ci.generation == 0) origcells++; println(f, spaced(int(geometry), isize(all), origcells)); for(auto h: all) { origcells = 0; for(auto i: cells_of_heptagon[h->master]) if(cells[i].generation == 0) origcells++; println(f, origcells); for(auto i: cells_of_heptagon[h->master]) if(cells[i].generation == 0) { auto &ci = cells[i]; println(f, spaced(ci.p[0], ci.p[1], ci.p[LDIM])); } } return true; } vector float_order; EX void save_map_bin(hstream& f) { if(!base) { f.write(-1); return; } auto& all = base->allcells(); int origcells = 0; for(cellinfo& ci: cells) if(ci.generation == 0) origcells++; f.write (base_geometry); f.write (isize(all)); f.write (origcells); int foi = 0; auto check_float_order = [&] (ld x) { if(foi >= isize(float_order)) { float_order.push_back(x); f.write(x); } else if(abs(float_order[foi] - x) > 1e-6) { println(hlog, float_order[foi], " vs ", x, " : abs difference is ", abs(float_order[foi] - x)); float_order[foi] = x; } f.write(float_order[foi++]); }; for(auto h: all) { origcells = 0; for(auto i: cells_of_heptagon[h->master]) if(cells[i].generation == 0) origcells++; f.write (origcells); for(auto i: cells_of_heptagon[h->master]) if(cells[i].generation == 0) { auto &ci = cells[i]; check_float_order(ci.p[0]); check_float_order(ci.p[1]); check_float_order(ci.p[LDIM]); } } } bool load_map(const string &fname) { fhstream f(fname, "rt"); if(!f.f) return false; auto& all = base->allcells(); int g, sa; scan(f, g, sa, cellcount); if(sa != isize(all) || g != geometry) { printf("bad parameters\n"); addMessage(XLAT("bad format or bad map geometry")); return false; } density = cellcount * 1. / isize(all); cells.clear(); for(auto h: all) { int q = 0; scan(f, q); if(q < 0 || q > cellcount) { runlevel = 0; return false; } while(q--) { cells.emplace_back(); cellinfo& s = cells.back(); s.patterndir = -1; double a, b, c; scan(f, a, b, c); s.p = hpxyz(a, b, c); for(auto c0: all) s.relmatrices[c0] = calc_relative_matrix(c0, h, s.p); s.owner = h; } } make_cells_of_heptagon(); runlevel = 2; return true; } EX void load_map_bin(hstream& f) { auto& all = base->allcells(); eGeometry g = (eGeometry) f.get(); if(int(g) == -1) return; int sa = f.get(); cellcount = f.get(); if(g != geometry) throw hstream_exception("bad geometry"); if(sa != isize(all)) throw hstream_exception("bad size of all"); density = cellcount * 1. / isize(all); cells.clear(); float_order.clear(); for(auto h: all) { int q = f.get(); if(q < 0 || q > cellcount) throw hstream_exception("incorrect quantity"); while(q--) { cells.emplace_back(); cellinfo& s = cells.back(); s.patterndir = -1; double a, b, c; a = f.get(); b = f.get(); c = f.get(); float_order.push_back(a); float_order.push_back(b); float_order.push_back(c); s.p = hpxyz(a, b, c); s.p = normalize(s.p); for(auto c0: all) s.relmatrices[c0] = calc_relative_matrix(c0, h, s.p); s.owner = h; } } make_cells_of_heptagon(); runlevel = 2; } EX void load_map_full(hstream& f) { init(); load_map_bin(f); while(runlevel < 10) step(1000); start_game_on_created_map(); } void cancel_map_creation() { base = NULL; runlevel = 0; popScreen(); gridmaking = false; stop_game(); geometry = orig_geometry; start_game(); } string irrmapfile = "irregularmap.txt"; string irrhelp = "This option creates irregular grids to play the game on. " "Currently rather slow algorithms are used, " "so not recommended with too high density or " "with too large periodic base geometry. " "For technical reasons, the density cannot be too small."; bool too_small_euclidean() { for(cell *c: base->allcells()) forCellIdEx(c1, i1, c) forCellIdEx(c2, i2, c) if(i1 != i2 && c1 == c2) return true; return false; } void show_gridmaker() { cmode = sm::SIDE | sm::MAYDARK; gamescreen(); dialog::init(XLAT("irregular grid")); dialog::addSelItem(XLAT("density"), fts(density), 'd'); dialog::add_action([] { dialog::editNumber(density, 1, 10, .1, 4, XLAT("density"), XLAT(irrhelp)); dialog::get_di().reaction = [] () { int s = cellcount; if(density < 1) density = 1; cellcount = int(isize(currentmap->allcells()) * density + .5); println(hlog, "density = ", fts(density), " cellcount = ", cellcount); if(cellcount > s) runlevel = 1; if(cellcount < s) runlevel = 0; }; }); dialog::addSelItem(XLAT("min edge to median"), fts(quality), 'q'); dialog::add_action([] { dialog::editNumber(quality, 0, 1, .05, .2, XLAT("quality"), XLAT( "The smallest allowed ratio of edge length to median edge length. " "Tilings with low values are easier to generate, but tend to be more ugly." )); dialog::get_di().reaction = [] () { println(hlog, "quality = ", density); if(runlevel > 4) runlevel = 4; }; }); dialog::addBreak(100); for(int i=0; i<5; i++) dialog::addInfo(status[i]); dialog::addBreak(100); dialog::addSelItem(XLAT("activate"), runlevel == 10 ? XLAT("ready") : XLAT("wait..."), 'f'); if(runlevel == 10) dialog::add_action(start_game_on_created_map); dialog::addItem(XLAT("cancel"), 'c'); dialog::add_action(cancel_map_creation); dialog::addItem(XLAT("save"), 's'); dialog::add_action([] () { dialog::openFileDialog(irrmapfile, XLAT("irregular to save:"), ".txt", [] () { if(save_map(irrmapfile)) { addMessage(XLAT("Map saved to %1", irrmapfile)); return true; } else { addMessage(XLAT("Failed to save map to %1", irrmapfile)); return false; } }); }); dialog::addItem(XLAT("load"), 'l'); dialog::add_action([] () { dialog::openFileDialog(irrmapfile, XLAT("irregular to load:"), ".txt", [] () { if(load_map(irrmapfile)) { addMessage(XLAT("Map loaded from %1", irrmapfile)); return true; } else { addMessage(XLAT("Failed to load map from %1", irrmapfile)); return false; } }); }); dialog::addSelItem(XLAT("bitruncation count"), its(bitruncations_requested), 'b'); dialog::add_action([] () { dialog::editNumber(bitruncations_requested, 0, 5, 1, 1, XLAT("bitruncation const"), XLAT("Bitruncation introduces some regularity, allowing more sophisticated floor tilings and textures.")); dialog::get_di().reaction = [] () { if(bitruncations_requested > bitruncations_performed && runlevel > 5) runlevel = 5; if(bitruncations_requested < bitruncations_performed) runlevel = 0; }; }); if(too_small_euclidean()) dialog::addInfo(XLAT("too small period -- irregular tiling generation fails")); dialog::addItem(XLAT("reset"), 'r'); dialog::add_action([] () { runlevel = 0; }); dialog::addHelp(); dialog::display(); keyhandler = [] (int sym, int uni) { handlePanning(sym, uni); if(uni == 'h' || sym == SDLK_F1) gotoHelp(XLAT(irrhelp)); dialog::handleNavigation(sym, uni); // no exit }; } EX void init() { stop_game(); orig_geometry = geometry; switch(geometry) { case gNormal: geometry = gKleinQuartic; break; case gOctagon: geometry = gBolza2; break; default: ; break; } base_geometry = geometry; variation = eVariation::pure; start_game(); if(base) delete base; base = currentmap; base_config = euc::eu; cellcount = int(isize(base->allcells()) * density + .5); gridmaking = true; drawthemap(); } EX void visual_creator() { init(); pushScreen(show_gridmaker); runlevel = 0; gridmaking = true; } EX void auto_creator() { variation = eVariation::pure; int cc = cellcount; bitruncations_requested = bitruncations_performed; visual_creator(); cellcount = cc; density = cc * 1. / isize(base->allcells()); printf("Creating the irregular map automatically...\n"); while(runlevel < 10) step(1000); start_game_on_created_map(); } #if CAP_COMMANDLINE int readArgs() { using namespace arg; if(0) ; else if(argis("-irrvis")) { PHASE(3); restart_game(); visual_creator(); showstartmenu = false; } else if(argis("-irrdens")) { PHASEFROM(2); shift_arg_formula(density); } else if(argis("-irrb")) { PHASEFROM(2); shift(); bitruncations_requested = argi(); } else if(argis("-irrq")) { PHASEFROM(2); shift_arg_formula(quality); } else if(argis("-irrload")) { PHASE(3); restart_game(); init(); showstartmenu = false; shift(); load_map(args()); while(runlevel < 10) step(1000); start_game_on_created_map(); } else return 1; return 0; } #endif EX unsigned char density_code() { if(isize(cells) < 128) return isize(cells); else { int t = 127, a = isize(cells); while(a > 127) a = a * 9/10, t++; return t; } } EX bool pseudohept(cell* c) { return cells[cellindex[c]].is_pseudohept; } EX bool ctof(cell* c) { return cells[cellindex[c]].patterndir == -1; } EX bool supports(eGeometry g) { if(g == gEuclid || g == gEuclidSquare) return ginf[g].flags & qCLOSED; return among(g, gNormal, gKleinQuartic, gOctagon, gBolza2, gFieldQuotient, gSphere, gSmallSphere, gTinySphere); } EX array get_masters(cell *c) { int d = cells[cellindex[c]].patterndir; heptspin s = periodmap[c->master].base; heptspin s0 = heptspin(c->master, 0) + (d - s.spin); return make_array(s0.at, (s0 + wstep).at, (s0 + 1 + wstep).at); } EX void swap_vertices() { for(auto& c: cells) { swappoint(c.p); swapmatrix(c.pusher); swapmatrix(c.rpusher); for(auto& jp: c.jpoints) swappoint(jp); for(auto& rm: c.relmatrices) swapmatrix(rm.second); for(auto& v: c.vertices) swappoint(v); } } auto hook = #if CAP_COMMANDLINE addHook(hooks_args, 100, readArgs) + #endif #if MAXMDIM >= 4 addHook(hooks_swapdim, 100, swap_vertices) + #endif addHook(hooks_drawcell, 100, draw_cell_schematics) + addHook(shmup::hooks_turn, 100, step); #endif }} /* if(mouseover && !ctof(mouseover)) { for(auto h: gp::get_masters(mouseover)) queueline(ggmatrix(h->c7)*C0, shmup::ggmatrix(mouseover)*C0, 0xFFFFFFFF); } */