namespace nilrider { const int steps_per_block = 16; const int texture_density = 64; void level::init_textures() { create_castle(); int tY = isize(map_tiles); int tX = isize(map_tiles[0]); int ttY = next_p2(tY); int ttX = next_p2(tX); transmatrix T = gpushxto0(new_levellines_for); for(int which: {0, 1, 2}) { bool stepped = which == 1; bool levels = which == 2; auto& target = stepped ? unil_texture_stepped : levels ? unil_texture_levels : unil_texture; if(target && !(levels && levellines_for != new_levellines_for)) continue; bool regen = !target; if(regen) target = new texture::texture_data; auto& tex = *target; if(regen) { tex.twidth = tex.tx = ttX * texture_density; tex.theight = tex.ty = ttY * texture_density; tex.stretched = false; tex.strx = tex.tx; tex.stry = tex.ty; tex.base_x = 0; tex.base_y = 0; tex.whitetexture(); } auto getpix = [&] (int x, int y) { int px = x / pixel_per_block; int py = y / pixel_per_block; if(px < 0 || py < 0 || px >= tX || py >= tY) return '!'; char bmch = map_tiles[py][px]; if(bmch == '!') return '!'; return submaps[bmch][y % pixel_per_block][x % pixel_per_block]; }; int subpixels = texture_density / pixel_per_block; int stepdiv = texture_density / steps_per_block; for(int y=0; y 0) col = gradient(col, 0xFFFF0000, 0, z - floor(z), 4); if(z < 0) col = gradient(col, 0xFF0000FF, 0, -z - floor(-z), 4); } } else { int mx = x % stepdiv; int my = y % stepdiv; int mx0, mx1, my0, my1; if(mx < stepdiv/2) { mx0 = mx1 = 2*mx; } else {mx0 = stepdiv-2; mx1=stepdiv; } if(my < stepdiv/2) { my0 = my1 = 2*my; } else {my0 = stepdiv-2; my1=stepdiv; } int ax = (x/stepdiv) * stepdiv; int ay = (y/stepdiv) * stepdiv; ld maxh = -HUGE_VAL; char c = '!'; for(int i=0; i<4; i++) { hyperpoint h = mappt(ax + ((mx0 != mx1) ? (i&1?3:1)*stepdiv/2 : mx0), ay + ((my0 != my1) ? (i&2?3:1)*stepdiv/2 : my0), texture_density); if(h[2] > maxh) { c = getpix(((i&1?mx1:mx0) + ax) / subpixels, ((i&2?my1:my0)+ ay) / subpixels); if(c != '!') maxh = h[2]; } } col = bcols[c]; if(mx0 != mx1) col = gradient(col, 0xFF000000, 0, .1, 1); if(my0 != my1) col = gradient(col, 0xFF000000, 0, .2, 1); } tex.get_texture_pixel(x, y) = col; tex.get_texture_pixel(x, y) |= 0xFF000000; if(!levels) tex.get_texture_pixel(x, y) ^= hrand(0x1000000) & 0xF0F0F; } tex.loadTextureGL(); } levellines_for = new_levellines_for; } void level::init_shapes() { check_cgi(); string s = "nillevel-" + name; if(cgi.ext.count(s)) return; cgi.ext[s] = nullptr; int tY = isize(map_tiles); int tX = isize(map_tiles[0]); int ttY = next_p2(tY); int ttX = next_p2(tX); for(int s=0; s<3; s++) { if(euclid && s != 1) continue; if(nil && s == 1) continue; cgi.bshape(s == 0 ? shFloor : s == 1 ? shPlanFloor : shStepFloor, PPR::WALL); shFloor.flags |= POLY_TRIANGLES; shPlanFloor.flags |= POLY_TRIANGLES; shStepFloor.flags |= POLY_TRIANGLES; int prec = 16; if(s == 2) prec *= 4; int cdiv = prec / steps_per_block; prec >>= reduce_quality; bool need_uniltinf = uniltinf.tvertices.empty(); auto pt = [&] (int x, int y, int qx, int qy) { if(need_uniltinf) uniltinf.tvertices.push_back(glhr::makevertex(x * 1. / ttX / prec, y * 1. / ttY / prec, 0)); if(s == 2) { ld ax = x, ay = y; if(qx) { if(x % cdiv == cdiv/2+1) qx = 0; } else { ax -= (x % cdiv); ax += .5; ax += (x % cdiv) * (cdiv/2-1.) / (cdiv/2); } if(qy) { if(y % cdiv == cdiv/2+1) qy = 0; } else { ay -= (y % cdiv); ay += .5; ay += (y % cdiv) * (cdiv/2-1.) / (cdiv/2); } uniltinf_stepped.tvertices.push_back(glhr::makevertex((ax+qx) / ttX / prec, (ay+qy) / ttY / prec, 0)); } hyperpoint h = mappt(x, y, prec); if(s == 2) { if(x % cdiv == cdiv/2+1) x += cdiv/2 - 1; if(y % cdiv == cdiv/2+1) y += cdiv/2 - 1; int gx = x/cdiv*cdiv + (x%cdiv) * 2; int gy = y/cdiv*cdiv + (y%cdiv) * 2; hyperpoint gh = mappt(gx, gy, prec); int ax = x/cdiv*cdiv + cdiv/2; int ay = y/cdiv*cdiv + cdiv/2; hyperpoint ah = mappt(ax, ay, prec); gh[0] = gh[0] - ah[0]; gh[1] = gh[1] - ah[1]; gh[2] = 0; gh = sym_to_used(gh); h = rgpushxto0(ah) * gh; ld delta = 0; // make sure steps are below the actual level for(int z=0; z<4; z++) { int zx = x/cdiv*cdiv + ((z&1)?cdiv:0); int zy = y/cdiv*cdiv + ((z&2)?cdiv:0); hyperpoint zh = mappt(zx, zy, prec); hyperpoint uh; uh[0] = zh[0] - ah[0]; uh[1] = zh[1] - ah[1]; uh[2] = 0; uh[3] = 1; uh = sym_to_used(uh); uh = rgpushxto0(ah) * uh; delta = max(delta, uh[2] - zh[2]); } h[2] -= delta; } real_minx = min(real_minx, h[0]); real_maxx = max(real_maxx, h[0]); real_miny = min(real_miny, h[1]); real_maxy = max(real_maxy, h[1]); if(s == 1) h[2] = h[3] = 1; cgi.hpcpush(h); }; for(int y=0; y (cdiv/2)) qx+=2; if(y % cdiv == (cdiv/2)) qy++; if(y % cdiv > (cdiv/2)) qy+=2; if(qx + qy > 1) continue; } pt(x, y, qx, qy); pt(x, y+1, qx, qy); pt(x+1, y, qx, qy); pt(x+1, y+1, qx, qy); pt(x+1, y, qx, qy); pt(x, y+1, qx, qy); } cgi.finishshape(); } if(1) { cgi.bshape(shField, PPR::WALL); shField.flags |= POLY_TRIANGLES; auto pt = [&] (hyperpoint p) { hyperpoint h = mappt(p[0], p[1], 16); h[2] += p[2]; cgi.hpcpush(h); // cgi.hpcpush(hyperpoint(rand() % 10 - 5, rand() % 10 - 5, rand() % 10 - 5, 1)); }; for(int y=0; yname = l->name + "#" + its(y); l->surface = [y] (hyperpoint h) { return rot_plane(h) - 3 * y; }; l->map_tiles[1][1] = '*'; l->sublevels = {}; sublevels.push_back(l); } } initialized = true; check_cgi(); real_minx = HUGE_VAL; real_miny = HUGE_VAL; real_maxx = -HUGE_VAL; real_maxy = -HUGE_VAL; if(flags & nrlPolar) scale = 1; else scale = abs(maxx - minx) / isize(map_tiles[0]); println(hlog, "SCALE IS ", this->scale); levellines_for = new_levellines_for = Hypc; if(1) { int tY = isize(map_tiles); int tX = isize(map_tiles[0]); for(int y=0; y lop1(loaded_or_planned, true); dynamicval lop2(planning_mode, false); if(c.tick(this) == b) break; start.heading_angle -= degree; steps++; if(steps > 1080) break; } if(steps > 1080) println(hlog, "warning: could not find a correct start.heading_angle"); if(flags & nrlOrder) { sort(triangles.begin(), triangles.end(), [this] (triangledata a, triangledata b) { return atan2(spin(120._deg)*(a.where - start.where)) < atan2(spin(120._deg)*(b.where - start.where)); }); for(auto t: triangles) println(hlog, t.where); } init_plan(); for(auto s: sublevels) { s->init(); for(auto& t: s->triangles) triangles.push_back(t); s->triangles.clear(); } } xy_float level::get_xy_f(hyperpoint h) { if(flags & nrlPolar) { tie(h[0], h[1]) = make_pair(atan2(h[0], h[1]), hypot(h[0], h[1])); ld bar = (minx + maxx) / 2; cyclefix(h[0], bar); } int tY = isize(map_tiles); int tX = isize(map_tiles[0]); ld rtx = ilerp(minx, maxx, h[0]) * tX; ld rty = ilerp(miny, maxy, h[1]) * tY; return {rtx, rty}; } char level::mapchar(xy_int p) { auto x = p.first; auto y = p.second; int tY = isize(map_tiles); int tX = isize(map_tiles[0]); if(x < 0 || y < 0 || x >= tX || y >= tY) return '!'; return map_tiles[y][x]; } /* convert ASCII map coordinates to Heisenberg coordinates */ hyperpoint level::mappt(ld x, ld y, int s) { int tY = isize(map_tiles); int tX = isize(map_tiles[0]); x /= s; y /= s; hyperpoint h; h[0] = lerp(minx, maxx, x / tX); h[1] = lerp(miny, maxy, y / tY); if(flags & nrlPolar) tie(h[0], h[1]) = make_pair(h[1] * sin(h[0]), h[1] * cos(h[0])); h[2] = surface(h); h[3] = 1; return h; }; void level::init_plan() { plan.emplace_back(start.where, hpxy(cos(start.heading_angle + 90._deg) * 2, sin(start.heading_angle + 90._deg) * 2)); current = start; } ld level::safe_alt(hyperpoint h, ld mul, ld mulx) { ld maxv = 0; for(int x: {-1, 0, 1}) for(int y: {-1, 0, 1}) { hyperpoint c = sym_to_used(point31(x*.5*scale*mulx, y*.5*scale*mulx, 0)); hyperpoint j = rgpushxto0(h) * c; maxv = max(maxv, mul * (surface(j) - j[2])); } return maxv; } bool stepped_display; int nilrider_tempo = 562; int nilrider_shift = 2633; void level::draw_level(const shiftmatrix& V) { int id = 0; init_statues(); init_shapes(); init_textures(); for(auto& t: triangles) { bool gotit = current.collected_triangles & Flag(id); id++; if(!gotit) { for(int i=0; i<6; i++) { ld tim = current.timer * 1000; tim -= nilrider_shift; tim /= nilrider_tempo; transmatrix spin = Id; if(nilv::model_used == 0) spin = cspin(0, 1, tim * M_PI / 6); tim = abs(0.2 * sin(tim * M_PI)); auto &poly = queuepoly(V * rgpushxto0(t.where) * cpush(2, tim) * spin, shMini[i], t.colors[i]); poly.tinf = &floor_texture_vertices[cgi.shFloor.id]; ensure_vertex_number(*poly.tinf, poly.cnt); } } } if(true) { auto& poly = queuepoly(V, shCastle, 0xC02020FF); poly.tinf = &castle_tinf; castle_tinf.texture_id = castle_texture->textureid; } for(auto st: statues) queuepoly(V * st.T, *st.shape, st.color); queuepoly(V, shField, 0xFFFF00FF); if(!stepped_display) { auto& poly = queuepoly(V, shFloor, 0xFFFFFFFF); // 0xFFFFFFFF); poly.tinf = &uniltinf; uniltinf.texture_id = unil_texture->textureid; } else { auto& poly = queuepoly(V, shStepFloor, 0xFFFFFFFF); // 0xFFFFFFFF); poly.tinf = &uniltinf_stepped; uniltinf_stepped.texture_id = unil_texture_stepped->textureid; } } void level::draw_level_rec(const shiftmatrix& V) { draw_level(V); for(auto sub: sublevels) sub->draw_level_rec(V); } void cleanup_texture(texture::texture_data*& d) { if(d) delete d; d = nullptr; } void cleanup_textures() { for(auto l: all_levels) { cleanup_texture(l->unil_texture); cleanup_texture(l->unil_texture_stepped); } println(hlog, "CLEANUP texture"); cleanup_texture(castle_texture); } void load_level(const string& fname, bool init) { fhstream f(fname, "r"); if(!f.f) throw hr_exception("could not open file "); level lev("Untitled", '1', nrlUserCreated, "", -1, 1, 1, -1, {}, 0, 0, {}, rot_plane, { goal{0x40FF40, "Collect all the triangles", basic_check(999, 999), "", ""} }); lev.filename = fname; level *csub = &lev; string s; while(true) { string s = scanline_noblank(f); if(s == "" && feof(f.f)) break; if(s == "") continue; if(s[0] == '#') continue; auto pos = s.find(' '); if(pos == string::npos) throw hr_exception("incorrect format, a line without space"); string cmd = s.substr(0, pos); string param = s.substr(pos + 1); if(cmd == "NAME") { csub->name = param; } else if(cmd == "DESC") { if(csub->longdesc != "") csub->longdesc += "\n\n"; csub->longdesc += param; } else if(cmd == "BOUNDS") { if(sscanf(param.c_str(), "%lf%lf%lf%lf", &csub->minx, &csub->miny, &csub->maxx, &csub->maxy) != 4) throw hr_exception("incorrect BOUNDS line"); } else if(cmd == "START") { if(sscanf(param.c_str(), "%lf%lf", &csub->startx, &csub->starty) != 2) throw hr_exception("incorrect START line"); } else if(cmd == "MAP") csub->map_tiles.push_back(param); else if(cmd == "PIXEL") { char label; color_t col; if(sscanf(param.c_str(), "%c%x", &label, &col) != 2) throw hr_exception("incorrect PIXEL line"); bcols[label] = col; } else if(cmd == "BLOCK") { if(param.size() != 1) throw hr_exception("incorrect BLOCK line"); auto& submap = submaps[param[0]]; for(int y=0; ysurface = rot_plane; else if(param == "heisenberg") csub->surface = f_heisenberg0; else if(param == "well") csub->surface = f_rot_well; else if(param == "longtrack") csub->surface = long_x; else csub->surface = [param] (hyperpoint h) -> ld { exp_parser ep; ep.extra_params["x"] = h[0]; ep.extra_params["y"] = h[1]; ep.s = param; try { return ep.rparse(); } catch(hr_parse_exception&) { return 0; } }; } else if(cmd == "LAYER") { csub = new level(lev); csub->name = param; lev.sublevels.push_back(csub); csub->map_tiles = {}; } } if(lev.startx < 0 || lev.starty < 0 || lev.starty >= isize(lev.map_tiles) || lev.startx >= isize(lev.map_tiles[0])) throw hr_exception("start position incorrect"); auto all = lev.gen_layer_list(); for(auto& l: all) { if(l->map_tiles.empty()) throw hr_exception("no map"); if(l->map_tiles[0].empty()) throw hr_exception("empty strings in map"); for(auto& s: l->map_tiles) if(isize(s) != isize(l->map_tiles[0])) throw hr_exception("map is not rectangular"); if(l->minx == l->maxx) throw hr_exception("bad map X dimensions"); if(l->miny == l->maxy) throw hr_exception("bad map Y dimensions"); } for(auto& l: all_levels) if(l->name == lev.name) { if(l->flags & nrlUserCreated) { cgi.ext.erase("nillevel-" + l->name); swap(*l, lev); curlev = l; if(on) l->init(); return; } else throw hr_exception("cannot use the same name as an official level"); } if(init) { curlev = new level(lev); all_levels.emplace_back(curlev); if(on) curlev->init(); } } }