Partially implemented new building rendering

This commit is contained in:
Anuken
2025-07-20 17:06:03 -04:00
parent 72e9db57a4
commit 6cd3a0a096
12 changed files with 345 additions and 414 deletions

View File

@@ -70,7 +70,7 @@ public class DrawOperation{
}
}
case opBlock -> {
tile.getLinkedTiles(t -> editor.renderer.updatePoint(t.x, t.y));
tile.getLinkedTiles(t -> editor.renderer.updateStatic(t.x, t.y));
Block block = content.block(to);
tile.setBlock(block, tile.team(), tile.build == null ? 0 : tile.build.rotation);
@@ -78,7 +78,7 @@ public class DrawOperation{
tile.build.enabled = true;
}
tile.getLinkedTiles(t -> editor.renderer.updatePoint(t.x, t.y));
tile.getLinkedTiles(t -> editor.renderer.updateStatic(t.x, t.y));
}
case opRotation -> {
if(tile.build != null) tile.build.rotation = to;
@@ -86,7 +86,7 @@ public class DrawOperation{
case opTeam -> tile.setTeam(Team.get(to));
}
});
editor.renderer.updatePoint(tile.x, tile.y);
editor.renderer.updateStatic(tile.x, tile.y);
}
@Struct

View File

@@ -0,0 +1,171 @@
package mindustry.editor;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.graphics.gl.*;
import arc.math.*;
import arc.struct.*;
import arc.util.*;
public class EditorSpriteCache implements Disposable{
//xy + color + uv
static final int vertexSize = 2 + 1 + 2;
private @Nullable Mesh mesh;
private final Seq<Texture> textures = new Seq<>(8);
private final IntSeq counts = new IntSeq(8);
private float[] tmpVertices;
/** Index in tmpVertices of current vertex data. */
private int index;
/** @param tmpVertices Temporary buffer to hold vertices while building up sprites. Should be large enough to hold all sprite data this cache will contain. */
public EditorSpriteCache(float[] tmpVertices){
this.tmpVertices = tmpVertices;
}
/** @return whether anything was added to the cache. */
public boolean isEmpty(){
return index == 0;
}
/**
* Builds this cache into a mesh that can be used for rendering. Use after calling {@link #draw(TextureRegion, float, float, float, float, float, float, float, float)}.
* Until this method is called, no mesh is created.
*
* @param indices The shared index data in standard quad format, as seen in SpriteBatch.
* Since this data is static, it should be the same across all caches, and be large enough to accommodate all sprites.
* */
public void build(IndexData indices){
if(mesh != null) mesh.dispose();
mesh = new Mesh(true, index / vertexSize, 0,
VertexAttribute.position,
VertexAttribute.color,
VertexAttribute.texCoords
);
mesh.indices = indices;
mesh.setVertices(tmpVertices, 0, index);
}
/** Adds the specified region to the cache. */
public void draw(TextureRegion region, float x, float y, float originX, float originY, float width, float height, float rotation, float colorPacked){
if(mesh != null) throw new IllegalStateException("This cache is already built. Call #clear() before drawing new sprites.");
// bottom left and top right corner points relative to origin
final float worldOriginX = x + originX;
final float worldOriginY = y + originY;
float fx = -originX;
float fy = -originY;
float fx2 = width - originX;
float fy2 = height - originY;
float x1, y1, x2, y2, x3, y3, x4, y4;
// rotate
if(rotation != 0){
final float cos = Mathf.cosDeg(rotation);
final float sin = Mathf.sinDeg(rotation);
x1 = cos * fx - sin * fy;
y1 = sin * fx + cos * fy;
x2 = cos * fx - sin * fy2;
y2 = sin * fx + cos * fy2;
x3 = cos * fx2 - sin * fy2;
y3 = sin * fx2 + cos * fy2;
x4 = x1 + (x3 - x2);
y4 = y3 - (y2 - y1);
}else{
x1 = fx;
y1 = fy;
x2 = fx;
y2 = fy2;
x3 = fx2;
y3 = fy2;
x4 = fx2;
y4 = fy;
}
x1 += worldOriginX;
y1 += worldOriginY;
x2 += worldOriginX;
y2 += worldOriginY;
x3 += worldOriginX;
y3 += worldOriginY;
x4 += worldOriginX;
y4 += worldOriginY;
final float u = region.u;
final float v = region.v2;
final float u2 = region.u2;
final float v2 = region.v;
int idx = index;
float[] verts = tmpVertices;
Texture texture = region.texture;
verts[idx + 0] = x1;
verts[idx + 1] = y1;
verts[idx + 2] = colorPacked;
verts[idx + 3] = u;
verts[idx + 4] = v;
verts[idx + 5] = x2;
verts[idx + 6] = y2;
verts[idx + 7] = colorPacked;
verts[idx + 8] = u;
verts[idx + 9] = v2;
verts[idx + 10] = x3;
verts[idx + 11] = y3;
verts[idx + 12] = colorPacked;
verts[idx + 13] = u2;
verts[idx + 14] = v2;
verts[idx + 15] = x4;
verts[idx + 16] = y4;
verts[idx + 17] = colorPacked;
verts[idx + 18] = u2;
verts[idx + 19] = v;
int lastIndex = textures.size - 1;
if(lastIndex < 0 || textures.get(lastIndex) != texture){
textures.add(texture);
counts.add(6);
}else{
counts.incr(lastIndex, 6);
}
index += vertexSize * 4;
}
/** Renders the cached mesh. The shader must already have the correct view matrix (usually u_projectionViewMatrix) set as a uniform. */
public void render(Shader shader){
if(mesh == null) throw new IllegalStateException("Cache is empty, call build() first.");
int offset = 0;
for(int i = 0; i < textures.size; i++){
int count = counts.items[i];
textures.get(i).bind();
mesh.render(shader, Gl.triangles, offset, count);
offset += count;
}
}
@Override
public void dispose(){
if(mesh != null){
mesh.dispose();
mesh = null;
}
}
}

View File

@@ -4,6 +4,7 @@ import arc.func.*;
import mindustry.content.*;
import mindustry.game.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import mindustry.world.modules.*;
@@ -46,13 +47,16 @@ public class EditorTile extends Tile{
@Override
public void setBlock(Block type, Team team, int rotation, Prov<Building> entityprov){
Block prev = this.block;
Tile prevCenter = (build == null ? this : build.tile);
if(skip()){
super.setBlock(type, team, rotation, entityprov);
return;
}
if(this.block == type && (build == null || build.rotation == rotation)){
update();
updateStatic();
return;
}
@@ -61,7 +65,7 @@ public class EditorTile extends Tile{
cen.op(DrawOperation.opRotation, (byte)build.rotation);
cen.op(DrawOperation.opTeam, (byte)build.team.id);
cen.op(DrawOperation.opBlock, block.id);
update();
updateStatic();
}else{
if(build != null) op(DrawOperation.opRotation, (byte)build.rotation);
if(build != null) op(DrawOperation.opTeam, (byte)build.team.id);
@@ -70,7 +74,20 @@ public class EditorTile extends Tile{
super.setBlock(type, team, rotation, entityprov);
renderer.blocks.updateShadowTile(this);
if(requiresBlockUpdate(type) || requiresBlockUpdate(prev)){
if(prev.size > 1){
prevCenter.getLinkedTilesAs(prev, tile -> {
editor.renderer.updateBlock(tile.x, tile.y);
renderer.blocks.updateShadowTile(tile);
});
}
getLinkedTiles(tile -> {
editor.renderer.updateBlock(tile.x, tile.y);
renderer.blocks.updateShadowTile(tile);
});
}else{
renderer.blocks.updateShadowTile(this);
}
}
@Override
@@ -84,7 +101,7 @@ public class EditorTile extends Tile{
op(DrawOperation.opTeam, (byte)getTeamID());
super.setTeam(team);
getLinkedTiles(t -> editor.renderer.updatePoint(t.x, t.y));
getLinkedTiles(t -> editor.renderer.updateStatic(t.x, t.y));
}
@Override
@@ -105,7 +122,7 @@ public class EditorTile extends Tile{
if(skip()){
super.fireChanged();
}else{
update();
updateStatic();
}
}
@@ -114,7 +131,7 @@ public class EditorTile extends Tile{
if(skip()){
super.firePreChanged();
}else{
update();
updateStatic();
}
}
@@ -159,8 +176,12 @@ public class EditorTile extends Tile{
return skip() && super.isDarkened();
}
private void update(){
editor.renderer.updatePoint(x, y);
private boolean requiresBlockUpdate(Block block){
return block != Blocks.air && block.cacheLayer == CacheLayer.normal;
}
private void updateStatic(){
editor.renderer.updateStatic(x, y);
}
private boolean skip(){

View File

@@ -374,7 +374,7 @@ public class MapEditor{
if(currentOp == null) currentOp = new DrawOperation();
currentOp.addOperation(data);
renderer.updatePoint(TileOp.x(data), TileOp.y(data));
renderer.updateStatic(TileOp.x(data), TileOp.y(data));
}
class Context implements WorldContext{

View File

@@ -303,7 +303,7 @@ public class MapEditorDialog extends Dialog implements Disposable{
state.rules = (lastSavedRules == null ? new Rules() : lastSavedRules);
lastSavedRules = null;
saved = false;
editor.renderer.updateAll();
editor.renderer.recache();
}
private void editInGame(){

View File

@@ -185,7 +185,7 @@ public class MapGenerateDialog extends BaseDialog{
}
//reset undo stack as generation... messes things up
editor.renderer.updateAll();
editor.renderer.recache();
editor.clearOp();
}

View File

@@ -41,7 +41,7 @@ public class MapProcessorsDialog extends BaseDialog{
foundAny = true;
tile.setNet(Blocks.worldProcessor, Team.sharded, 0);
if(ui.editor.isShown()){
Vars.editor.renderer.updatePoint(x, y);
Vars.editor.renderer.updateStatic(x, y);
}
break outer;
}

View File

@@ -3,228 +3,178 @@ package mindustry.editor;
import arc.*;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.graphics.gl.*;
import arc.math.geom.*;
import arc.struct.*;
import arc.util.*;
import mindustry.content.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.blocks.environment.*;
import static mindustry.Vars.*;
public class MapRenderer implements Disposable{
private static final int chunkSize = 62;
private IndexedRenderer[][] chunks;
private IntSet updates = new IntSet();
private IntSet delayedUpdates = new IntSet();
private TextureRegion clearEditor;
private static final int chunkSize = 60;
private EditorSpriteCache[][] chunks;
private IntSet recacheChunks = new IntSet();
private int width, height;
private Shader shader;
public void resize(int width, int height){
updates.clear();
delayedUpdates.clear();
if(chunks != null){
for(int x = 0; x < chunks.length; x++){
for(int y = 0; y < chunks[0].length; y++){
chunks[x][y].dispose();
}
}
}
dispose();
chunks = new IndexedRenderer[(int)Math.ceil((float)width / chunkSize)][(int)Math.ceil((float)height / chunkSize)];
recacheChunks.clear();
chunks = new EditorSpriteCache[(int)Math.ceil((float)width / chunkSize)][(int)Math.ceil((float)height / chunkSize)];
for(int x = 0; x < chunks.length; x++){
for(int y = 0; y < chunks[0].length; y++){
chunks[x][y] = new IndexedRenderer(chunkSize * chunkSize * 2);
}
}
this.width = width;
this.height = height;
updateAll();
renderer.blocks.floor.clearTiles();
renderer.blocks.reload();
recache();
}
public void draw(float tx, float ty, float tw, float th, float zoom){
public void draw(float tx, float ty, float tw, float th){
if(shader == null){
shader = new Shader(
"""
attribute vec4 a_position;
attribute vec4 a_color;
attribute vec2 a_texCoord0;
uniform mat4 u_projTrans;
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_projTrans * a_position;
}
""",
"""
varying lowp vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;
void main(){
gl_FragColor = v_color * texture2D(u_texture, v_texCoords);
}
"""
);
}
Draw.flush();
//TODO properly integrate this later
if(true){
updates.each(i -> renderer.blocks.floor.recacheTile(i % width, i / width));
updates.clear();
renderer.blocks.floor.checkChanges();
updates.addAll(delayedUpdates);
delayedUpdates.clear();
boolean prev = renderer.animateWater;
renderer.animateWater = false;
renderer.blocks.floor.checkChanges();
Core.camera.position.set(world.width()/2f * tilesize, world.height()/2f * tilesize);
Core.camera.width = 999999f;
Core.camera.height = 999999f;
Core.camera.mat.set(Draw.proj()).mul(Tmp.m3.setToTranslation(tx, ty).scale(tw / (width * tilesize), th / (height * tilesize)).translate(4f, 4f));
renderer.blocks.floor.drawFloor();
boolean prev = renderer.animateWater;
renderer.animateWater = false;
Tmp.m2.set(Draw.proj());
Core.camera.position.set(world.width()/2f * tilesize, world.height()/2f * tilesize);
Core.camera.width = 999999f;
Core.camera.height = 999999f;
Core.camera.mat.set(Draw.proj()).mul(Tmp.m3.setToTranslation(tx, ty).scale(tw / (width * tilesize), th / (height * tilesize)).translate(4f, 4f));
renderer.blocks.floor.drawFloor();
//scissors are always enabled because this is drawn clipped in UI, make sure they don't interfere with drawing shadow events
Gl.disable(Gl.scissorTest);
Tmp.m2.set(Draw.proj());
renderer.blocks.processShadows();
//this sure is awful!
Gl.disable(Gl.scissorTest);
Gl.enable(Gl.scissorTest);
renderer.blocks.processShadows();
Draw.proj(Core.camera.mat);
Gl.enable(Gl.scissorTest);
Draw.shader(Shaders.darkness);
Draw.rect(Draw.wrap(renderer.blocks.getShadowBuffer().getTexture()), world.width() * tilesize/2f - tilesize/2f, world.height() * tilesize/2f - tilesize/2f, world.width() * tilesize, -world.height() * tilesize);
Draw.shader();
Draw.proj(Core.camera.mat);
Draw.proj(Tmp.m2);
Draw.shader(Shaders.darkness);
Draw.rect(Draw.wrap(renderer.blocks.getShadowBuffer().getTexture()), world.width() * tilesize/2f - tilesize/2f, world.height() * tilesize/2f - tilesize/2f, world.width() * tilesize, -world.height() * tilesize);
Draw.shader();
renderer.blocks.floor.beginDraw();
renderer.blocks.floor.drawLayer(CacheLayer.walls);
renderer.animateWater = prev;
Draw.proj(Tmp.m2);
if(chunks == null) return;
renderer.blocks.floor.beginDraw();
renderer.blocks.floor.drawLayer(CacheLayer.walls);
renderer.animateWater = prev;
return;
}
recacheChunks.each(i -> recacheChunk(Point2.x(i), Point2.y(i)));
recacheChunks.clear();
clearEditor = Core.atlas.find("clear-editor");
updates.each(i -> render(i % width, i / width));
updates.clear();
updates.addAll(delayedUpdates);
delayedUpdates.clear();
//????
if(chunks == null){
return;
}
var texture = clearEditor.texture;
shader.bind();
shader.setUniformMatrix4("u_projTrans", Core.camera.mat);
for(int x = 0; x < chunks.length; x++){
for(int y = 0; y < chunks[0].length; y++){
IndexedRenderer mesh = chunks[x][y];
EditorSpriteCache mesh = chunks[x][y];
if(mesh == null){
continue;
if(mesh == null) continue;
mesh.render(shader);
}
}
}
void updateStatic(int x, int y){
renderer.blocks.floor.recacheTile(x, y);
}
void updateBlock(int x, int y){
recacheChunks.add(Point2.pack(x / chunkSize, y / chunkSize));
}
void recache(){
renderer.blocks.floor.clearTiles();
renderer.blocks.reload();
for(int x = 0; x < chunks.length; x++){
for(int y = 0; y < chunks[0].length; y++){
recacheChunk(x, y);
}
}
}
void recacheChunk(int cx, int cy){
if(chunks[cx][cy] != null){
chunks[cx][cy].dispose();
chunks[cx][cy] = null;
}
EditorSpriteCache cache = new EditorSpriteCache(renderer.blocks.floor.getVertexBuffer());
for(int x = cx * chunkSize; x < (cx + 1) * chunkSize; x++){
for(int y = cy * chunkSize; y < (cy + 1) * chunkSize; y++){
Tile tile = world.tile(x, y);
if(tile != null && tile.block() != Blocks.air && tile.block().cacheLayer == CacheLayer.normal && tile.isCenter()){
Block block = tile.block();
TextureRegion region = block.fullIcon;
float width = region.width * region.scl(), height = region.height * region.scl();
cache.draw(block.fullIcon,
x * tilesize + block.offset - width / 2f,
y * tilesize + block.offset - height / 2f,
width/2f, height/2f,
width, height,
tile.build == null || !block.rotate ? 0 : tile.build.rotdeg(),
Color.whiteFloatBits);
}
mesh.getTransformMatrix().setToTranslation(tx, ty).scale(tw / (width * tilesize), th / (height * tilesize));
mesh.setProjectionMatrix(Draw.proj());
mesh.render(texture);
}
}
}
public void updatePoint(int x, int y){
updates.add(x + y * width);
}
public void updateAll(){
clearEditor = Core.atlas.find("clear-editor");
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
render(x, y);
}
if(!cache.isEmpty()){
cache.build(renderer.blocks.floor.getIndexData());
chunks[cx][cy] = cache;
}
}
private TextureRegion getIcon(Block wall, int index){
return !wall.fullIcon.found() ?
clearEditor : wall.variants > 0 ?
wall.variantRegions()[Mathf.randomSeed(index, 0, wall.variantRegions().length - 1)] :
wall.fullIcon;
}
private void render(int wx, int wy){
int x = wx / chunkSize, y = wy / chunkSize;
if(x >= chunks.length || y >= chunks[0].length) return;
IndexedRenderer mesh = chunks[x][y];
Tile tile = editor.tiles().getn(wx, wy);
Floor floor = tile.floor();
Floor overlay = tile.overlay();
Block wall = tile.block();
TextureRegion region;
int idxWall = (wx % chunkSize) + (wy % chunkSize) * chunkSize;
int idxDecal = (wx % chunkSize) + (wy % chunkSize) * chunkSize + chunkSize * chunkSize;
boolean useSyntheticWall = overlay.wallOre;
//draw synthetic wall or floor OR standard wall if wall ore
if(wall != Blocks.air && useSyntheticWall){
region = getIcon(wall, idxWall);
float width = region.width * region.scl(), height = region.height * region.scl(), ox = wall.offset + (tilesize - width) / 2f, oy = wall.offset + (tilesize - height) / 2f;
//force fit to tile
if(overlay.wallOre && !wall.synthetic()){
width = height = tilesize;
ox = oy = 0f;
}
mesh.draw(idxWall, region,
wx * tilesize + ox,
wy * tilesize + oy,
width, height,
tile.build == null || !wall.rotate ? 0 : tile.build.rotdeg());
}else{
if(floor instanceof ColoredFloor){
mesh.setColor(Tmp.c1.set(tile.extraData | 0xff));
}
region = floor.variantRegions()[Mathf.randomSeed(idxWall, 0, floor.variantRegions().length - 1)];
mesh.draw(idxWall, region, wx * tilesize, wy * tilesize, 8, 8);
}
float offsetX = -((wall.size + 1) / 3) * tilesize, offsetY = -((wall.size + 1) / 3) * tilesize;
//draw non-synthetic wall or ore
if(!(wall.update || wall.destructible) && !useSyntheticWall && wall != Blocks.air){
region = getIcon(wall, idxWall);
if(wall == Blocks.cliff){
mesh.setColor(Tmp.c1.set(floor.mapColor).mul(1.6f));
region = ((Cliff)Blocks.cliff).editorCliffs[tile.data & 0xff];
}else if(wall instanceof ColoredWall){
mesh.setColor(Tmp.c1.set(tile.extraData | 0xff));
}
offsetX = tilesize / 2f - region.width * region.scl() / 2f;
offsetY = tilesize / 2f - region.height * region.scl() / 2f;
}else if((wall == Blocks.air || overlay.wallOre) && !overlay.isAir()){
if(floor.isLiquid){
mesh.setColor(Tmp.c1.set(1f, 1f, 1f, floor.overlayAlpha));
}
region = overlay.variantRegions()[Mathf.randomSeed(idxWall, 0, tile.overlay().variantRegions().length - 1)];
}else{
region = clearEditor;
}
float width = region.width * region.scl(), height = region.height * region.scl();
if(!wall.synthetic() && wall != Blocks.air && !wall.isMultiblock()){
offsetX = offsetY = 0f;
width = height = tilesize;
}
mesh.draw(idxDecal, region, wx * tilesize + offsetX, wy * tilesize + offsetY, width, height);
mesh.setColor(Color.white);
}
@Override
public void dispose(){
if(chunks == null){
return;
}
if(chunks == null) return;
for(int x = 0; x < chunks.length; x++){
for(int y = 0; y < chunks[0].length; y++){
if(chunks[x][y] != null){

View File

@@ -248,7 +248,7 @@ public class MapView extends Element implements GestureListener{
Draw.color(Pal.remove);
Lines.stroke(2f);
Lines.rect(centerx - sclwidth / 2 - 1, centery - sclheight / 2 - 1, sclwidth + 2, sclheight + 2);
editor.renderer.draw(centerx - sclwidth / 2 + Core.scene.marginLeft, centery - sclheight / 2 + Core.scene.marginBottom, sclwidth, sclheight, zoom);
editor.renderer.draw(centerx - sclwidth / 2 + Core.scene.marginLeft, centery - sclheight / 2 + Core.scene.marginBottom, sclwidth, sclheight);
Draw.reset();
if(grid){

View File

@@ -101,7 +101,7 @@ public class CacheLayer{
renderer.effectBuffer.begin();
Core.graphics.clear(Color.clear);
renderer.blocks.floor.beginc();
renderer.blocks.floor.beginDraw();
}
@Override
@@ -110,7 +110,7 @@ public class CacheLayer{
renderer.effectBuffer.end();
renderer.effectBuffer.blit(shader);
renderer.blocks.floor.beginc();
renderer.blocks.floor.beginDraw();
}
}
}

View File

@@ -115,6 +115,10 @@ public class FloorRenderer{
return indexData;
}
public float[] getVertexBuffer(){
return vertices;
}
/** Queues up a cache change for a tile. Only runs in render loop. */
public void recacheTile(Tile tile){
recacheTile(tile.x, tile.y);
@@ -183,14 +187,6 @@ public class FloorRenderer{
underwaterDraw.clear();
}
public void beginc(){
shader.bind();
shader.setUniformMatrix4("u_projectionViewMatrix", Core.camera.mat);
//only ever use the base environment texture
texture.bind(0);
}
public void checkChanges(){
if(recacheSet.size > 0){
//recache one chunk at a time
@@ -215,7 +211,11 @@ public class FloorRenderer{
Draw.flush();
beginc();
shader.bind();
shader.setUniformMatrix4("u_projectionViewMatrix", Core.camera.mat);
//only ever use the base environment texture
texture.bind(0);
Gl.enable(Gl.blend);
}
@@ -343,7 +343,7 @@ public class FloorRenderer{
(cx+1) * tilesize * chunksize + tilesize/2f, (cy+1) * tilesize * chunksize + tilesize/2f);
mesh.setVertices(vertices, 0, vidx);
//all vertices are shared
//all indices are shared and identical
mesh.indices = indexData;
return mesh;

View File

@@ -1,211 +0,0 @@
package mindustry.graphics;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.graphics.gl.*;
import arc.math.*;
import arc.util.*;
import mindustry.*;
import java.nio.*;
public class IndexedRenderer implements Disposable{
private static final int vsize = 5;
private final static Shader program = new Shader(
"""
attribute vec4 a_position;
attribute vec4 a_color;
attribute vec2 a_texCoord0;
uniform mat4 u_projTrans;
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_projTrans * a_position;
}
""",
"""
varying lowp vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;
void main(){
gl_FragColor = v_color * texture2D(u_texture, v_texCoords);
}
"""
);
private static final float[] tmpVerts = new float[vsize * 4];
private Mesh mesh;
private FloatBuffer buffer;
private Mat projMatrix = new Mat();
private Mat transMatrix = new Mat();
private Mat combined = new Mat();
private float color = Color.white.toFloatBits();
public IndexedRenderer(int sprites){
resize(sprites);
}
public void render(Texture texture){
Gl.enable(Gl.blend);
updateMatrix();
program.bind();
texture.bind();
program.setUniformMatrix4("u_projTrans", combined);
mesh.render(program, Gl.triangles, 0, mesh.getMaxVertices() * 6 / 4);
}
public void setColor(Color color){
this.color = color.toFloatBits();
}
public void draw(int index, TextureRegion region, float x, float y, float w, float h){
float fx2 = x + w;
float fy2 = y + h;
float u = region.u;
float v = region.v2;
float u2 = region.u2;
float v2 = region.v;
float[] vertices = tmpVerts;
float color = this.color;
int idx = 0;
vertices[idx++] = x;
vertices[idx++] = y;
vertices[idx++] = color;
vertices[idx++] = u;
vertices[idx++] = v;
vertices[idx++] = x;
vertices[idx++] = fy2;
vertices[idx++] = color;
vertices[idx++] = u;
vertices[idx++] = v2;
vertices[idx++] = fx2;
vertices[idx++] = fy2;
vertices[idx++] = color;
vertices[idx++] = u2;
vertices[idx++] = v2;
vertices[idx++] = fx2;
vertices[idx++] = y;
vertices[idx++] = color;
vertices[idx++] = u2;
vertices[idx++] = v;
int dest = index * vsize * 4;
buffer.position(dest);
buffer.put(vertices);
//mark dirty
mesh.getVerticesBuffer();
}
public void draw(int index, TextureRegion region, float x, float y, float w, float h, float rotation){
float u = region.u;
float v = region.v2;
float u2 = region.u2;
float v2 = region.v;
float originX = w / 2, originY = h / 2;
float cos = Mathf.cosDeg(rotation);
float sin = Mathf.sinDeg(rotation);
float fx = -originX;
float fy = -originY;
float fx2 = w - originX;
float fy2 = h - originY;
float worldOriginX = x + originX;
float worldOriginY = y + originY;
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[] vertices = tmpVerts;
float color = this.color;
int idx = 0;
vertices[idx++] = x1;
vertices[idx++] = y1;
vertices[idx++] = color;
vertices[idx++] = u;
vertices[idx++] = v;
vertices[idx++] = x2;
vertices[idx++] = y2;
vertices[idx++] = color;
vertices[idx++] = u;
vertices[idx++] = v2;
vertices[idx++] = x3;
vertices[idx++] = y3;
vertices[idx++] = color;
vertices[idx++] = u2;
vertices[idx++] = v2;
vertices[idx++] = x4;
vertices[idx++] = y4;
vertices[idx++] = color;
vertices[idx++] = u2;
vertices[idx++] = v;
int dest = index * vsize * 4;
buffer.position(dest);
buffer.put(vertices);
//mark dirty
mesh.getVerticesBuffer();
}
public Mat getTransformMatrix(){
return transMatrix;
}
public void setProjectionMatrix(Mat matrix){
projMatrix = matrix;
}
public void resize(int sprites){
if(mesh != null) mesh.dispose();
mesh = new Mesh(true, 4 * sprites, 0,
VertexAttribute.position,
VertexAttribute.color,
VertexAttribute.texCoords);
buffer = mesh.getVerticesBuffer();
buffer.limit(buffer.capacity());
mesh.indices = Vars.renderer.blocks.floor.getIndexData();
}
private void updateMatrix(){
combined.set(projMatrix).mul(transMatrix);
}
@Override
public void dispose(){
mesh.dispose();
}
}