Partial 7.0 merge - API preview
This commit is contained in:
@@ -6,6 +6,7 @@ import arc.graphics.Texture.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.graphics.gl.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import mindustry.content.*;
|
||||
@@ -23,8 +24,7 @@ public class BlockRenderer{
|
||||
public static final int crackRegions = 8, maxCrackSize = 9;
|
||||
|
||||
private static final int initialRequests = 32 * 32;
|
||||
private static final int expandr = 10;
|
||||
private static final Color shadowColor = new Color(0, 0, 0, 0.71f);
|
||||
private static final Color shadowColor = new Color(0, 0, 0, 0.71f), blendShadowColor = Color.white.cpy().lerp(Color.black, shadowColor.a);
|
||||
|
||||
public final FloorRenderer floor = new FloorRenderer();
|
||||
public TextureRegion[][] cracks;
|
||||
@@ -38,7 +38,11 @@ public class BlockRenderer{
|
||||
private FrameBuffer dark = new FrameBuffer();
|
||||
private Seq<Building> outArray2 = new Seq<>();
|
||||
private Seq<Tile> shadowEvents = new Seq<>();
|
||||
private IntSet procEntities = new IntSet(), procLinks = new IntSet(), procLights = new IntSet();
|
||||
private IntSet darkEvents = new IntSet();
|
||||
private IntSet procLinks = new IntSet(), procLights = new IntSet();
|
||||
|
||||
private BlockQuadtree blockTree;
|
||||
private FloorQuadtree floorTree;
|
||||
|
||||
public BlockRenderer(){
|
||||
|
||||
@@ -52,6 +56,8 @@ public class BlockRenderer{
|
||||
});
|
||||
|
||||
Events.on(WorldLoadEvent.class, event -> {
|
||||
blockTree = new BlockQuadtree(new Rect(0, 0, world.unitWidth(), world.unitHeight()));
|
||||
floorTree = new FloorQuadtree(new Rect(0, 0, world.unitWidth(), world.unitHeight()));
|
||||
shadowEvents.clear();
|
||||
lastCamY = lastCamX = -99; //invalidate camera position so blocks get updated
|
||||
|
||||
@@ -61,7 +67,7 @@ public class BlockRenderer{
|
||||
Core.graphics.clear(Color.white);
|
||||
Draw.proj().setOrtho(0, 0, shadows.getWidth(), shadows.getHeight());
|
||||
|
||||
Draw.color(shadowColor);
|
||||
Draw.color(blendShadowColor);
|
||||
|
||||
for(Tile tile : world.tiles){
|
||||
if(tile.block().hasShadow){
|
||||
@@ -73,17 +79,19 @@ public class BlockRenderer{
|
||||
Draw.color();
|
||||
shadows.end();
|
||||
|
||||
dark.getTexture().setFilter(TextureFilter.linear, TextureFilter.linear);
|
||||
dark.getTexture().setFilter(TextureFilter.linear);
|
||||
dark.resize(world.width(), world.height());
|
||||
dark.begin();
|
||||
Core.graphics.clear(Color.white);
|
||||
Draw.proj().setOrtho(0, 0, dark.getWidth(), dark.getHeight());
|
||||
|
||||
for(Tile tile : world.tiles){
|
||||
recordIndex(tile);
|
||||
|
||||
float darkness = world.getDarkness(tile.x, tile.y);
|
||||
|
||||
if(darkness > 0){
|
||||
Draw.color(0f, 0f, 0f, Math.min((darkness + 0.5f) / 4f, 1f));
|
||||
Draw.colorl(1f - Math.min((darkness + 0.5f) / 4f, 1f));
|
||||
Fill.rect(tile.x + 0.5f, tile.y + 0.5f, 1, 1);
|
||||
}
|
||||
}
|
||||
@@ -93,6 +101,11 @@ public class BlockRenderer{
|
||||
dark.end();
|
||||
});
|
||||
|
||||
Events.on(TilePreChangeEvent.class, event -> {
|
||||
if(indexBlock(event.tile)) blockTree.remove(event.tile);
|
||||
if(indexFloor(event.tile)) floorTree.remove(event.tile);
|
||||
});
|
||||
|
||||
Events.on(TileChangeEvent.class, event -> {
|
||||
shadowEvents.add(event.tile);
|
||||
|
||||
@@ -104,10 +117,60 @@ public class BlockRenderer{
|
||||
if(Math.abs(avgx - event.tile.x) <= rangex && Math.abs(avgy - event.tile.y) <= rangey){
|
||||
lastCamY = lastCamX = -99; //invalidate camera position so blocks get updated
|
||||
}
|
||||
|
||||
recordIndex(event.tile);
|
||||
});
|
||||
}
|
||||
|
||||
boolean indexBlock(Tile tile){
|
||||
var block = tile.block();
|
||||
return tile.isCenter() && block != Blocks.air && block.cacheLayer == CacheLayer.normal;
|
||||
}
|
||||
|
||||
boolean indexFloor(Tile tile){
|
||||
return tile.block() == Blocks.air && tile.floor().emitLight && world.getDarkness(tile.x, tile.y) < 3;
|
||||
}
|
||||
|
||||
void recordIndex(Tile tile){
|
||||
if(indexBlock(tile)) blockTree.insert(tile);
|
||||
if(indexFloor(tile)) floorTree.insert(tile);
|
||||
}
|
||||
|
||||
public void recacheWall(Tile tile){
|
||||
for(int cx = tile.x - darkRadius; cx <= tile.x + darkRadius; cx++){
|
||||
for(int cy = tile.y - darkRadius; cy <= tile.y + darkRadius; cy++){
|
||||
Tile other = world.tile(cx, cy);
|
||||
if(other != null){
|
||||
darkEvents.add(other.pos());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void drawDarkness(){
|
||||
if(!darkEvents.isEmpty()){
|
||||
Draw.flush();
|
||||
|
||||
dark.begin();
|
||||
Draw.proj().setOrtho(0, 0, dark.getWidth(), dark.getHeight());
|
||||
|
||||
darkEvents.each(pos -> {
|
||||
var tile = world.tile(pos);
|
||||
tile.data = world.getWallDarkness(tile);
|
||||
float darkness = world.getDarkness(tile.x, tile.y);
|
||||
//then draw the shadow
|
||||
Draw.colorl(!tile.isDarkened() || darkness <= 0f ? 1f : 1f - Math.min((darkness + 0.5f) / 4f, 1f));
|
||||
Fill.rect(tile.x + 0.5f, tile.y + 0.5f, 1, 1);
|
||||
});
|
||||
|
||||
Draw.flush();
|
||||
Draw.color();
|
||||
dark.end();
|
||||
darkEvents.clear();
|
||||
|
||||
Draw.proj(camera);
|
||||
}
|
||||
|
||||
Draw.shader(Shaders.darkness);
|
||||
Draw.fbo(dark, world.width(), world.height(), tilesize);
|
||||
Draw.shader();
|
||||
@@ -129,7 +192,7 @@ public class BlockRenderer{
|
||||
|
||||
Draw.alpha(0.33f * brokenFade);
|
||||
Draw.mixcol(Color.white, 0.2f + Mathf.absin(Time.globalTime, 6f, 0.2f));
|
||||
Draw.rect(b.icon(Cicon.full), block.x * tilesize + b.offset, block.y * tilesize + b.offset, b.rotate ? block.rotation * 90 : 0f);
|
||||
Draw.rect(b.fullIcon, block.x * tilesize + b.offset, block.y * tilesize + b.offset, b.rotate ? block.rotation * 90 : 0f);
|
||||
}
|
||||
Draw.reset();
|
||||
}
|
||||
@@ -143,11 +206,8 @@ public class BlockRenderer{
|
||||
Draw.proj().setOrtho(0, 0, shadows.getWidth(), shadows.getHeight());
|
||||
|
||||
for(Tile tile : shadowEvents){
|
||||
//clear it first
|
||||
Draw.color(Color.white);
|
||||
Fill.rect(tile.x + 0.5f, tile.y + 0.5f, 1, 1);
|
||||
//then draw the shadow
|
||||
Draw.color(!tile.block().hasShadow ? Color.white : shadowColor);
|
||||
//draw white/shadow color depending on blend
|
||||
Draw.color(!tile.block().hasShadow ? Color.white : blendShadowColor);
|
||||
Fill.rect(tile.x + 0.5f, tile.y + 0.5f, 1, 1);
|
||||
}
|
||||
|
||||
@@ -176,12 +236,11 @@ public class BlockRenderer{
|
||||
|
||||
/** Process all blocks to draw. */
|
||||
public void processBlocks(){
|
||||
|
||||
int avgx = (int)(camera.position.x / tilesize);
|
||||
int avgy = (int)(camera.position.y / tilesize);
|
||||
|
||||
int rangex = (int)(camera.width / tilesize / 2) + 3;
|
||||
int rangey = (int)(camera.height / tilesize / 2) + 3;
|
||||
int rangex = (int)(camera.width / tilesize / 2);
|
||||
int rangey = (int)(camera.height / tilesize / 2);
|
||||
|
||||
if(avgx == lastCamX && avgy == lastCamY && lastRangeX == rangex && lastRangeY == rangey){
|
||||
return;
|
||||
@@ -189,56 +248,32 @@ public class BlockRenderer{
|
||||
|
||||
tileview.clear();
|
||||
lightview.clear();
|
||||
procEntities.clear();
|
||||
procLinks.clear();
|
||||
procLights.clear();
|
||||
|
||||
int minx = Math.max(avgx - rangex - expandr, 0);
|
||||
int miny = Math.max(avgy - rangey - expandr, 0);
|
||||
int maxx = Math.min(world.width() - 1, avgx + rangex + expandr);
|
||||
int maxy = Math.min(world.height() - 1, avgy + rangey + expandr);
|
||||
var bounds = camera.bounds(Tmp.r3).grow(tilesize);
|
||||
|
||||
for(int x = minx; x <= maxx; x++){
|
||||
for(int y = miny; y <= maxy; y++){
|
||||
boolean expanded = (Math.abs(x - avgx) > rangex || Math.abs(y - avgy) > rangey);
|
||||
Tile tile = world.rawTile(x, y);
|
||||
Block block = tile.block();
|
||||
//link to center
|
||||
if(tile.build != null){
|
||||
tile = tile.build.tile;
|
||||
}
|
||||
//draw floor lights
|
||||
floorTree.intersect(bounds, tile -> lightview.add(tile));
|
||||
|
||||
if(block != Blocks.air && block.cacheLayer == CacheLayer.normal && (tile.build == null || !procEntities.contains(tile.build.id))){
|
||||
if(block.expanded || !expanded){
|
||||
if(tile.build == null || procLinks.add(tile.build.id)){
|
||||
tileview.add(tile);
|
||||
if(tile.build != null){
|
||||
procEntities.add(tile.build.id);
|
||||
procLinks.add(tile.build.id);
|
||||
}
|
||||
}
|
||||
blockTree.intersect(bounds, tile -> {
|
||||
if(tile.build == null || procLinks.add(tile.build.id)){
|
||||
tileview.add(tile);
|
||||
}
|
||||
|
||||
//lights are drawn even in the expanded range
|
||||
if(((tile.build != null && procLights.add(tile.build.pos())) || tile.block().emitLight)){
|
||||
lightview.add(tile);
|
||||
}
|
||||
|
||||
if(tile.build != null && tile.build.power != null && tile.build.power.links.size > 0){
|
||||
for(Building other : tile.build.getPowerConnections(outArray2)){
|
||||
if(other.block instanceof PowerNode && procLinks.add(other.id)){ //TODO need a generic way to render connections!
|
||||
tileview.add(other.tile);
|
||||
}
|
||||
|
||||
//lights are drawn even in the expanded range
|
||||
if(((tile.build != null && procLights.add(tile.build.pos())) || tile.block().emitLight)){
|
||||
lightview.add(tile);
|
||||
}
|
||||
|
||||
if(tile.build != null && tile.build.power != null && tile.build.power.links.size > 0){
|
||||
for(Building other : tile.build.getPowerConnections(outArray2)){
|
||||
if(other.block instanceof PowerNode && procLinks.add(other.id)){ //TODO need a generic way to render connections!
|
||||
tileview.add(other.tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//special case for floors
|
||||
if((block == Blocks.air && tile.floor().emitLight) && procLights.add(tile.pos())){
|
||||
lightview.add(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
lastCamX = avgx;
|
||||
lastCamY = avgy;
|
||||
@@ -246,7 +281,29 @@ public class BlockRenderer{
|
||||
lastRangeY = rangey;
|
||||
}
|
||||
|
||||
//debug method for drawing block bounds
|
||||
void drawTree(QuadTree<Tile> tree){
|
||||
Draw.color(Color.blue);
|
||||
Lines.rect(tree.bounds);
|
||||
|
||||
Draw.color(Color.green);
|
||||
for(var tile : tree.objects){
|
||||
var block = tile.block();
|
||||
Tmp.r1.setCentered(tile.worldx() + block.offset, tile.worldy() + block.offset, block.clipSize, block.clipSize);
|
||||
Lines.rect(Tmp.r1);
|
||||
}
|
||||
|
||||
if(!tree.leaf){
|
||||
drawTree(tree.botLeft);
|
||||
drawTree(tree.botRight);
|
||||
drawTree(tree.topLeft);
|
||||
drawTree(tree.topRight);
|
||||
}
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
public void drawBlocks(){
|
||||
|
||||
drawDestroyed();
|
||||
|
||||
//draw most tile stuff
|
||||
@@ -291,13 +348,47 @@ public class BlockRenderer{
|
||||
entity.drawLight();
|
||||
}else if(tile.block().emitLight){
|
||||
tile.block().drawEnvironmentLight(tile);
|
||||
}else if(tile.floor().emitLight && !tile.block().solid && world.getDarkness(tile.x, tile.y) < 3){ //only draw floor light under non-solid blocks
|
||||
}else if(tile.floor().emitLight && tile.block() == Blocks.air){ //only draw floor light under non-solid blocks
|
||||
tile.floor().drawEnvironmentLight(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class BlockQuadtree extends QuadTree<Tile>{
|
||||
|
||||
public BlockQuadtree(Rect bounds){
|
||||
super(bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitbox(Tile tile){
|
||||
var block = tile.block();
|
||||
tmp.setCentered(tile.worldx() + block.offset, tile.worldy() + block.offset, block.clipSize, block.clipSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QuadTree<Tile> newChild(Rect rect){
|
||||
return new BlockQuadtree(rect);
|
||||
}
|
||||
}
|
||||
|
||||
static class FloorQuadtree extends QuadTree<Tile>{
|
||||
|
||||
public FloorQuadtree(Rect bounds){
|
||||
super(bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hitbox(Tile tile){
|
||||
var floor = tile.floor();
|
||||
tmp.setCentered(tile.worldx(), tile.worldy(), floor.clipSize, floor.clipSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected QuadTree<Tile> newChild(Rect rect){
|
||||
return new FloorQuadtree(rect);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,92 +6,80 @@ import arc.graphics.gl.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public enum CacheLayer{
|
||||
water{
|
||||
@Override
|
||||
public void begin(){
|
||||
beginShader();
|
||||
}
|
||||
public class CacheLayer{
|
||||
public static CacheLayer
|
||||
|
||||
@Override
|
||||
public void end(){
|
||||
endShader(Shaders.water);
|
||||
}
|
||||
},
|
||||
mud{
|
||||
@Override
|
||||
public void begin(){
|
||||
beginShader();
|
||||
}
|
||||
water, mud, tar, slag, space, normal, walls;
|
||||
|
||||
@Override
|
||||
public void end(){
|
||||
endShader(Shaders.mud);
|
||||
}
|
||||
},
|
||||
tar{
|
||||
@Override
|
||||
public void begin(){
|
||||
beginShader();
|
||||
}
|
||||
public static CacheLayer[] all = {};
|
||||
|
||||
@Override
|
||||
public void end(){
|
||||
endShader(Shaders.tar);
|
||||
}
|
||||
},
|
||||
slag{
|
||||
@Override
|
||||
public void begin(){
|
||||
beginShader();
|
||||
}
|
||||
public int id;
|
||||
|
||||
@Override
|
||||
public void end(){
|
||||
endShader(Shaders.slag);
|
||||
}
|
||||
},
|
||||
space{
|
||||
@Override
|
||||
public void begin(){
|
||||
beginShader();
|
||||
}
|
||||
/** Register a new CacheLayer. */
|
||||
public static void add(CacheLayer... layers){
|
||||
int newSize = all.length + layers.length;
|
||||
var prev = all;
|
||||
//reallocate the array and copy everything over; performance matters very little here anyway
|
||||
all = new CacheLayer[newSize];
|
||||
System.arraycopy(prev, 0, all, 0, prev.length);
|
||||
System.arraycopy(layers, 0, all, prev.length, layers.length);
|
||||
|
||||
@Override
|
||||
public void end(){
|
||||
endShader(Shaders.space);
|
||||
for(int i = 0; i < all.length; i++){
|
||||
all[i].id = i;
|
||||
}
|
||||
},
|
||||
normal,
|
||||
walls;
|
||||
}
|
||||
|
||||
public static final CacheLayer[] all = values();
|
||||
/** Loads default cache layers. */
|
||||
public static void init(){
|
||||
add(
|
||||
water = new ShaderLayer(Shaders.water),
|
||||
mud = new ShaderLayer(Shaders.mud),
|
||||
tar = new ShaderLayer(Shaders.tar),
|
||||
slag = new ShaderLayer(Shaders.slag),
|
||||
space = new ShaderLayer(Shaders.space),
|
||||
normal = new CacheLayer(),
|
||||
walls = new CacheLayer()
|
||||
);
|
||||
}
|
||||
|
||||
/** Called before the cache layer begins rendering. Begin FBOs here. */
|
||||
public void begin(){
|
||||
|
||||
}
|
||||
|
||||
/** Called after the cache layer ends rendering. Blit FBOs here. */
|
||||
public void end(){
|
||||
|
||||
}
|
||||
|
||||
void beginShader(){
|
||||
if(!Core.settings.getBool("animatedwater")) return;
|
||||
public static class ShaderLayer extends CacheLayer{
|
||||
public Shader shader;
|
||||
|
||||
renderer.blocks.floor.endc();
|
||||
renderer.effectBuffer.begin();
|
||||
Core.graphics.clear(Color.clear);
|
||||
renderer.blocks.floor.beginc();
|
||||
}
|
||||
public ShaderLayer(Shader shader){
|
||||
//shader will be null on headless backend, but that's ok
|
||||
this.shader = shader;
|
||||
}
|
||||
|
||||
void endShader(Shader shader){
|
||||
if(!Core.settings.getBool("animatedwater")) return;
|
||||
@Override
|
||||
public void begin(){
|
||||
if(!Core.settings.getBool("animatedwater")) return;
|
||||
|
||||
renderer.blocks.floor.endc();
|
||||
renderer.effectBuffer.end();
|
||||
renderer.blocks.floor.endc();
|
||||
renderer.effectBuffer.begin();
|
||||
Core.graphics.clear(Color.clear);
|
||||
renderer.blocks.floor.beginc();
|
||||
}
|
||||
|
||||
renderer.effectBuffer.blit(shader);
|
||||
@Override
|
||||
public void end(){
|
||||
if(!Core.settings.getBool("animatedwater")) return;
|
||||
|
||||
renderer.blocks.floor.beginc();
|
||||
renderer.blocks.floor.endc();
|
||||
renderer.effectBuffer.end();
|
||||
|
||||
renderer.effectBuffer.blit(shader);
|
||||
|
||||
renderer.blocks.floor.beginc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,21 @@ import mindustry.*;
|
||||
import mindustry.ctype.*;
|
||||
import mindustry.game.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class Drawf{
|
||||
|
||||
public static void dashLine(Color color, float x, float y, float x2, float y2){
|
||||
int segments = (int)(Math.max(Math.abs(x - x2), Math.abs(y - y2)) / tilesize * 2);
|
||||
Lines.stroke(3f, Pal.gray);
|
||||
Lines.dashLine(x, y, x2, y2, segments);
|
||||
Lines.stroke(1f, color);
|
||||
Lines.dashLine(x, y, x2, y2, segments);
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
public static void target(float x, float y, float rad, Color color){
|
||||
target(x, y, rad, 1, color);
|
||||
}
|
||||
@@ -96,7 +104,7 @@ public class Drawf{
|
||||
|
||||
public static void shadow(float x, float y, float rad, float alpha){
|
||||
Draw.color(0, 0, 0, 0.4f * alpha);
|
||||
Draw.rect("circle-shadow", x, y, rad, rad);
|
||||
Draw.rect("circle-shadow", x, y, rad * Draw.xscl, rad * Draw.yscl);
|
||||
Draw.color();
|
||||
}
|
||||
|
||||
@@ -223,7 +231,7 @@ public class Drawf{
|
||||
}
|
||||
|
||||
public static void construct(Building t, UnlockableContent content, float rotation, float progress, float speed, float time){
|
||||
construct(t, content.icon(Cicon.full), rotation, progress, speed, time);
|
||||
construct(t, content.fullIcon, rotation, progress, speed, time);
|
||||
}
|
||||
|
||||
public static void construct(float x, float y, TextureRegion region, float rotation, float progress, float speed, float time){
|
||||
|
||||
8
core/src/mindustry/graphics/EnvRenderers.java
Normal file
8
core/src/mindustry/graphics/EnvRenderers.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package mindustry.graphics;
|
||||
|
||||
public class EnvRenderers{
|
||||
|
||||
public static void init(){
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package mindustry.graphics;
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.graphics.gl.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
@@ -12,29 +13,93 @@ import mindustry.game.EventType.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static mindustry.Vars.*;
|
||||
|
||||
public class FloorRenderer implements Disposable{
|
||||
private static final int chunksize = mobile ? 16 : 32, chunkunits = chunksize * tilesize;
|
||||
/**
|
||||
* general implementation:
|
||||
*
|
||||
* caching:
|
||||
* 1. create fixed-size float array fpr rendering into
|
||||
* 2. for each chunk, cache each layer into buffer; record layer boundary indices (alternatively, create mesh per layer for fast recache)
|
||||
* 3. create mesh for this chunk based on buffer size, copy buffer into mesh
|
||||
*
|
||||
* rendering:
|
||||
* 1. iterate through visible chunks
|
||||
* 2. activate the shader vertex attributes beforehand
|
||||
* 3. bind each mesh individually, draw it
|
||||
*
|
||||
* */
|
||||
public class FloorRenderer{
|
||||
private static final VertexAttribute[] attributes = {VertexAttribute.position, VertexAttribute.color, VertexAttribute.texCoords};
|
||||
private static final int
|
||||
chunksize = 30, //todo 32?
|
||||
chunkunits = chunksize * tilesize,
|
||||
vertexSize = 2 + 1 + 2,
|
||||
spriteSize = vertexSize * 4,
|
||||
maxSprites = chunksize * chunksize * 9;
|
||||
private static final float pad = tilesize/2f;
|
||||
//if true, chunks are rendered on-demand; this causes small lag spikes and is generally not needed for most maps
|
||||
private static final boolean dynamic = false;
|
||||
|
||||
private int[][][] cache;
|
||||
private MultiCacheBatch cbatch;
|
||||
private float[] vertices = new float[maxSprites * vertexSize * 4];
|
||||
private short[] indices = new short[maxSprites * 6];
|
||||
private int vidx;
|
||||
private FloorRenderBatch batch = new FloorRenderBatch();
|
||||
private Shader shader;
|
||||
private Texture texture;
|
||||
private TextureRegion error;
|
||||
|
||||
private Mesh[][][] cache;
|
||||
private IntSet drawnLayerSet = new IntSet();
|
||||
private IntSet recacheSet = new IntSet();
|
||||
private IntSeq drawnLayers = new IntSeq();
|
||||
private ObjectSet<CacheLayer> used = new ObjectSet<>();
|
||||
|
||||
public FloorRenderer(){
|
||||
short j = 0;
|
||||
for(int i = 0; i < indices.length; i += 6, j += 4){
|
||||
indices[i] = j;
|
||||
indices[i + 1] = (short)(j + 1);
|
||||
indices[i + 2] = (short)(j + 2);
|
||||
indices[i + 3] = (short)(j + 2);
|
||||
indices[i + 4] = (short)(j + 3);
|
||||
indices[i + 5] = j;
|
||||
}
|
||||
|
||||
shader = new Shader(
|
||||
"""
|
||||
attribute vec4 a_position;
|
||||
attribute vec4 a_color;
|
||||
attribute vec2 a_texCoord0;
|
||||
uniform mat4 u_projectionViewMatrix;
|
||||
varying vec4 v_color;
|
||||
varying vec2 v_texCoords;
|
||||
|
||||
void main(){
|
||||
v_color = a_color;
|
||||
v_color.a = v_color.a * (255.0/254.0);
|
||||
v_texCoords = a_texCoord0;
|
||||
gl_Position = u_projectionViewMatrix * a_position;
|
||||
}
|
||||
""",
|
||||
"""
|
||||
varying vec4 v_color;
|
||||
varying vec2 v_texCoords;
|
||||
uniform sampler2D u_texture;
|
||||
|
||||
void main(){
|
||||
gl_FragColor = v_color * texture2D(u_texture, v_texCoords);
|
||||
}
|
||||
""");
|
||||
|
||||
Events.on(WorldLoadEvent.class, event -> clearTiles());
|
||||
}
|
||||
|
||||
/**Queues up a cache change for a tile. Only runs in render loop. */
|
||||
/** Queues up a cache change for a tile. Only runs in render loop. */
|
||||
public void recacheTile(Tile tile){
|
||||
//currently a no-op
|
||||
//recacheSet.add(Point2.pack(tile.x / chunksize, tile.y / chunksize));
|
||||
//TODO will be faster it the position also specified the layer to be recached
|
||||
//recaching all layers may not be necessary
|
||||
recacheSet.add(Point2.pack(tile.x / chunksize, tile.y / chunksize));
|
||||
}
|
||||
|
||||
public void drawFloor(){
|
||||
@@ -44,6 +109,8 @@ public class FloorRenderer implements Disposable{
|
||||
|
||||
Camera camera = Core.camera;
|
||||
|
||||
float pad = tilesize/2f;
|
||||
|
||||
int
|
||||
minx = (int)((camera.position.x - camera.width/2f - pad) / chunkunits),
|
||||
miny = (int)((camera.position.y - camera.height/2f - pad) / chunkunits),
|
||||
@@ -61,11 +128,15 @@ public class FloorRenderer implements Disposable{
|
||||
|
||||
if(!Structs.inBounds(x, y, cache)) continue;
|
||||
|
||||
int[] chunk = cache[x][y];
|
||||
if(cache[x][y].length == 0){
|
||||
cacheChunk(x, y);
|
||||
}
|
||||
|
||||
Mesh[] chunk = cache[x][y];
|
||||
|
||||
//loop through all layers, and add layer index if it exists
|
||||
for(int i = 0; i < layers; i++){
|
||||
if(chunk[i] != -1 && i != CacheLayer.walls.ordinal()){
|
||||
if(chunk[i] != null && i != CacheLayer.walls.id){
|
||||
drawnLayerSet.add(i);
|
||||
}
|
||||
}
|
||||
@@ -92,11 +163,30 @@ public class FloorRenderer implements Disposable{
|
||||
}
|
||||
|
||||
public void beginc(){
|
||||
cbatch.beginDraw();
|
||||
shader.bind();
|
||||
shader.setUniformMatrix4("u_projectionViewMatrix", Core.camera.mat);
|
||||
shader.setUniformi("u_texture", 0);
|
||||
|
||||
//only ever use the base environment texture
|
||||
//TODO show error texture for anything else
|
||||
texture.bind(0);
|
||||
|
||||
//enable all mesh attributes
|
||||
for(VertexAttribute attribute : attributes){
|
||||
shader.enableVertexAttribute(attribute.alias);
|
||||
}
|
||||
}
|
||||
|
||||
public void endc(){
|
||||
cbatch.endDraw();
|
||||
|
||||
//disable all mesh attributes
|
||||
for(VertexAttribute attribute : attributes){
|
||||
shader.disableVertexAttribute(attribute.alias);
|
||||
}
|
||||
|
||||
//unbind last buffer
|
||||
Gl.bindBuffer(Gl.arrayBuffer, 0);
|
||||
Gl.bindBuffer(Gl.elementArrayBuffer, 0);
|
||||
}
|
||||
|
||||
public void checkChanges(){
|
||||
@@ -118,8 +208,8 @@ public class FloorRenderer implements Disposable{
|
||||
}
|
||||
|
||||
Draw.flush();
|
||||
cbatch.setProjection(Core.camera.mat);
|
||||
cbatch.beginDraw();
|
||||
|
||||
beginc();
|
||||
|
||||
Gl.enable(Gl.blend);
|
||||
}
|
||||
@@ -129,7 +219,7 @@ public class FloorRenderer implements Disposable{
|
||||
return;
|
||||
}
|
||||
|
||||
cbatch.endDraw();
|
||||
endc();
|
||||
}
|
||||
|
||||
public void drawLayer(CacheLayer layer){
|
||||
@@ -150,13 +240,37 @@ public class FloorRenderer implements Disposable{
|
||||
for(int x = minx; x <= maxx; x++){
|
||||
for(int y = miny; y <= maxy; y++){
|
||||
|
||||
if(!Structs.inBounds(x, y, cache)){
|
||||
if(!Structs.inBounds(x, y, cache) || cache[x][y].length == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
int[] chunk = cache[x][y];
|
||||
if(chunk[layer.ordinal()] == -1) continue;
|
||||
cbatch.drawCache(chunk[layer.ordinal()]);
|
||||
var mesh = cache[x][y][layer.id];
|
||||
|
||||
if(mesh != null){
|
||||
|
||||
//this *must* be a vertexbufferobject, so cast it and render it directly
|
||||
if(mesh.vertices instanceof VertexBufferObject vbo && mesh.indices instanceof IndexBufferObject ibo){
|
||||
|
||||
//bindi the buffer and update its contents, but do not unnecessarily enable all the attributes again
|
||||
vbo.bind();
|
||||
//set up vertex attribute pointers for this specific VBO
|
||||
int offset = 0;
|
||||
for(VertexAttribute attribute : attributes){
|
||||
int location = shader.getAttributeLocation(attribute.alias);
|
||||
int aoffset = offset;
|
||||
offset += attribute.size;
|
||||
if(location < 0) continue;
|
||||
|
||||
shader.setVertexAttribute(location, attribute.components, attribute.type, attribute.normalized, vertexSize * 4, aoffset);
|
||||
}
|
||||
|
||||
ibo.bind();
|
||||
|
||||
mesh.vertices.render(mesh.indices, Gl.triangles, 0, mesh.getNumIndices());
|
||||
}else{
|
||||
throw new ArcRuntimeException("Non-VBO meshes are not supported for caches.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +279,6 @@ public class FloorRenderer implements Disposable{
|
||||
|
||||
private void cacheChunk(int cx, int cy){
|
||||
used.clear();
|
||||
int[] chunk = cache[cx][cy];
|
||||
|
||||
for(int tilex = cx * chunksize; tilex < (cx + 1) * chunksize && tilex < world.width(); tilex++){
|
||||
for(int tiley = cy * chunksize; tiley < (cy + 1) * chunksize && tiley < world.height(); tiley++){
|
||||
@@ -179,21 +292,29 @@ public class FloorRenderer implements Disposable{
|
||||
}
|
||||
}
|
||||
|
||||
if(cache[cx][cy].length == 0){
|
||||
cache[cx][cy] = new Mesh[CacheLayer.all.length];
|
||||
}
|
||||
|
||||
var meshes = cache[cx][cy];
|
||||
|
||||
for(CacheLayer layer : CacheLayer.all){
|
||||
if(meshes[layer.id] != null){
|
||||
meshes[layer.id].dispose();
|
||||
}
|
||||
meshes[layer.id] = null;
|
||||
}
|
||||
|
||||
for(CacheLayer layer : used){
|
||||
cacheChunkLayer(cx, cy, chunk, layer);
|
||||
meshes[layer.id] = cacheChunkLayer(cx, cy, layer);
|
||||
}
|
||||
}
|
||||
|
||||
private void cacheChunkLayer(int cx, int cy, int[] chunk, CacheLayer layer){
|
||||
Batch current = Core.batch;
|
||||
Core.batch = cbatch;
|
||||
private Mesh cacheChunkLayer(int cx, int cy, CacheLayer layer){
|
||||
vidx = 0;
|
||||
|
||||
//begin a new cache
|
||||
if(chunk[layer.ordinal()] == -1){
|
||||
cbatch.beginCache();
|
||||
}else{
|
||||
cbatch.beginCache(chunk[layer.ordinal()]);
|
||||
}
|
||||
Batch current = Core.batch;
|
||||
Core.batch = batch;
|
||||
|
||||
for(int tilex = cx * chunksize; tilex < (cx + 1) * chunksize; tilex++){
|
||||
for(int tiley = cy * chunksize; tiley < (cy + 1) * chunksize; tiley++){
|
||||
@@ -217,36 +338,173 @@ public class FloorRenderer implements Disposable{
|
||||
}
|
||||
|
||||
Core.batch = current;
|
||||
chunk[layer.ordinal()] = cbatch.endCache();
|
||||
|
||||
int floats = vidx;
|
||||
//every 4 vertices need 6 indices
|
||||
int vertCount = floats / vertexSize, indCount = vertCount * 6/4;
|
||||
|
||||
Mesh mesh = new Mesh(true, vertCount, indCount, attributes);
|
||||
mesh.setAutoBind(false);
|
||||
mesh.setVertices(vertices, 0, vidx);
|
||||
mesh.setIndices(indices, 0, indCount);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
public void clearTiles(){
|
||||
if(cbatch != null) cbatch.dispose();
|
||||
|
||||
recacheSet.clear();
|
||||
int chunksx = Mathf.ceil((float)(world.width()) / chunksize),
|
||||
chunksy = Mathf.ceil((float)(world.height()) / chunksize);
|
||||
cache = new int[chunksx][chunksy][CacheLayer.all.length];
|
||||
cbatch = new MultiCacheBatch(chunksize * chunksize * 9);
|
||||
|
||||
Time.mark();
|
||||
|
||||
for(int x = 0; x < chunksx; x++){
|
||||
for(int y = 0; y < chunksy; y++){
|
||||
Arrays.fill(cache[x][y], -1);
|
||||
|
||||
cacheChunk(x, y);
|
||||
//dispose all old meshes
|
||||
if(cache != null){
|
||||
for(var x : cache){
|
||||
for(var y : x){
|
||||
for(var mesh : y){
|
||||
if(mesh != null){
|
||||
mesh.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.debug("Time to cache: @", Time.elapsed());
|
||||
recacheSet.clear();
|
||||
int chunksx = Mathf.ceil((float)(world.width()) / chunksize), chunksy = Mathf.ceil((float)(world.height()) / chunksize);
|
||||
cache = new Mesh[chunksx][chunksy][dynamic ? 0 : CacheLayer.all.length];
|
||||
|
||||
texture = Core.atlas.find("grass1").texture;
|
||||
error = Core.atlas.find("env-error");
|
||||
//not supported due to internal access
|
||||
Mesh.useVAO = false;
|
||||
|
||||
//pre-cache chunks
|
||||
if(!dynamic){
|
||||
Time.mark();
|
||||
|
||||
for(int x = 0; x < chunksx; x++){
|
||||
for(int y = 0; y < chunksy; y++){
|
||||
cacheChunk(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
Log.debug("Time to cache: @", Time.elapsed());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose(){
|
||||
if(cbatch != null){
|
||||
cbatch.dispose();
|
||||
cbatch = null;
|
||||
class FloorRenderBatch extends Batch{
|
||||
|
||||
@Override
|
||||
protected void draw(TextureRegion region, float x, float y, float originX, float originY, float width, float height, float rotation){
|
||||
|
||||
//substitute invalid regions with error
|
||||
if(region.texture != texture && region != error){
|
||||
draw(error, x, y, originX, originY, width, height, rotation);
|
||||
return;
|
||||
}
|
||||
|
||||
float[] verts = vertices;
|
||||
int idx = vidx;
|
||||
vidx += spriteSize;
|
||||
|
||||
if(!Mathf.zero(rotation)){
|
||||
//bottom left and top right corner points relative to origin
|
||||
float worldOriginX = x + originX;
|
||||
float worldOriginY = y + originY;
|
||||
float fx = -originX;
|
||||
float fy = -originY;
|
||||
float fx2 = width - originX;
|
||||
float fy2 = height - originY;
|
||||
|
||||
// rotate
|
||||
float cos = Mathf.cosDeg(rotation);
|
||||
float sin = Mathf.sinDeg(rotation);
|
||||
|
||||
float x1 = cos * fx - sin * fy + worldOriginX;
|
||||
float y1 = sin * fx + cos * fy + worldOriginY;
|
||||
float x2 = cos * fx - sin * fy2 + worldOriginX;
|
||||
float y2 = sin * fx + cos * fy2 + worldOriginY;
|
||||
float x3 = cos * fx2 - sin * fy2 + worldOriginX;
|
||||
float y3 = sin * fx2 + cos * fy2 + worldOriginY;
|
||||
float x4 = x1 + (x3 - x2);
|
||||
float y4 = y3 - (y2 - y1);
|
||||
|
||||
float u = region.u;
|
||||
float v = region.v2;
|
||||
float u2 = region.u2;
|
||||
float v2 = region.v;
|
||||
|
||||
float color = this.colorPacked;
|
||||
|
||||
verts[idx] = x1;
|
||||
verts[idx + 1] = y1;
|
||||
verts[idx + 2] = color;
|
||||
verts[idx + 3] = u;
|
||||
verts[idx + 4] = v;
|
||||
|
||||
verts[idx + 5] = x2;
|
||||
verts[idx + 6] = y2;
|
||||
verts[idx + 7] = color;
|
||||
verts[idx + 8] = u;
|
||||
verts[idx + 9] = v2;
|
||||
|
||||
verts[idx + 10] = x3;
|
||||
verts[idx + 11] = y3;
|
||||
verts[idx + 12] = color;
|
||||
verts[idx + 13] = u2;
|
||||
verts[idx + 14] = v2;
|
||||
|
||||
verts[idx + 15] = x4;
|
||||
verts[idx + 16] = y4;
|
||||
verts[idx + 17] = color;
|
||||
verts[idx + 18] = u2;
|
||||
verts[idx + 19] = v;
|
||||
}else{
|
||||
float fx2 = x + width;
|
||||
float fy2 = y + height;
|
||||
float u = region.u;
|
||||
float v = region.v2;
|
||||
float u2 = region.u2;
|
||||
float v2 = region.v;
|
||||
|
||||
float color = this.colorPacked;
|
||||
|
||||
verts[idx] = x;
|
||||
verts[idx + 1] = y;
|
||||
verts[idx + 2] = color;
|
||||
verts[idx + 3] = u;
|
||||
verts[idx + 4] = v;
|
||||
|
||||
verts[idx + 5] = x;
|
||||
verts[idx + 6] = fy2;
|
||||
verts[idx + 7] = color;
|
||||
verts[idx + 8] = u;
|
||||
verts[idx + 9] = v2;
|
||||
|
||||
verts[idx + 10] = fx2;
|
||||
verts[idx + 11] = fy2;
|
||||
verts[idx + 12] = color;
|
||||
verts[idx + 13] = u2;
|
||||
verts[idx + 14] = v2;
|
||||
|
||||
verts[idx + 15] = fx2;
|
||||
verts[idx + 16] = y;
|
||||
verts[idx + 17] = color;
|
||||
verts[idx + 18] = u2;
|
||||
verts[idx + 19] = v;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush(){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShader(Shader shader, boolean apply){
|
||||
throw new IllegalArgumentException("cache shader unsupported");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void draw(Texture texture, float[] spriteVertices, int offset, int count){
|
||||
throw new IllegalArgumentException("cache vertices unsupported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ public class LightRenderer{
|
||||
private float[] vertices = new float[24];
|
||||
private FrameBuffer buffer = new FrameBuffer();
|
||||
private Seq<Runnable> lights = new Seq<>();
|
||||
private Seq<CircleLight> circles = new Seq<>(CircleLight.class);
|
||||
private int circleIndex = 0;
|
||||
private TextureRegion circleRegion;
|
||||
|
||||
public void add(Runnable run){
|
||||
if(!enabled()) return;
|
||||
@@ -27,14 +30,17 @@ public class LightRenderer{
|
||||
}
|
||||
|
||||
public void add(float x, float y, float radius, Color color, float opacity){
|
||||
if(!enabled()) return;
|
||||
if(!enabled() || radius <= 0f) return;
|
||||
|
||||
float res = color.toFloatBits();
|
||||
add(() -> {
|
||||
Draw.color(res);
|
||||
Draw.alpha(opacity);
|
||||
Draw.rect("circle-shadow", x, y, radius * 2, radius * 2);
|
||||
});
|
||||
float res = Color.toFloatBits(color.r, color.g, color.b, opacity);
|
||||
|
||||
if(circles.size <= circleIndex) circles.add(new CircleLight());
|
||||
|
||||
//pool circles to prevent runaway GC usage from lambda capturing
|
||||
var light = circles.items[circleIndex];
|
||||
light.set(x, y, res, radius);
|
||||
|
||||
circleIndex ++;
|
||||
}
|
||||
|
||||
public void add(float x, float y, TextureRegion region, Color color, float opacity){
|
||||
@@ -170,7 +176,7 @@ public class LightRenderer{
|
||||
}
|
||||
|
||||
public boolean enabled(){
|
||||
return state.rules.lighting && state.rules.ambientLight.a > 0.00001f;
|
||||
return state.rules.lighting && state.rules.ambientLight.a > 0.0001f;
|
||||
}
|
||||
|
||||
public void draw(){
|
||||
@@ -179,6 +185,8 @@ public class LightRenderer{
|
||||
return;
|
||||
}
|
||||
|
||||
if(circleRegion == null) circleRegion = Core.atlas.find("circle-shadow");
|
||||
|
||||
buffer.resize(Core.graphics.getWidth()/scaling, Core.graphics.getHeight()/scaling);
|
||||
|
||||
Draw.color();
|
||||
@@ -188,6 +196,11 @@ public class LightRenderer{
|
||||
for(Runnable run : lights){
|
||||
run.run();
|
||||
}
|
||||
for(int i = 0; i < circleIndex; i++){
|
||||
var cir = circles.items[i];
|
||||
Draw.color(cir.color);
|
||||
Draw.rect(circleRegion, cir.x, cir.y, cir.radius * 2, cir.radius * 2);
|
||||
}
|
||||
Draw.reset();
|
||||
buffer.end();
|
||||
Gl.blendEquationSeparate(Gl.funcAdd, Gl.funcAdd);
|
||||
@@ -197,5 +210,17 @@ public class LightRenderer{
|
||||
buffer.blit(Shaders.light);
|
||||
|
||||
lights.clear();
|
||||
circleIndex = 0;
|
||||
}
|
||||
|
||||
static class CircleLight{
|
||||
float x, y, color, radius;
|
||||
|
||||
public void set(float x, float y, float color, float radius){
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.color = color;
|
||||
this.radius = radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ public class LoadRenderer implements Disposable{
|
||||
private int lastLength = -1;
|
||||
private FxProcessor fx;
|
||||
private WindowedMean renderTimes = new WindowedMean(20);
|
||||
private BloomFilter bloom;
|
||||
private long lastFrameTime;
|
||||
|
||||
{
|
||||
@@ -49,7 +50,7 @@ public class LoadRenderer implements Disposable{
|
||||
|
||||
//vignetting is probably too much
|
||||
//fx.addEffect(new VignettingFilter(false));
|
||||
fx.addEffect(new BloomFilter());
|
||||
fx.addEffect(bloom = new BloomFilter());
|
||||
|
||||
bars = new Bar[]{
|
||||
new Bar("s_proc#", OS.cores / 16f, OS.cores < 4),
|
||||
@@ -69,6 +70,7 @@ public class LoadRenderer implements Disposable{
|
||||
public void dispose(){
|
||||
mesh.dispose();
|
||||
fx.dispose();
|
||||
bloom.dispose();
|
||||
}
|
||||
|
||||
public void draw(){
|
||||
|
||||
@@ -12,7 +12,6 @@ import arc.util.*;
|
||||
import arc.util.noise.*;
|
||||
import mindustry.content.*;
|
||||
import mindustry.type.*;
|
||||
import mindustry.ui.*;
|
||||
import mindustry.world.*;
|
||||
import mindustry.world.blocks.environment.*;
|
||||
|
||||
@@ -30,7 +29,7 @@ public class MenuRenderer implements Disposable{
|
||||
private float time = 0f;
|
||||
private float flyerRot = 45f;
|
||||
private int flyers = Mathf.chance(0.2) ? Mathf.random(35) : Mathf.random(15);
|
||||
private UnitType flyerType = Structs.select(UnitTypes.flare, UnitTypes.flare, UnitTypes.horizon, UnitTypes.mono, UnitTypes.poly, UnitTypes.mega, UnitTypes.zenith);
|
||||
private UnitType flyerType = content.units().select(u -> u.hitSize <= 20f && u.flying && u.region.found()).random();
|
||||
|
||||
public MenuRenderer(){
|
||||
Time.mark();
|
||||
@@ -42,7 +41,7 @@ public class MenuRenderer implements Disposable{
|
||||
private void generate(){
|
||||
world.beginMapLoad();
|
||||
Tiles tiles = world.resize(width, height);
|
||||
Seq<Block> ores = content.blocks().select(b -> b instanceof OreBlock);
|
||||
Seq<Block> ores = content.blocks().select(b -> b instanceof OreBlock && !(b instanceof WallOreBlock));
|
||||
shadows = new FrameBuffer(width, height);
|
||||
int offset = Mathf.random(100000);
|
||||
Simplex s1 = new Simplex(offset);
|
||||
@@ -239,7 +238,7 @@ public class MenuRenderer implements Disposable{
|
||||
private void drawFlyers(){
|
||||
Draw.color(0f, 0f, 0f, 0.4f);
|
||||
|
||||
TextureRegion icon = flyerType.icon(Cicon.full);
|
||||
TextureRegion icon = flyerType.fullIcon;
|
||||
|
||||
float size = Math.max(icon.width, icon.height) * Draw.scl * 1.6f;
|
||||
|
||||
@@ -275,10 +274,12 @@ public class MenuRenderer implements Disposable{
|
||||
float offset = -100f;
|
||||
|
||||
for(int i = 0; i < flyers; i++){
|
||||
Tmp.v1.trns(flyerRot, time * (2f + flyerType.speed));
|
||||
Tmp.v1.trns(flyerRot, time * (flyerType.speed));
|
||||
|
||||
cons.get((Mathf.randomSeedRange(i, range) + Tmp.v1.x + Mathf.absin(time + Mathf.randomSeedRange(i + 2, 500), 10f, 3.4f) + offset) % (tw + Mathf.randomSeed(i + 5, 0, 500)),
|
||||
(Mathf.randomSeedRange(i + 1, range) + Tmp.v1.y + Mathf.absin(time + Mathf.randomSeedRange(i + 3, 500), 10f, 3.4f) + offset) % th);
|
||||
cons.get(
|
||||
(Mathf.randomSeedRange(i, range) + Tmp.v1.x + Mathf.absin(time + Mathf.randomSeedRange(i + 2, 500), 10f, 3.4f) + offset) % (tw + Mathf.randomSeed(i + 5, 0, 500)),
|
||||
(Mathf.randomSeedRange(i + 1, range) + Tmp.v1.y + Mathf.absin(time + Mathf.randomSeedRange(i + 3, 500), 10f, 3.4f) + offset) % th
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package mindustry.graphics;
|
||||
|
||||
import arc.*;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.Pixmap.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
@@ -36,6 +35,7 @@ public class MinimapRenderer{
|
||||
|
||||
//make sure to call on the graphics thread
|
||||
Events.on(TileChangeEvent.class, event -> {
|
||||
//TODO don't update when the minimap is off?
|
||||
if(!ui.editor.isShown()){
|
||||
update(event.tile);
|
||||
}
|
||||
@@ -69,7 +69,7 @@ public class MinimapRenderer{
|
||||
texture.dispose();
|
||||
}
|
||||
setZoom(4f);
|
||||
pixmap = new Pixmap(world.width(), world.height(), Format.rgba8888);
|
||||
pixmap = new Pixmap(world.width(), world.height());
|
||||
texture = new Texture(pixmap);
|
||||
region = new TextureRegion(texture);
|
||||
}
|
||||
@@ -96,7 +96,7 @@ public class MinimapRenderer{
|
||||
|
||||
Draw.mixcol(unit.team().color, 1f);
|
||||
float scale = Scl.scl(1f) / 2f * scaling * 32f;
|
||||
var region = unit.type.icon(Cicon.full);
|
||||
var region = unit.type.fullIcon;
|
||||
Draw.rect(region, x + rx, y + ry, scale, scale * (float)region.height / region.width, unit.rotation() - 90);
|
||||
Draw.reset();
|
||||
}
|
||||
@@ -136,7 +136,7 @@ public class MinimapRenderer{
|
||||
|
||||
public void updateAll(){
|
||||
for(Tile tile : world.tiles){
|
||||
pixmap.draw(tile.x, pixmap.getHeight() - 1 - tile.y, colorFor(tile));
|
||||
pixmap.set(tile.x, pixmap.height - 1 - tile.y, colorFor(tile));
|
||||
}
|
||||
texture.draw(pixmap);
|
||||
}
|
||||
@@ -144,10 +144,10 @@ public class MinimapRenderer{
|
||||
public void update(Tile tile){
|
||||
if(world.isGenerating() || !state.isGame()) return;
|
||||
|
||||
int color = colorFor(world.tile(tile.x, tile.y));
|
||||
pixmap.draw(tile.x, pixmap.getHeight() - 1 - tile.y, color);
|
||||
int color = colorFor(tile);
|
||||
pixmap.set(tile.x, pixmap.height - 1 - tile.y, color);
|
||||
|
||||
Pixmaps.drawPixel(texture, tile.x, pixmap.getHeight() - 1 - tile.y, color);
|
||||
Pixmaps.drawPixel(texture, tile.x, pixmap.height - 1 - tile.y, color);
|
||||
}
|
||||
|
||||
public void updateUnitArray(){
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
package mindustry.graphics;
|
||||
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.Pixmap.*;
|
||||
import arc.graphics.Texture.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
|
||||
public class MultiPacker implements Disposable{
|
||||
private PixmapPacker[] packers = new PixmapPacker[PageType.all.length];
|
||||
|
||||
public MultiPacker(){
|
||||
for(int i = 0; i < packers.length; i++){
|
||||
int pageSize = 2048;
|
||||
packers[i] = new PixmapPacker(pageSize, pageSize, Format.rgba8888, 2, true);
|
||||
packers[i] = new PixmapPacker(Math.min(Vars.maxTextureSize, PageType.all[i].width), Math.min(Vars.maxTextureSize, PageType.all[i].height), 2, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PixmapRegion get(String name){
|
||||
for(var packer : packers){
|
||||
var region = packer.getRegion(name);
|
||||
if(region != null){
|
||||
return region;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean has(String name){
|
||||
for(var page : PageType.all){
|
||||
if(packers[page.ordinal()].getRect(name) != null){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean has(PageType type, String name){
|
||||
return packers[type.ordinal()].getRect(name) != null;
|
||||
}
|
||||
@@ -24,6 +43,10 @@ public class MultiPacker implements Disposable{
|
||||
packers[type.ordinal()].pack(name, region);
|
||||
}
|
||||
|
||||
public void add(PageType type, String name, PixmapRegion region, int[] splits, int[] pads){
|
||||
packers[type.ordinal()].pack(name, region, splits, pads);
|
||||
}
|
||||
|
||||
public void add(PageType type, String name, Pixmap pix){
|
||||
packers[type.ordinal()].pack(name, pix);
|
||||
}
|
||||
@@ -50,11 +73,27 @@ public class MultiPacker implements Disposable{
|
||||
//zone page (sprites4.png) - zone previews
|
||||
//ui page (sprites5.png) - content icons, white icons and UI elements
|
||||
public enum PageType{
|
||||
main,
|
||||
main(4096),
|
||||
environment,
|
||||
editor,
|
||||
ui;
|
||||
editor(4096, 2048),
|
||||
rubble,
|
||||
ui(4096);
|
||||
|
||||
public static final PageType[] all = values();
|
||||
|
||||
public int width = 2048, height = 2048;
|
||||
|
||||
PageType(int defaultSize){
|
||||
this.width = this.height = defaultSize;
|
||||
}
|
||||
|
||||
PageType(int width, int height){
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
PageType(){
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import arc.math.geom.*;
|
||||
import arc.util.*;
|
||||
import mindustry.*;
|
||||
import mindustry.ai.types.*;
|
||||
import mindustry.entities.*;
|
||||
import mindustry.gen.*;
|
||||
import mindustry.input.*;
|
||||
import mindustry.ui.*;
|
||||
@@ -21,7 +22,7 @@ public class OverlayRenderer{
|
||||
private static final Rect rect = new Rect();
|
||||
|
||||
private float buildFade, unitFade;
|
||||
private Unit lastSelect;
|
||||
private Sized lastSelect;
|
||||
|
||||
public void drawBottom(){
|
||||
InputHandler input = control.input;
|
||||
@@ -72,27 +73,31 @@ public class OverlayRenderer{
|
||||
|
||||
InputHandler input = control.input;
|
||||
|
||||
Unit select = input.selectedUnit();
|
||||
|
||||
Sized select = input.selectedUnit();
|
||||
if(select == null) select = input.selectedControlBuild();
|
||||
if(!Core.input.keyDown(Binding.control)) select = null;
|
||||
|
||||
unitFade = Mathf.lerpDelta(unitFade, Mathf.num(select != null), 0.1f);
|
||||
|
||||
if(select != null) lastSelect = select;
|
||||
if(select == null) select = lastSelect;
|
||||
if(select != null && select.isAI()){
|
||||
if(select != null && (!(select instanceof Unitc u) || u.isAI())){
|
||||
Draw.mixcol(Pal.accent, 1f);
|
||||
Draw.alpha(unitFade);
|
||||
Building build = (select instanceof BlockUnitc b ? b.tile() : select instanceof Building b ? b : null);
|
||||
|
||||
if(select instanceof BlockUnitc){
|
||||
if(build != null){
|
||||
//special selection for block "units"
|
||||
Fill.square(select.x, select.y, ((BlockUnitc)select).tile().block.size * tilesize/2f);
|
||||
}else{
|
||||
Draw.rect(select.type.icon(Cicon.full), select.x(), select.y(), select.rotation() - 90);
|
||||
Fill.square(build.x, build.y, build.block.size * tilesize/2f);
|
||||
}else if(select instanceof Unit u){
|
||||
Draw.rect(u.type.fullIcon, u.x, u.y, u.rotation - 90);
|
||||
}
|
||||
|
||||
for(int i = 0; i < 4; i++){
|
||||
float rot = i * 90f + 45f + (-Time.time) % 360f;
|
||||
float length = select.hitSize() * 1.5f + (unitFade * 2.5f);
|
||||
Draw.rect("select-arrow", select.x + Angles.trnsx(rot, length), select.y + Angles.trnsy(rot, length), length / 1.9f, length / 1.9f, rot - 135f);
|
||||
Draw.rect("select-arrow", select.getX() + Angles.trnsx(rot, length), select.getY() + Angles.trnsy(rot, length), length / 1.9f, length / 1.9f, rot - 135f);
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
@@ -159,7 +164,8 @@ public class OverlayRenderer{
|
||||
|
||||
input.drawOverSelect();
|
||||
|
||||
if(ui.hudfrag.blockfrag.hover() instanceof Unit unit && unit.controller() instanceof LogicAI ai && ai.controller instanceof Building build && build.isValid()){
|
||||
if(ui.hudfrag.blockfrag.hover() instanceof Unit unit && unit.controller() instanceof LogicAI ai && ai.controller != null && ai.controller.isValid()){
|
||||
var build = ai.controller;
|
||||
Drawf.square(build.x, build.y, build.block.size * tilesize/2f + 2f);
|
||||
if(!unit.within(build, unit.hitSize * 2f)){
|
||||
Drawf.arrow(unit.x, unit.y, build.x, build.y, unit.hitSize *2f, 4f);
|
||||
@@ -170,7 +176,7 @@ public class OverlayRenderer{
|
||||
if(input.isDroppingItem()){
|
||||
Vec2 v = Core.input.mouseWorld(input.getMouseX(), input.getMouseY());
|
||||
float size = 8;
|
||||
Draw.rect(player.unit().item().icon(Cicon.medium), v.x, v.y, size, size);
|
||||
Draw.rect(player.unit().item().fullIcon, v.x, v.y, size, size);
|
||||
Draw.color(Pal.accent);
|
||||
Lines.circle(v.x, v.y, 6 + Mathf.absin(Time.time, 5f, 1f));
|
||||
Draw.reset();
|
||||
|
||||
@@ -14,10 +14,12 @@ public class Pal{
|
||||
sapBullet = Color.valueOf("bf92f9"),
|
||||
sapBulletBack = Color.valueOf("6d56bf"),
|
||||
|
||||
reactorPurple = Color.valueOf("bf92f9"),
|
||||
reactorPurple2 = Color.valueOf("8a73c6"),
|
||||
|
||||
spore = Color.valueOf("7457ce"),
|
||||
|
||||
shield = Color.valueOf("ffd37f").a(0.7f),
|
||||
shieldIn = Color.black.cpy().a(0f),
|
||||
|
||||
bulletYellow = Color.valueOf("fff8e8"),
|
||||
bulletYellowBack = Color.valueOf("f9c27a"),
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package mindustry.graphics;
|
||||
|
||||
import arc.graphics.*;
|
||||
import arc.math.*;
|
||||
import arc.util.noise.*;
|
||||
|
||||
/** Generates a scorch pixmap based on parameters. Thread safe, unless multiple scorch generators are running in parallel. */
|
||||
public class ScorchGenerator{
|
||||
private static final Simplex sim = new Simplex();
|
||||
|
||||
public int size = 80, seed = 0, color = Color.white.rgba();
|
||||
public double scale = 18, pow = 2, octaves = 4, pers = 0.4, add = 2, nscl = 4.5f;
|
||||
|
||||
public Pixmap generate(){
|
||||
Pixmap pix = new Pixmap(size, size);
|
||||
sim.setSeed(seed);
|
||||
|
||||
pix.each((x, y) -> {
|
||||
double dst = Mathf.dst(x, y, size/2, size/2) / (size / 2f);
|
||||
double scaled = Math.abs(dst - 0.5f) * 5f + add;
|
||||
scaled -= noise(Angles.angle(x, y, size/2, size/2))*nscl;
|
||||
if(scaled < 1.5f) pix.draw(x, y, color);
|
||||
});
|
||||
|
||||
return pix;
|
||||
}
|
||||
|
||||
private double noise(float angle){
|
||||
return Math.pow(sim.octaveNoise2D(octaves, pers, 1 / scale, Angles.trnsx(angle, size/2f) + size/2f, Angles.trnsy(angle, size/2f) + size/2f), pow);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public class Shaders{
|
||||
public static UnitBuildShader build;
|
||||
public static DarknessShader darkness;
|
||||
public static LightShader light;
|
||||
public static SurfaceShader water, mud, tar, slag, space;
|
||||
public static SurfaceShader water, mud, tar, slag, space, caustics;
|
||||
public static PlanetShader planet;
|
||||
public static PlanetGridShader planetGrid;
|
||||
public static AtmosphereShader atmosphere;
|
||||
@@ -48,6 +48,12 @@ public class Shaders{
|
||||
tar = new SurfaceShader("tar");
|
||||
slag = new SurfaceShader("slag");
|
||||
space = new SpaceShader("space");
|
||||
caustics = new SurfaceShader("caustics"){
|
||||
@Override
|
||||
public String textureName(){
|
||||
return "caustics";
|
||||
}
|
||||
};
|
||||
planet = new PlanetShader();
|
||||
planetGrid = new PlanetGridShader();
|
||||
atmosphere = new AtmosphereShader();
|
||||
@@ -249,6 +255,8 @@ public class Shaders{
|
||||
}
|
||||
|
||||
public static class SurfaceShader extends Shader{
|
||||
Texture noiseTex;
|
||||
|
||||
public SurfaceShader(String frag){
|
||||
super(getShaderFi("screenspace.vert"), getShaderFi(frag + ".frag"));
|
||||
loadNoise();
|
||||
@@ -259,8 +267,12 @@ public class Shaders{
|
||||
loadNoise();
|
||||
}
|
||||
|
||||
public String textureName(){
|
||||
return "noise";
|
||||
}
|
||||
|
||||
public void loadNoise(){
|
||||
Core.assets.load("sprites/noise.png", Texture.class).loaded = t -> {
|
||||
Core.assets.load("sprites/" + textureName() + ".png", Texture.class).loaded = t -> {
|
||||
((Texture)t).setFilter(TextureFilter.linear);
|
||||
((Texture)t).setWrap(TextureWrap.repeat);
|
||||
};
|
||||
@@ -273,7 +285,11 @@ public class Shaders{
|
||||
setUniformf("u_time", Time.time);
|
||||
|
||||
if(hasUniform("u_noise")){
|
||||
Core.assets.get("sprites/noise.png", Texture.class).bind(1);
|
||||
if(noiseTex == null){
|
||||
noiseTex = Core.assets.get("sprites/" + textureName() + ".png", Texture.class);
|
||||
}
|
||||
|
||||
noiseTex.bind(1);
|
||||
renderer.effectBuffer.getTexture().bind(0);
|
||||
|
||||
setUniformi("u_noise", 1);
|
||||
|
||||
@@ -3,51 +3,101 @@ package mindustry.graphics;
|
||||
import arc.graphics.*;
|
||||
import arc.graphics.g2d.*;
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.pooling.*;
|
||||
import arc.util.*;
|
||||
|
||||
public class Trail{
|
||||
public int length;
|
||||
|
||||
private final Seq<Vec3> points;
|
||||
private float lastX = -1, lastY = -1;
|
||||
private final FloatSeq points;
|
||||
private float lastX = -1, lastY = -1, lastAngle = -1, counter = 0f;
|
||||
|
||||
public Trail(int length){
|
||||
this.length = length;
|
||||
points = new Seq<>(length);
|
||||
points = new FloatSeq(length*3);
|
||||
}
|
||||
|
||||
public Trail copy(){
|
||||
Trail out = new Trail(length);
|
||||
out.points.addAll(points);
|
||||
out.lastX = lastX;
|
||||
out.lastY = lastY;
|
||||
out.lastAngle = lastAngle;
|
||||
return out;
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
points.clear();
|
||||
}
|
||||
|
||||
public int size(){
|
||||
return points.size/3;
|
||||
}
|
||||
|
||||
public void drawCap(Color color, float width){
|
||||
if(points.size > 0){
|
||||
Draw.color(color);
|
||||
float[] items = points.items;
|
||||
int i = points.size - 3;
|
||||
float x1 = items[i], y1 = items[i + 1], w1 = items[i + 2], w = w1 * width / (points.size/3) * i/3f * 2f;
|
||||
Draw.rect("hcircle", x1, y1, w, w, -Mathf.radDeg * lastAngle + 180f);
|
||||
Draw.reset();
|
||||
}
|
||||
}
|
||||
|
||||
public void draw(Color color, float width){
|
||||
Draw.color(color);
|
||||
float[] items = points.items;
|
||||
float lx = lastX, ly = lastY, lastAngle = this.lastAngle;
|
||||
|
||||
for(int i = 0; i < points.size - 1; i++){
|
||||
Vec3 c = points.get(i);
|
||||
Vec3 n = points.get(i + 1);
|
||||
float size = width / length;
|
||||
for(int i = 0; i < points.size - 3; i+= 3){
|
||||
float x1 = items[i], y1 = items[i + 1], w1 = items[i + 2],
|
||||
x2 = items[i + 3], y2 = items[i + 4], w2 = items[i + 5];
|
||||
float size = width / (points.size/3);
|
||||
float z1 = lastAngle;
|
||||
float z2 = -Angles.angleRad(x2, y2, lx, ly);
|
||||
|
||||
float cx = Mathf.sin(c.z) * i * size, cy = Mathf.cos(c.z) * i * size, nx = Mathf.sin(n.z) * (i + 1) * size, ny = Mathf.cos(n.z) * (i + 1) * size;
|
||||
Fill.quad(c.x - cx, c.y - cy, c.x + cx, c.y + cy, n.x + nx, n.y + ny, n.x - nx, n.y - ny);
|
||||
float cx = Mathf.sin(z1) * i/3f * size * w1, cy = Mathf.cos(z1) * i/3f * size * w1,
|
||||
nx = Mathf.sin(z2) * (i/3f + 1) * size * w2, ny = Mathf.cos(z2) * (i/3f + 1) * size * w2;
|
||||
Fill.quad(x1 - cx, y1 - cy, x1 + cx, y1 + cy, x2 + nx, y2 + ny, x2 - nx, y2 - ny);
|
||||
|
||||
lastAngle = z2;
|
||||
lx = x2;
|
||||
ly = y2;
|
||||
}
|
||||
|
||||
Draw.reset();
|
||||
}
|
||||
|
||||
public void update(float x, float y){
|
||||
if(points.size > length){
|
||||
Pools.free(points.first());
|
||||
points.remove(0);
|
||||
/** Removes the last point from the trail at intervals. */
|
||||
public void shorten(){
|
||||
if((counter += Time.delta) >= 0.99f){
|
||||
if(points.size >= 3){
|
||||
points.removeRange(0, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float angle = -Angles.angle(x, y, lastX, lastY);
|
||||
/** Adds a new point to the trail at intervals. */
|
||||
public void update(float x, float y){
|
||||
update(x, y, 1f);
|
||||
}
|
||||
|
||||
points.add(Pools.obtain(Vec3.class, Vec3::new).set(x, y, (angle) * Mathf.degRad));
|
||||
/** Adds a new point to the trail at intervals. */
|
||||
public void update(float x, float y, float width){
|
||||
if((counter += Time.delta) >= 0.99f){
|
||||
if(points.size > length*3){
|
||||
points.removeRange(0, 2);
|
||||
}
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
lastAngle = -Angles.angleRad(x, y, lastX, lastY);
|
||||
|
||||
points.add(x, y, width);
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
|
||||
counter = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +94,12 @@ public class PlanetRenderer implements Disposable{
|
||||
cam.position.setZero();
|
||||
cam.update();
|
||||
|
||||
Gl.depthMask(false);
|
||||
|
||||
skybox.render(cam.combined);
|
||||
|
||||
Gl.depthMask(true);
|
||||
|
||||
cam.position.set(lastPos);
|
||||
cam.update();
|
||||
|
||||
@@ -172,7 +176,7 @@ public class PlanetRenderer implements Disposable{
|
||||
}
|
||||
|
||||
public void renderOrbit(Planet planet){
|
||||
if(planet.parent == null || !planet.visible()) return;
|
||||
if(planet.parent == null || !planet.visible() || orbitAlpha <= 0.02f) return;
|
||||
|
||||
Vec3 center = planet.parent.position;
|
||||
float radius = planet.orbitRadius;
|
||||
@@ -182,6 +186,8 @@ public class PlanetRenderer implements Disposable{
|
||||
}
|
||||
|
||||
public void renderSectors(Planet planet){
|
||||
if(orbitAlpha <= 0.02f) return;
|
||||
|
||||
//apply transformed position
|
||||
batch.proj().mul(planet.getTransform(mat));
|
||||
|
||||
@@ -203,6 +209,7 @@ public class PlanetRenderer implements Disposable{
|
||||
public void drawArc(Planet planet, Vec3 a, Vec3 b){
|
||||
drawArc(planet, a, b, Pal.accent, Color.clear, 1f);
|
||||
}
|
||||
|
||||
public void drawArc(Planet planet, Vec3 a, Vec3 b, Color from, Color to, float length){
|
||||
drawArc(planet, a, b, from, to, length, 80f, 25);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user