package mindustry.graphics; import arc.*; import arc.func.*; import arc.fx.*; import arc.fx.filters.*; import arc.graphics.*; import arc.graphics.Pixmap.*; import arc.graphics.g2d.*; import arc.graphics.g3d.*; import arc.graphics.gl.GLVersion.*; import arc.input.*; import arc.math.*; import arc.math.geom.*; import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; import mindustry.*; import mindustry.core.*; import mindustry.graphics.g3d.*; import static arc.Core.*; public class LoadRenderer implements Disposable{ private static final Color color = new Color(Pal.accent).lerp(Color.black, 0.5f); private static final Color colorRed = Pal.breakInvalid.cpy().lerp(Color.black, 0.3f); private static final String red = "[#" + colorRed + "]"; private static final String orange = "[#" + color + "]"; private static final FloatSeq floats = new FloatSeq(); private static final boolean preview = false; private float testprogress = 0f; private StringBuilder assetText = new StringBuilder(); private Bar[] bars; private Mesh mesh = MeshBuilder.buildPlanetGrid(PlanetGrid.create(2), colorRed, 1f); private Camera3D cam = new Camera3D(); private int lastLength = -1; private FxProcessor fx; private WindowedMean renderTimes = new WindowedMean(20); private BloomFilter bloom; private boolean renderStencil = true; private long lastFrameTime; { //some systems don't support rgba8888 w/ a stencil buffer try{ fx = new FxProcessor(Format.rgba8888, 2, 2, false, true); }catch(Exception e){ try{ fx = new FxProcessor(Format.rgb565, 2, 2, false, true); }catch(Exception awful){ renderStencil = false; fx = new FxProcessor(Format.rgba8888, 2, 2, false, false); } } //vignetting is probably too much //fx.addEffect(new VignettingFilter(false)); fx.addEffect(bloom = new BloomFilter()); bars = new Bar[]{ new Bar("s_proc#", OS.cores / 16f, OS.cores < 4), new Bar("c_aprog", () -> assets != null, () -> assets.getProgress(), () -> false), new Bar("g_vtype", graphics.getGLVersion().type == GlType.GLES ? 0.5f : 1f, graphics.getGLVersion().type == GlType.GLES), new Bar("s_mem#", () -> true, () -> Core.app.getJavaHeap() / 1024f / 1024f / 200f, () -> Core.app.getJavaHeap() > 1024 * 1024 * 110), new Bar("v_ver#", () -> Version.build != 0, () -> Version.build == -1 ? 0.3f : (Version.build - 103f) / 10f, () -> !Version.modifier.equals("release")), new Bar("s_osv", OS.isWindows ? 0.35f : OS.isLinux ? 0.9f : OS.isMac ? 0.5f : 0.2f, OS.isMac), new Bar("v_worlds#", () -> Vars.control != null && Vars.control.saves != null, () -> Vars.control.saves.getSaveSlots().size / 30f, () -> Vars.control.saves.getSaveSlots().size > 30), new Bar("c_datas#", () -> settings.keySize() > 0, () -> settings.keySize() / 50f, () -> settings.keySize() > 20), new Bar("v_alterc", () -> Vars.mods != null, () -> (Vars.mods.list().size + 1) / 6f, () -> Vars.mods.list().size > 0), new Bar("g_vcomp#", (graphics.getGLVersion().majorVersion + graphics.getGLVersion().minorVersion / 10f) / 4.6f, !graphics.getGLVersion().atLeast(3, 2)), }; } @Override public void dispose(){ mesh.dispose(); fx.dispose(); bloom.dispose(); } public void draw(){ if(!preview){ if(lastFrameTime == 0){ lastFrameTime = Time.millis(); } float timespace = Time.timeSinceMillis(lastFrameTime) / 1000f; renderTimes.add(timespace); lastFrameTime = Time.millis(); } if(fx.getWidth() != graphics.getWidth() || fx.getHeight() != graphics.getHeight()){ fx.resize(graphics.getWidth(), graphics.getHeight()); } fx.begin(); if(assets.getLoadedAssets() != lastLength){ assetText.setLength(0); for(String name : assets.getAssetNames()){ boolean isRed = name.toLowerCase().contains("mod") || assets.getAssetType(name).getSimpleName().toLowerCase().contains("mod") || name.contains("preview"); assetText .append(isRed ? red : orange) .append(name.replace(OS.username, "<>").replace("/", "::")).append(red).append("::[]") .append(assets.getAssetType(name).getSimpleName()).append("\n"); } lastLength = assets.getLoadedAssets(); } Core.graphics.clear(Color.black); float w = Core.graphics.getWidth(), h = Core.graphics.getHeight(), s = Scl.scl(); //s = 2f; Draw.proj().setOrtho(0, 0, Core.graphics.getWidth(), Core.graphics.getHeight()); int lightVerts = 20; float lightRad = Math.max(w, h)*0.6f; float stroke = 5f * s; //light Fill.light(w/2, h/2, lightVerts, lightRad, Tmp.c1.set(color).a(0.15f), Color.clear); float space = s*(60); float progress = assets.getProgress(); int dotw = (int)(w / space)/2 + 1; int doth = (int)(h / space)/2 + 1; //preview : no frametime if(preview){ testprogress += Time.delta / (60f * 3); progress = testprogress; if(input.keyTap(KeyCode.space)){ testprogress = 0; } } //square matrix Draw.color(Pal.accent, Color.black, 0.9f); Lines.stroke(stroke); for(int cx = -dotw; cx <= dotw; cx++){ for(int cy = -doth; cy <= doth; cy++){ float dx = cx * space + w/2f, dy = cy * space + h/2f; Lines.poly(dx, dy, 4, space/2f); } } Draw.flush(); float aspect = 1.94f; Vec2 size = Scaling.fit.apply(graphics.getWidth(), graphics.getWidth() / aspect, graphics.getWidth(), graphics.getHeight()); int viewportWidth = (int)size.x, viewportHeight = (int)size.y, viewportX = (int)(graphics.getWidth()/2f - size.x/2f), viewportY = (int)(graphics.getHeight()/2f - size.y/2f); //portrait? no viewport if(graphics.getHeight() > graphics.getWidth()){ viewportHeight = graphics.getHeight(); viewportWidth = graphics.getWidth(); viewportX = viewportY = 0; } w = viewportWidth; h = viewportHeight; Gl.viewport(viewportX, viewportY, viewportWidth, viewportHeight); Draw.proj().setOrtho(0, 0, viewportWidth, viewportHeight); //background text and indicator float rads = 110 * s; float rad = Math.min(Math.min(w, h) / 3.1f, Math.min(w, h)/2f - rads); float rad2 = rad + rads; float epad = 60f * s; float mpad = 100f * s; Draw.color(color); Lines.stroke(stroke); Lines.poly(w/2, h/2, 4, rad); Lines.poly(w/2, h/2, 4, rad2); if(assets.isLoaded("tech") && renderStencil){ Font font = assets.get("tech"); font.getData().markupEnabled = true; int panei = 0; for(int sx : Mathf.signs){ for(int sy : Mathf.signs){ float y1 = h/2f + sy*rad2, y2 = h/2f + sy*120f; //if(sy < 0) y1 = Math.min(y2, y1); floats.clear(); if(w > h){ //non-portrait floats.add(w/2f + sx*mpad, y1); floats.add(w/2f + (w/2f-epad)*sx, y1); floats.add(w/2f + (w/2f-epad)*sx, y2); floats.add(w/2f + sx*mpad + sx*Math.abs(y2-y1), y2); }else{ //portrait float py2 = h/2f + (h/2f-epad)*sy; float testval = sy < 0 ? Math.min(y2, y1) : Math.max(y2, y1); if(py2*sy < testval*sy){ continue; } floats.add(w/2f + sx*mpad, y1); floats.add(w/2f + sx*mpad, py2); floats.add(Mathf.clamp(w/2f + sx*(mpad + Math.abs(y2-y1)), stroke/2f, w - stroke/2f), py2); floats.add(Mathf.clamp(w/2f + sx*(mpad + Math.abs(y2-y1)), stroke/2f, w - stroke/2f), y2); } float minx = Float.MAX_VALUE, miny = Float.MAX_VALUE, maxx = 0, maxy = 0; for(int i = 0; i < floats.size; i+= 2){ float x = floats.items[i], y = floats.items[i + 1]; minx = Math.min(x, minx); miny = Math.min(y, miny); maxx = Math.max(x, maxx); maxy = Math.max(y, maxy); } Draw.flush(); Gl.clear(Gl.stencilBufferBit); Draw.beginStencil(); Fill.poly(floats); Draw.beginStenciled(); GlyphLayout layout = GlyphLayout.obtain(); float pad = 4; if(panei == 0){ layout.setText(font, assetText); font.draw(assetText, minx + pad, maxy - pad + Math.max(0, layout.height - (maxy - miny))); }else if(panei == 1){ float height = maxy - miny; float barpad = s * 8f; int barsUsed = Math.min((int)((height - barpad) / (font.getLineHeight() * 1.4f)), bars.length); float barspace = (height - barpad) / barsUsed; float barheight = barspace * 0.8f; for(int i = 0; i < barsUsed; i++){ Bar bar = bars[i]; if(bar.valid()){ Draw.color(bar.red() ? colorRed : color); float y = maxy - i * barspace - barpad - barheight; float width = Mathf.clamp(bar.value()); float baseWidth = Core.graphics.isPortrait() ? maxx - minx : (maxx - minx) - (maxy - y) - barpad * 2f - s * 4; float cx = minx + barpad, cy = y, topY = cy + barheight, botY = cy; Lines.square(cx + barheight / 2f, botY + barheight / 2f, barheight / 2f); Fill.quad( cx + barheight, cy, cx + barheight, topY, cx + width * baseWidth + barheight, topY, cx + width * baseWidth, botY ); Draw.color(Color.black); Fill.quad( cx + width * baseWidth + barheight, topY, cx + width * baseWidth, botY, cx + baseWidth, botY, cx + baseWidth + barheight, topY); font.setColor(Color.black); layout.setText(font, bar.text); font.draw(bar.text, cx + barheight * 1.5f, botY + barheight / 2f + layout.height / 2f); } } Draw.color(color); }else if(panei == 2){ float barw = 30f*s; float barspace = 40f*s; float barpad = 10f*s; int bars = (int)(maxx - minx / barspace) + 1; int barmax = (int)((maxy - miny) / barspace); for(int i = 0; i < bars; i++){ int index = i % renderTimes.getWindowSize(); float val = renderTimes.get(index); float scale = Mathf.clamp(!renderTimes.hasEnoughData() ? Mathf.randomSeed(i) : (val / renderTimes.mean() - 0.5f)); Color dst = scale > 0.8f ? colorRed : color; Draw.color(dst); int height = Math.max((int)(scale * barmax), 1); float cx = maxx - barw/2f - barpad - i*barspace; for(int j = 0; j < barmax; j++){ if(j >= height){ Draw.color(color, Color.black, 0.7f); }else{ Draw.color(dst); } Fill.square(cx, miny + j * barspace + barw/2f + barpad, barw/2f); } } Draw.color(color); }else if(panei == 3){ Draw.flush(); float vx = floats.get(6), vy = floats.get(7), vw = (maxx - vx), vh = (maxy - vy), cx = vx + vw/2f, cy = vy + vh/2f; float vpad = 30*s; float vcont = Math.min(vw, vh); float vsize = vcont - vpad*2; int rx = (int)(vx + vw/2f - vsize/2f), ry = (int)(vy + vh/2f - vsize/2f), rw = (int)vsize, rh = (int)vsize; float vrad = vsize/2f + vpad; //planet + bars if(!graphics.isPortrait()){ String text = "<>"; layout.setText(font, text); //draw only when text fits if(layout.width * 1.5f < vw){ Lines.circle(cx, cy, vsize/2f); if(rw > 0 && rh > 0){ Gl.viewport(viewportX + rx, viewportY + ry, rw, rh); cam.position.set(2, 0, 2); cam.resize(rw, rh); cam.lookAt(0, 0, 0); cam.fov = 42f; cam.update(); Shaders.mesh.bind(); Shaders.mesh.setUniformMatrix4("u_proj", cam.combined.val); mesh.render(Shaders.mesh, Gl.lines); //restore viewport Gl.viewport(viewportX, viewportY, viewportWidth, viewportHeight); } int points = 4; for(int i = 0; i < points; i++){ float ang = i * 360f / points + 45; Fill.poly(cx + Angles.trnsx(ang, vrad), cy + Angles.trnsy(ang, vrad), 3, 20 * s, ang); } Draw.color(Color.black); Fill.rect(cx, cy, layout.width + 14f * s, layout.height + 14f * s); font.setColor(color); font.draw(text, cx - layout.width / 2f, cy + layout.height / 2f); Draw.color(color); Lines.square(cx, cy, vcont / 2f); Lines.line(vx, vy, vx, vy + vh); float pspace = 70f * s; int pcount = (int)(vh / pspace / 2) + 2; float pw = (vw - vcont) / 2f; float slope = pw / 2f; //side bars for planet for(int i : Mathf.signs){ float px = cx + i * (vcont / 2f + pw / 2f); float xleft = px - pw / 2f, xright = px + pw / 2f; for(int j = -2; j < pcount * 2; j++){ float py = vy + j * pspace * 2, ybot = py - slope, ytop = py + slope; Fill.quad( xleft, ybot, xleft, ybot + pspace, xright, ytop + pspace, xright, ytop ); } } }else{ //X Lines.line(vx, vy, vx + vw, vy + vh); Lines.line(vx, vy + vh, vx + vw, vy); } } //fill the triangle with some stuff float trispace = 70f*s, tpad = 5f*s; int tris = (int)(vh / trispace) + 1; for(int tx = 0; tx < tris; tx++){ for(int ty = 0; ty < tris; ty++){ float trix = vx - trispace/2f - trispace*tx - tpad, triy = vy + vh - trispace/2f - trispace*ty -tpad; Draw.color(Mathf.randomSeed(Pack.longInt(tx + 91, ty + 55)) < 0.5 * (preview ? 1f : 1f - progress) ? colorRed : color); Fill.square(trix, triy, trispace/2.5f, 0); Draw.color(Color.black); Fill.square(trix, triy, trispace/2.5f / Mathf.sqrt2, 0); } } Draw.color(color); } layout.free(); Draw.endStencil(); Lines.polyline(floats, true); panei ++; } } } //middle display always has correct ratio, ignores viewport Draw.flush(); Gl.viewport(0, 0, graphics.getWidth(), graphics.getHeight()); Draw.proj(0, 0, graphics.getWidth(), graphics.getHeight()); w = graphics.getWidth(); h = graphics.getHeight(); //middle display float bspace = s * 100f; float bsize = s * 80f; int bars = (int)(w / bspace / 2) + 1; float pscale = 1f / bars; float barScale = 1.5f; Draw.color(Color.black); Fill.rect(w/2, h/2, w, bsize * barScale); Lines.stroke(stroke); Draw.color(color); Lines.rect(0, h/2 - bsize * barScale/2f, w, bsize * barScale, 10, 0); for(int i = 1; i < bars; i++){ float cx = i * bspace; float fract = 1f - (i - 1) / (float)(bars - 1); float alpha = progress >= fract ? 1f : Mathf.clamp((pscale - (fract - progress)) / pscale); Draw.color(Color.black, color, alpha); for(int dir : Mathf.signs){ float width = bsize/1.7f; float skew = bsize/2f; float v = w / 2 + cx * dir - width / 2f; Fill.rects(v + dir*skew, h/2f, width, bsize/2f, -dir*skew); Fill.rects(v, h/2f - bsize/2f, width, bsize/2f, dir*skew); } } //centerpiece has different rendering float fract = 1f - (-1) / (float)(bars - 1); float alpha = progress >= fract ? 1f : Mathf.clamp((pscale - (fract - progress)) / pscale); Draw.color(Color.black, color, alpha); Fill.square(w/2f, h/2f, bsize/3f, 45); //note for translators: this text is unreadable and for debugging/show anyway, so it's not translated if(assets.isLoaded("tech")){ String name = assets.getCurrentLoading() != null ? assets.getCurrentLoading().fileName.toLowerCase() : "system"; String key = name.contains("script") ? "scripts" : name.contains("content") ? "content" : name.contains("mod") ? "mods" : name.contains("msav") || name.contains("maps") ? "map" : name.contains("ogg") || name.contains("mp3") ? "sound" : name.contains("png") ? "image" : "system"; Font font = assets.get("tech"); font.getData().markupEnabled = true; font.setColor(Pal.accent); Draw.color(Color.black); font.draw(red + "[[[[ " + key + " ]]\n" + orange + "<" + Version.modifier + " " + (Version.build == 0 ? "[init]" : Version.buildString()) + ">", w/2f, h/2f + 110*s, Align.center); } Draw.flush(); fx.end(); fx.applyEffects(); fx.render(); } static class Bar{ final Floatp value; final Boolp red, valid; final String text; public Bar(String text, float value, boolean red){ this.value = () -> value; this.red = () -> red; this.valid = () -> true; this.text = text; } public Bar(String text, Boolp valid, Floatp value, Boolp red){ this.valid = valid; this.value = value; this.red = red; this.text = text; } boolean valid(){ return valid.get(); } boolean red(){ return red.get(); } float value(){ return Mathf.clamp(value.get()); } } }