diff --git a/core/res/shaders/los.frag b/core/res/shaders/los.frag index c402ceec..8a322c43 100644 --- a/core/res/shaders/los.frag +++ b/core/res/shaders/los.frag @@ -28,11 +28,12 @@ vec3 hsv2rgb(vec3 c){ void main(){ vec2 coords = vec2(v_texCoords.x * u_resolution.x + u_campos.x, v_texCoords.y * u_resolution.y + u_campos.y); + float random = rand(ceil(vec2(coords.x, coords.y) / 8. * u_cameraScale)); vec4 c = texture2D(u_texture, v_texCoords); vec3 hsv = rgb2hsv(c.rgb); - hsv.r += sin(u_time + rand(vec2(coords.x / u_cameraScale, coords.y / u_cameraScale)) * 100.0) * 0.08 - 0.04; + hsv.r += sin(u_time + random) * 0.16 - 0.04; c = vec4(hsv2rgb(hsv), c.a); gl_FragColor = c; diff --git a/core/res/sprites/blocks/turrets/bases/lumoni-block-4.png b/core/res/sprites/blocks/turrets/bases/lumoni-block-4.png new file mode 100644 index 00000000..f94cd5d9 Binary files /dev/null and b/core/res/sprites/blocks/turrets/bases/lumoni-block-4.png differ diff --git a/core/res/sprites/blocks/turrets/bases/lumoni-block-5.png b/core/res/sprites/blocks/turrets/bases/lumoni-block-5.png new file mode 100644 index 00000000..d5fd1614 Binary files /dev/null and b/core/res/sprites/blocks/turrets/bases/lumoni-block-5.png differ diff --git a/core/res/sprites/items/aluminium.png b/core/res/sprites/items/aluminium.png index f1ae284c..7a2fd81b 100644 Binary files a/core/res/sprites/items/aluminium.png and b/core/res/sprites/items/aluminium.png differ diff --git a/core/res/sprites/items/cuberium.png b/core/res/sprites/items/cuberium.png index 254eef25..7459563d 100644 Binary files a/core/res/sprites/items/cuberium.png and b/core/res/sprites/items/cuberium.png differ diff --git a/core/res/sprites/items/luminium.png b/core/res/sprites/items/luminium.png index 322c9db9..c1ceb5f7 100644 Binary files a/core/res/sprites/items/luminium.png and b/core/res/sprites/items/luminium.png differ diff --git a/core/res/sprites/items/nickel.png b/core/res/sprites/items/nickel.png index f70d3993..6a0a34f6 100644 Binary files a/core/res/sprites/items/nickel.png and b/core/res/sprites/items/nickel.png differ diff --git a/core/res/sprites/items/sulphur.png b/core/res/sprites/items/sulphur.png new file mode 100644 index 00000000..299ab5e6 Binary files /dev/null and b/core/res/sprites/items/sulphur.png differ diff --git a/core/src/fos/content/FOSItems.java b/core/src/fos/content/FOSItems.java index 923e35a1..52879089 100644 --- a/core/src/fos/content/FOSItems.java +++ b/core/src/fos/content/FOSItems.java @@ -42,7 +42,10 @@ public static void load(){ brass = new Item("brass", Color.valueOf("b57050")){{ cost = 2f; }}; - sulphur = new Item("sulphur", Color.valueOf("000000" /*TODO*/)); + sulphur = new Item("sulphur", Color.valueOf("f0b454")){{ + flammability = 0.6f; + cost = 0.5f; + }}; cuberium = new Item("cuberium", Color.valueOf("855992")){{ cost = 3f; }}; diff --git a/core/src/fos/core/FOSMod.java b/core/src/fos/core/FOSMod.java index abbf4493..435809c1 100644 --- a/core/src/fos/core/FOSMod.java +++ b/core/src/fos/core/FOSMod.java @@ -13,6 +13,7 @@ import fos.content.*; import fos.controllers.CapsulesController; import fos.gen.FosEntityMapping; +import fos.graphics.FOSOreRenderer; import fos.graphics.FOSShaders; import fos.net.FOSPackets; import fos.ui.DamageDisplay; @@ -83,6 +84,8 @@ public FOSMod() { @Override public void loadContent() { + FOSVars.oreRenderer = new FOSOreRenderer(); + SplashTexts.load(); FOSShaders.init(); diff --git a/core/src/fos/core/FOSVars.java b/core/src/fos/core/FOSVars.java index 65a1c190..e319d081 100644 --- a/core/src/fos/core/FOSVars.java +++ b/core/src/fos/core/FOSVars.java @@ -6,6 +6,7 @@ import fos.content.FOSFluids; import fos.controllers.CapsulesController; import fos.files.InternalFileTree; +import fos.graphics.FOSOreRenderer; import fos.ui.*; import fos.ui.menus.FOSMenuRenderer; import mindustry.ai.Pathfinder; @@ -51,6 +52,9 @@ public class FOSVars { /** Main menu renderer **/ public static FOSMenuRenderer menuRenderer = new FOSMenuRenderer(); + /** Ore cache renderer **/ + public static FOSOreRenderer oreRenderer; + /** Mod reference **/ public static Mods.LoadedMod mod; diff --git a/core/src/fos/graphics/FOSOreRenderer.java b/core/src/fos/graphics/FOSOreRenderer.java new file mode 100644 index 00000000..47ff85eb --- /dev/null +++ b/core/src/fos/graphics/FOSOreRenderer.java @@ -0,0 +1,492 @@ +package fos.graphics; + +import arc.Core; +import arc.Events; +import arc.graphics.*; +import arc.graphics.g2d.Batch; +import arc.graphics.g2d.Draw; +import arc.graphics.g2d.TextureRegion; +import arc.graphics.gl.IndexBufferObject; +import arc.graphics.gl.Shader; +import arc.graphics.gl.VertexBufferObject; +import arc.math.Mathf; +import arc.math.geom.Point2; +import arc.struct.IntSeq; +import arc.struct.IntSet; +import arc.struct.ObjectSet; +import arc.struct.Seq; +import arc.util.Log; +import arc.util.Structs; +import arc.util.Time; +import fos.graphics.cachelayers.AnimatedOreCacheLayer; +import mindustry.game.EventType; +import mindustry.graphics.CacheLayer; +import mindustry.world.Tile; +import mindustry.world.blocks.environment.Floor; +import mindustry.world.blocks.environment.OreBlock; + +import static mindustry.Vars.tilesize; +import static mindustry.Vars.world; + +public class FOSOreRenderer { + public static Seq oreCacheLayers = new Seq<>(); // do not change after world load + + private static final VertexAttribute[] attributes = {VertexAttribute.position, VertexAttribute.color, VertexAttribute.texCoords}; + private static final int + chunkSize = 32, + chunkUnits = chunkSize * tilesize, + vertexSize = 2 + 1 + 2, + spriteSize = vertexSize * 4, + maxSprites = chunkSize * chunkSize * 9; + private static final float pad = tilesize/2f; + private static final boolean dynamic = false; + + private float[] vertices = new float[maxSprites * vertexSize * 4]; + private short[] indices = new short[maxSprites * 6]; + private int vidx; + private FOSOreRendererBatch batch = new FOSOreRendererBatch(); + 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 used = new ObjectSet<>(); + + public FOSOreRenderer(){ + 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(EventType.WorldLoadEvent.class, event -> clearTiles()); + Events.run(EventType.Trigger.draw, this::drawOre); + Events.on(EventType.TileChangeEvent.class, event -> recacheTile(event.tile)); + } + + /** Queues up a cache change for a tile. Only runs in render loop. */ + public void recacheTile(Tile tile){ + //recaching all layers may not be necessary + recacheSet.add(Point2.pack(tile.x / chunkSize, tile.y / chunkSize)); + } + + public void drawOre(){ + if(cache == null){ + return; + } + + 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), + maxx = Mathf.ceil((camera.position.x + camera.width/2f + pad) / chunkUnits), + maxy = Mathf.ceil((camera.position.y + camera.height/2f + pad) / chunkUnits); + + int layers = oreCacheLayers.size; + + drawnLayers.clear(); + drawnLayerSet.clear(); + + for(int x = minx; x <= maxx; x++){ + for(int y = miny; y <= maxy; y++){ + + if(!Structs.inBounds(x, y, cache)) continue; + + 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] != null){ + drawnLayerSet.add(i); + } + } + } + } + + IntSet.IntSetIterator it = drawnLayerSet.iterator(); + while(it.hasNext){ + drawnLayers.add(it.next()); + } + + drawnLayers.sort(); + + Draw.flush(); + beginDraw(); + + for(int i = 0; i < drawnLayers.size; i++){ + CacheLayer layer = oreCacheLayers.get(drawnLayers.get(i)); + + drawLayer(layer); + } + + endDraw(); + } + + public void beginc(){ + shader.bind(); + shader.setUniformMatrix4("u_projectionViewMatrix", Core.camera.mat); + + texture.bind(0); + + if(Core.gl30 == null){ + for(VertexAttribute attribute : attributes){ + int loc = shader.getAttributeLocation(attribute.alias); + if(loc != -1) Gl.enableVertexAttribArray(loc); + } + } + + } + + public void endc(){ + if(Core.gl30 == null){ + for(VertexAttribute attribute : attributes){ + int loc = shader.getAttributeLocation(attribute.alias); + if(loc != -1) Gl.disableVertexAttribArray(loc); + } + } + + //unbind last buffer + Gl.bindBuffer(Gl.arrayBuffer, 0); + Gl.bindBuffer(Gl.elementArrayBuffer, 0); + } + + public void checkChanges(){ + if(recacheSet.size > 0){ + //recache one chunk at a time + IntSet.IntSetIterator iterator = recacheSet.iterator(); + while(iterator.hasNext){ + int chunk = iterator.next(); + cacheChunk(Point2.x(chunk), Point2.y(chunk)); + } + + recacheSet.clear(); + } + } + + public void beginDraw(){ + if(cache == null){ + return; + } + + Draw.flush(); + + beginc(); + + Gl.enable(Gl.blend); + } + + public void endDraw(){ + if(cache == null){ + return; + } + + endc(); + } + + public void drawLayer(CacheLayer layer){ + if(cache == null){ + return; + } + + Camera camera = Core.camera; + + int + minx = (int)((camera.position.x - camera.width/2f - pad) / chunkUnits), + miny = (int)((camera.position.y - camera.height/2f - pad) / chunkUnits), + maxx = Mathf.ceil((camera.position.x + camera.width/2f + pad) / chunkUnits), + maxy = Mathf.ceil((camera.position.y + camera.height/2f + pad) / chunkUnits); + + layer.begin(); + + for(int x = minx; x <= maxx; x++){ + for(int y = miny; y <= maxy; y++){ + + if(!Structs.inBounds(x, y, cache) || cache[x][y].length == 0){ + continue; + } + + var mesh = cache[x][y][layer.id]; + + //this *must* be a vertexbufferobject on gles2, so cast it and render it directly + if(mesh != null && 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; + + Gl.vertexAttribPointer(location, attribute.components, attribute.type, attribute.normalized, vertexSize * 4, aoffset); + } + + ibo.bind(); + + mesh.vertices.render(mesh.indices, Gl.triangles, 0, mesh.getNumIndices()); + }else if(mesh != null){ + //TODO this should be the default branch! + mesh.bind(shader); + mesh.render(shader, Gl.triangles); + } + } + } + + layer.end(); + } + + private void cacheChunk(int cx, int cy){ + used.clear(); + + for(int tilex = Math.max(cx * chunkSize - 1, 0); tilex < (cx + 1) * chunkSize + 1 && tilex < world.width(); tilex++){ + for(int tiley = Math.max(cy * chunkSize - 1, 0); tiley < (cy + 1) * chunkSize + 1 && tiley < world.height(); tiley++){ + Tile tile = world.rawTile(tilex, tiley); + + if (tile.overlay() instanceof OreBlock && tile.overlay().cacheLayer instanceof AnimatedOreCacheLayer layer) + used.add(layer); + } + } + + if(cache[cx][cy].length == 0){ + cache[cx][cy] = new Mesh[oreCacheLayers.size]; + } + + var meshes = cache[cx][cy]; + + for(CacheLayer layer : oreCacheLayers){ + if(meshes[layer.id] != null){ + meshes[layer.id].dispose(); + } + meshes[layer.id] = null; + } + + for(CacheLayer layer : used){ + meshes[layer.id] = cacheChunkLayer(cx, cy, layer); + } + } + + private Mesh cacheChunkLayer(int cx, int cy, CacheLayer layer){ + vidx = 0; + + 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++){ + Tile tile = world.tile(tilex, tiley); + + Floor overlay; + if (tile != null && (overlay = tile.overlay()) != null && overlay.cacheLayer == layer) { + overlay.drawBase(tile); + } + } + } + + Core.batch = current; + + 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.setVertices(vertices, 0, vidx); + mesh.setAutoBind(false); + mesh.setIndices(indices, 0, indCount); + + return mesh; + } + + public void clearTiles(){ + //dispose all old meshes + if(cache != null){ + for(var x : cache){ + for(var y : x){ + for(var mesh : y){ + if(mesh != null){ + mesh.dispose(); + } + } + } + } + } + + recacheSet.clear(); + int chunksx = Mathf.ceil((float)(world.width()) / chunkSize), chunksy = Mathf.ceil((float)(world.height()) / chunkSize); + cache = new Mesh[chunksx][chunksy][dynamic ? 0 : oreCacheLayers.size]; + + texture = Core.atlas.find("grass1").texture; + error = Core.atlas.find("env-error"); + + //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("Generated world mesh: @ms", Time.elapsed()); + } + } + + class FOSOreRendererBatch extends Batch{ + @Override + protected void draw(TextureRegion region, float x, float y, float originX, float originY, float width, float height, float rotation){ + 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"); + } + } +} diff --git a/core/src/fos/graphics/cachelayers/AnimatedOreCacheLayer.java b/core/src/fos/graphics/cachelayers/AnimatedOreCacheLayer.java index 7e56ee86..aa1536a1 100644 --- a/core/src/fos/graphics/cachelayers/AnimatedOreCacheLayer.java +++ b/core/src/fos/graphics/cachelayers/AnimatedOreCacheLayer.java @@ -3,7 +3,8 @@ import arc.Core; import arc.graphics.Color; import arc.graphics.gl.Shader; -import arc.util.Log; +import fos.core.FOSVars; +import fos.graphics.FOSOreRenderer; import mindustry.Vars; import mindustry.graphics.CacheLayer; @@ -11,27 +12,29 @@ public class AnimatedOreCacheLayer extends CacheLayer.ShaderLayer { public AnimatedOreCacheLayer(Shader shader) { super(shader); CacheLayer.add(this); + FOSOreRenderer.oreCacheLayers.add(this); + id = FOSOreRenderer.oreCacheLayers.size-1; } @Override public void begin(){ if(!Core.settings.getBool("fos-animatedore", true)) return; - Vars.renderer.blocks.floor.endc(); + FOSVars.oreRenderer.endc(); Vars.renderer.effectBuffer.begin(); Core.graphics.clear(Color.clear); - Vars.renderer.blocks.floor.beginc(); + FOSVars.oreRenderer.beginc(); } @Override public void end(){ if(!Core.settings.getBool("fos-animatedore", true)) return; - Vars.renderer.blocks.floor.endc(); + FOSVars.oreRenderer.endc(); Vars.renderer.effectBuffer.end(); Vars.renderer.effectBuffer.blit(shader); - Vars.renderer.blocks.floor.beginc(); + FOSVars.oreRenderer.beginc(); } }