From f0e92638af0e477f32d3276e46b80e0b7b664b3b Mon Sep 17 00:00:00 2001 From: DaniTheSkunk Date: Fri, 16 Sep 2022 03:18:12 +0200 Subject: [PATCH] implemented texture atlas --- Test.java | 13 ++-- .../skunkworks/GLRenderContext.java | 24 +++++-- com/danitheskunk/skunkworks/GLTexture.java | 11 ++- .../skunkworks/GLTextureAtlas.java | 72 +++++++++++++++++-- com/danitheskunk/skunkworks/GLWindow.java | 2 +- com/danitheskunk/skunkworks/ITexture.java | 2 +- 6 files changed, 107 insertions(+), 17 deletions(-) diff --git a/Test.java b/Test.java index d1557fd..301d950 100644 --- a/Test.java +++ b/Test.java @@ -4,10 +4,11 @@ public class Test { public static void main(String[] args) { var engine = new Engine(); var window = engine.openWindow(1280, 720, "Skunkworks"); + var img2 = engine.loadImage("C:\\art\\pixel stuff.png"); var img = engine.loadImage("C:\\Users\\dani\\Videos\\Screenshot 2022-06-25 17-00-59.png"); - //var img = engine.loadImage("C:\\art\\kyoko.png"); - var tex = window.loadTexture("C:\\Users\\dani\\Videos\\Screenshot 2022-06-25 17-00-59.png"); - System.out.println(img.getPixel(new Vec2i(60, 60))); + var tex2 = window.loadTexture("C:\\art\\pixel stuff.png"); + //img.drawImage(img2, Vec2i.ZERO); + var tex = window.loadTexture(img); while(!window.shouldClose()) { window.tick(); @@ -17,9 +18,13 @@ public class Test { // new Color(150, 200, 250) //); renderContext.drawTextureRectangle( - new Recti(100, 100, 100, 100), + new Recti(0, 0, 1280, 720), tex ); + renderContext.drawTextureRectangle( + new Recti(new Vec2i(200, 100), tex2.getSize()), + tex2 + ); window.renderFinish(renderContext); } } diff --git a/com/danitheskunk/skunkworks/GLRenderContext.java b/com/danitheskunk/skunkworks/GLRenderContext.java index cb37d76..2b25217 100644 --- a/com/danitheskunk/skunkworks/GLRenderContext.java +++ b/com/danitheskunk/skunkworks/GLRenderContext.java @@ -3,6 +3,11 @@ package com.danitheskunk.skunkworks; import static org.lwjgl.opengl.GL11.*; public class GLRenderContext implements IRenderContext{ + private GLTextureAtlas atlas; + + public GLRenderContext(GLTextureAtlas atlas) { + this.atlas = atlas; + } @Override public void drawRectangle(Recti rect, Color color) { var tl = rect.getTopLeft(); @@ -36,11 +41,18 @@ public class GLRenderContext implements IRenderContext{ @Override public void drawTextureRectangle(Recti rect, ITexture texture, Color color) { + var tex = (GLTexture)texture; + var tl = rect.getTopLeft(); var tr = rect.getTopRight(); var bl = rect.getBottomLeft(); var br = rect.getBottomRight(); + var ttl = Vec2i.divf(tex.getTexArea().getTopLeft(), atlas.getSize()); + var ttr = Vec2i.divf(tex.getTexArea().getTopRight(), atlas.getSize()); + var tbl = Vec2i.divf(tex.getTexArea().getBottomLeft(), atlas.getSize()); + var tbr = Vec2i.divf(tex.getTexArea().getBottomRight(), atlas.getSize()); + glColor4f( color.getR() / 255.0f, color.getG() / 255.0f, @@ -49,18 +61,18 @@ public class GLRenderContext implements IRenderContext{ ); //counter clockwise triangles - glTexCoord2f(0.0f, 1.0f); + glTexCoord2d(tbl.getX(), tbl.getY()); glVertex2i(bl.getX(), bl.getY()); - glTexCoord2f(1.0f, 0.0f); + glTexCoord2d(ttr.getX(), ttr.getY()); glVertex2i(tr.getX(), tr.getY()); - glTexCoord2f(0.0f, 0.0f); + glTexCoord2d(ttl.getX(), ttl.getY()); glVertex2i(tl.getX(), tl.getY()); - glTexCoord2f(1.0f, 0.0f); + glTexCoord2d(ttr.getX(), ttr.getY()); glVertex2i(tr.getX(), tr.getY()); - glTexCoord2f(0.0f, 1.0f); + glTexCoord2d(tbl.getX(), tbl.getY()); glVertex2i(bl.getX(), bl.getY()); - glTexCoord2f(1.0f, 1.0f); + glTexCoord2d(tbr.getX(), tbr.getY()); glVertex2i(br.getX(), bl.getY()); } } diff --git a/com/danitheskunk/skunkworks/GLTexture.java b/com/danitheskunk/skunkworks/GLTexture.java index c25a573..fb05f34 100644 --- a/com/danitheskunk/skunkworks/GLTexture.java +++ b/com/danitheskunk/skunkworks/GLTexture.java @@ -2,13 +2,22 @@ package com.danitheskunk.skunkworks; public class GLTexture implements ITexture { private Recti texArea; + Image img; //for re-blitting onto texture atlas - GLTexture(Recti texArea) { + GLTexture(Recti texArea, Image img) { this.texArea = texArea; + this.img = img; } //setters + @Override + public Vec2i getSize() { return this.img.getSize(); } void setTexArea(Recti texArea) { this.texArea = texArea; } + + + public Recti getTexArea() { + return texArea; + } } diff --git a/com/danitheskunk/skunkworks/GLTextureAtlas.java b/com/danitheskunk/skunkworks/GLTextureAtlas.java index fa4c7fd..3151743 100644 --- a/com/danitheskunk/skunkworks/GLTextureAtlas.java +++ b/com/danitheskunk/skunkworks/GLTextureAtlas.java @@ -1,9 +1,12 @@ package com.danitheskunk.skunkworks; import org.lwjgl.BufferUtils; +import org.lwjgl.stb.STBRPContext; +import org.lwjgl.stb.STBRectPack; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import static org.lwjgl.opengl.GL11.*; @@ -26,14 +29,63 @@ class GLTextureAtlas { ITexture addTexture(Image img) { //todo: do the actual texture stuff - this.img = img; - update(); - var texture = new GLTexture(new Recti(new Vec2i(0, 0), img.getSize())); + //this.img = img; + var texture = new GLTexture(new Recti(new Vec2i(0, 0), img.getSize()), img); textures.add(texture); + repack(); + updateImage(); + uploadToGpu(); return texture; } - void update() { + void doubleAtlasSize() { + img = new Image(Vec2i.mul(img.getSize(), 2)); + System.out.printf("Resized atlas to %dx%d\n", img.getSize().getX(), img.getSize().getY()); + } + + void repack() { + int x = 0; + int y = 0; + int height; + + textures.sort(new TextureHeightComparator().reversed()); + height = textures.get(0).img.getHeight(); + + for(var tex : textures) { + //texture larger than atlas? resize atlas and try again + if(tex.img.getHeight() > img.getHeight() || tex.img.getWidth() > img.getWidth()) { + doubleAtlasSize(); + repack(); + return; + } + + //row full? start the next row + if(img.getWidth() - x < tex.img.getWidth()) { + x = 0; + y += height; + height = tex.img.getHeight(); + + //not enough space for new row? resize atlas and try again + if(y + height > tex.img.getHeight()) { + doubleAtlasSize(); + repack(); + return; + } + } + + //take space, advance to right + tex.setTexArea(new Recti(new Vec2i(x, y), tex.img.getSize())); + x += tex.img.getWidth(); + } + } + + void updateImage() { + for(var tex : textures) { + img.drawImage(tex.img, tex.getTexArea().getPos()); + } + } + + void uploadToGpu() { var buf = BufferUtils.createByteBuffer(img.getData().length); buf.put(img.getData()); buf.flip(); @@ -50,4 +102,16 @@ class GLTextureAtlas { buf ); } + + public Vec2i getSize() { + return img.getSize(); + } + + private class TextureHeightComparator implements Comparator { + @Override + public int compare(GLTexture o1, GLTexture o2) { + return o1.img.getHeight() - o2.img.getHeight(); + } + } } + diff --git a/com/danitheskunk/skunkworks/GLWindow.java b/com/danitheskunk/skunkworks/GLWindow.java index aae6c16..ea99176 100644 --- a/com/danitheskunk/skunkworks/GLWindow.java +++ b/com/danitheskunk/skunkworks/GLWindow.java @@ -38,8 +38,8 @@ public class GLWindow implements IWindow { glEnable(GL_COLOR); glEnable(GL_TEXTURE_2D); - renderContext = new GLRenderContext(); textureAtlas = new GLTextureAtlas(); + renderContext = new GLRenderContext(textureAtlas); shouldClose = false; diff --git a/com/danitheskunk/skunkworks/ITexture.java b/com/danitheskunk/skunkworks/ITexture.java index b4c83c0..d086e85 100644 --- a/com/danitheskunk/skunkworks/ITexture.java +++ b/com/danitheskunk/skunkworks/ITexture.java @@ -1,5 +1,5 @@ package com.danitheskunk.skunkworks; public interface ITexture { - + Vec2i getSize(); }