Соединить несколько текстур в один атлас и изменить UV в OBJ модели

tox1cozZ

aka Agravaine
8,454
598
2,890
Вопрос в заголовке.
Есть модель, у нее есть группы и на каждую группу отдельный файл текстуры. Это неудобно, да и рисовать в игре плохо, ибо постоянно нужно щелкать текстуру...
Есть ли утилитка, которая упакует несколько текстур в один атлас + изменит UV координаты в модельке на правильные для атласа?
Ибо моделек таких много и вручную очень геморно все это переделывать...
 

timaxa007

Модератор
5,831
409
672
Текстуры блоки по отдельности, а в игре это единый атлас.
Java:
    //tessellator.setColorOpaque_I(clr);
    public static void boxOnIcon(Tessellator tessellator, IIcon icon, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
        if (icon == null) return;
        double icon_minX = (minX < 0D ? 0D : minX) * 16D;
        double icon_minY = (minY < 0D ? 0D : minY) * 16D;
        double icon_minZ = (minZ < 0D ? 0D : minZ) * 16D;
        double icon_maxX = (maxX > 1D ? 1D : maxX) * 16D;
        double icon_maxY = (maxY > 1D ? 1D : maxY) * 16D;
        double icon_maxZ = (maxZ > 1D ? 1D : maxZ) * 16D;
        tessellator.startDrawingQuads();
        //Top
        tessellator.addVertexWithUV(maxX, maxY, minZ,
                (double)icon.getInterpolatedU(icon_maxX),
                (double)icon.getInterpolatedV(icon_minZ)
                );
        tessellator.addVertexWithUV(minX, maxY, minZ,
                (double)icon.getInterpolatedU(icon_minX),
                (double)icon.getInterpolatedV(icon_minZ)
                );
        tessellator.addVertexWithUV(minX, maxY, maxZ,
                (double)icon.getInterpolatedU(icon_minX),
                (double)icon.getInterpolatedV(icon_maxZ)
                );
        tessellator.addVertexWithUV(maxX, maxY, maxZ,
                (double)icon.getInterpolatedU(icon_maxX),
                (double)icon.getInterpolatedV(icon_maxZ)
                );
        //Bottom
        tessellator.addVertexWithUV(maxX, minY, maxZ,
                (double)icon.getInterpolatedU(icon_maxX),
                (double)icon.getInterpolatedV(icon_maxZ)
                );
        tessellator.addVertexWithUV(minX, minY, maxZ,
                (double)icon.getInterpolatedU(icon_minX),
                (double)icon.getInterpolatedV(icon_maxZ)
                );
        tessellator.addVertexWithUV(minX, minY, minZ,
                (double)icon.getInterpolatedU(icon_minX),
                (double)icon.getInterpolatedV(icon_minZ)
                );
        tessellator.addVertexWithUV(maxX, minY, minZ,
                (double)icon.getInterpolatedU(icon_maxX),
                (double)icon.getInterpolatedV(icon_minZ)
                );
        //North
        tessellator.addVertexWithUV(maxX, minY, minZ,
                (double)icon.getInterpolatedU(icon_minX),
                (double)icon.getInterpolatedV(icon_maxY)
                );
        tessellator.addVertexWithUV(minX, minY, minZ,
                (double)icon.getInterpolatedU(icon_maxX),
                (double)icon.getInterpolatedV(icon_maxY)
                );
        tessellator.addVertexWithUV(minX, maxY, minZ,
                (double)icon.getInterpolatedU(icon_maxX),
                (double)icon.getInterpolatedV(icon_minY)
                );
        tessellator.addVertexWithUV(maxX, maxY, minZ,
                (double)icon.getInterpolatedU(icon_minX),
                (double)icon.getInterpolatedV(icon_minY)
                );
        //South
        tessellator.addVertexWithUV(maxX, maxY, maxZ,
                (double)icon.getInterpolatedU(icon_maxX),
                (double)icon.getInterpolatedV(icon_minY)
                );
        tessellator.addVertexWithUV(minX, maxY, maxZ,
                (double)icon.getInterpolatedU(icon_minX),
                (double)icon.getInterpolatedV(icon_minY)
                );
        tessellator.addVertexWithUV(minX, minY, maxZ,
                (double)icon.getInterpolatedU(icon_minX),
                (double)icon.getInterpolatedV(icon_maxY)
                );
        tessellator.addVertexWithUV(maxX, minY, maxZ,
                (double)icon.getInterpolatedU(icon_maxX),
                (double)icon.getInterpolatedV(icon_maxY)
                );
        //West
        tessellator.addVertexWithUV(minX, maxY, maxZ,
                (double)icon.getInterpolatedU(icon_maxY),
                (double)icon.getInterpolatedV(icon_minZ)
                );
        tessellator.addVertexWithUV(minX, maxY, minZ,
                (double)icon.getInterpolatedU(icon_minY),
                (double)icon.getInterpolatedV(icon_minZ)
                );
        tessellator.addVertexWithUV(minX, minY, minZ,
                (double)icon.getInterpolatedU(icon_minY),
                (double)icon.getInterpolatedV(icon_maxZ)
                );
        tessellator.addVertexWithUV(minX, minY, maxZ,
                (double)icon.getInterpolatedU(icon_maxY),
                (double)icon.getInterpolatedV(icon_maxZ)
                );
        //East
        tessellator.addVertexWithUV(maxX, minY, maxZ,
                (double)icon.getInterpolatedU(icon_minY),
                (double)icon.getInterpolatedV(icon_maxZ)
                );
        tessellator.addVertexWithUV(maxX, minY, minZ,
                (double)icon.getInterpolatedU(icon_maxY),
                (double)icon.getInterpolatedV(icon_maxZ)
                );
        tessellator.addVertexWithUV(maxX, maxY, minZ,
                (double)icon.getInterpolatedU(icon_maxY),
                (double)icon.getInterpolatedV(icon_minZ)
                );
        tessellator.addVertexWithUV(maxX, maxY, maxZ,
                (double)icon.getInterpolatedU(icon_minY),
                (double)icon.getInterpolatedV(icon_minZ)
                );

        tessellator.draw();
    }
//----------------------------------------------------------------------------------------------------
    public static void renderAllOnIcon(WavefrontObject obj, IIcon icon, Tessellator tessellator) {
        if (obj.groupObjects.size() > 0)
            for (GroupObject go : obj.groupObjects) {
                renderOnIcon(go, icon, tessellator);
            }
    }

    public static void renderPartOnIcon(WavefrontObject obj, String partName, IIcon icon, Tessellator tessellator) {
        if (obj.groupObjects.size() > 0)
            for (GroupObject go : obj.groupObjects) {
                if (partName.equals(go.name)) {
                    renderOnIcon(go, icon, tessellator);
                    break;
                } else continue;
            }
    }

    private static void renderOnIcon(GroupObject go, IIcon icon, Tessellator tessellator) {
        tessellator.startDrawing(GL11.GL_TRIANGLES);//GL_QUADS - напоминание.
        for (Face f : go.faces) {
            Vertex vf = f.faceNormal;
            tessellator.setNormal(vf.x, vf.y, vf.z);
            for (int i = 0; i < f.vertices.length; ++i) {
                Vertex v = f.vertices[i];
                if (f.textureCoordinates != null && f.textureCoordinates.length > 0) {
                    TextureCoordinate tc = f.textureCoordinates[i];
                    tessellator.addVertexWithUV(
                            (double)v.x, (double)v.y, (double)v.z,
                            (double)icon.getInterpolatedU((double)(tc.u * 16.0F)),
                            (double)icon.getInterpolatedV((double)(tc.v * 16.0F))
                            );
                } else {
                    //Плохо если код доходит до этого.
                    tessellator.addVertex((double)v.x, (double)v.y, (double)v.z);
                }
            }
        }
        tessellator.draw();
    }
 

timaxa007

Модератор
5,831
409
672
 
2,505
81
397
Наверное, нет такого.
Простых то атлас генераторов вагон, а вот чтобы еще и параллельно obj парсил...
Самое простое будет написать самому скриптик на пайтоне, взяв за основу какой-нибудь готовый атлас генератор и obj ридер.
 
2,505
81
397
Дак ты смотри, куда поместился регион, и вырази изменение через функцию (скейл и смещение). Затем на каждую uv модели примени эту функцию.
Т.к. у исходных текстур регион всегда (0f, 0f, 1f, 1f), то это должно работать правильно:
Kotlin:
val uScale = u2 - u1
val uOffset = u1
...
uVertex  = uVertex * uScale + uOffset
 
Последнее редактирование:

tox1cozZ

aka Agravaine
8,454
598
2,890
Java:
                    int atlasWidth = atlas.getCurrentWidth();
                    int atlasHeight = atlas.getCurrentHeight();
                    float atlasU = 1.0F / (float)atlasWidth;
                    float atlasV = 1.0F / (float)atlasHeight;
                    
                    float uScale = atlasU - (texture.getOriginX() * atlasU);
                    float uOffset = texture.getOriginX() * atlasU;
                    float uVertex = face.textureCoordinates[i].u * uScale + uOffset;
                    
                    float vScale = atlasV - (texture.getOriginY() * atlasV);
                    float vOffset = texture.getOriginY() * atlasV;
                    float vVertex = face.textureCoordinates[i].v * vScale + vOffset;
Чот не выходит, туплю.
 
2,505
81
397
Ты же когда подаешь атласу текстуру, должен получить иконку. А у этой иконки есть getMinU и т.п. А то что ты написал, это вообще дичь какая-то.
 
1,173
28
168
ТС дико плюсую, такая же проблема
Можешь потом фул решение скинуть?
 
2,505
81
397
Ну приведение, вроде, правильное. Так что, скорее всего, ты там чета накосячил.
 

tox1cozZ

aka Agravaine
8,454
598
2,890
Решено.
Java:
int x = texture.getOriginX();
int y = texture.getOriginY();
int w = texture.getIconWidth();
int h = texture.getIconHeight();
                
float scaleX = w / (float)stitcher.getCurrentWidth();
vertex.getTextureCoords().x *= scaleX;
vertex.getTextureCoords().x += texture.getMinU();

float scaleY = h / (float)stitcher.getCurrentHeight();
vertex.getTextureCoords().y = 1.0F - vertex.getTextureCoords().y;
vertex.getTextureCoords().y *= scaleY;
vertex.getTextureCoords().y += texture.getMinV();
texture достал из Stitcher, а stitcher - собственно сам объект Stitcher из майна.
 
Последнее редактирование:

tox1cozZ

aka Agravaine
8,454
598
2,890
Ну я просто скопировал майновский класс и чуточку подправил.
Код:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.google.common.collect.Lists;

import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.util.MathHelper;

public class AtlasStitcher{

    private final int mipmapLevelStitcher;
    private final Set setStitchHolders = new HashSet(256);
    private final List stitchSlots = new ArrayList(256);
    private int currentWidth;
    private int currentHeight;
    private final int maxWidth;
    private final int maxHeight;
    private final boolean forcePowerOf2;
    /** Max size (width or height) of a single tile */
    private final int maxTileDimension;
    private static final String __OBFID = "CL_00001054";
    public boolean rotate = false;

    public AtlasStitcher(int p_i45095_1_, int p_i45095_2_, boolean p_i45095_3_, int p_i45095_4_, int p_i45095_5_){
        this.mipmapLevelStitcher = p_i45095_5_;
        this.maxWidth = p_i45095_1_;
        this.maxHeight = p_i45095_2_;
        this.forcePowerOf2 = p_i45095_3_;
        this.maxTileDimension = p_i45095_4_;
    }

    public int getCurrentWidth(){
        return this.currentWidth;
    }

    public int getCurrentHeight(){
        return this.currentHeight;
    }

    public void addSprite(Texture p_110934_1_){
        AtlasStitcher.Holder holder = new AtlasStitcher.Holder(p_110934_1_, this.mipmapLevelStitcher);

        if(this.maxTileDimension > 0){
            holder.setNewDimension(this.maxTileDimension);
        }

        this.setStitchHolders.add(holder);
    }

    public void doStitch(){
        AtlasStitcher.Holder[] aholder = (AtlasStitcher.Holder[])this.setStitchHolders.toArray(new AtlasStitcher.Holder[this.setStitchHolders.size()]);
        Arrays.sort(aholder);
        AtlasStitcher.Holder[] aholder1 = aholder;
        int i = aholder.length;

        for(int j = 0; j < i; ++j){
            AtlasStitcher.Holder holder = aholder1[j];

            if(!this.allocateSlot(holder)){
                String s = String.format("Unable to fit: %s - size: %dx%d - Maybe try a lowerresolution resourcepack?", new Object[]{holder.getAtlasSprite().getIconName(), Integer.valueOf(holder.getAtlasSprite().getIconWidth()), Integer.valueOf(holder.getAtlasSprite().getIconHeight())});
            }
        }

        if(this.forcePowerOf2){
            this.currentWidth = MathHelper.roundUpToPowerOfTwo(this.currentWidth);
            this.currentHeight = MathHelper.roundUpToPowerOfTwo(this.currentHeight);
        }
    }

    public List getStichSlots(){
        ArrayList arraylist = Lists.newArrayList();
        Iterator iterator = this.stitchSlots.iterator();

        while(iterator.hasNext()){
            AtlasStitcher.Slot slot = (AtlasStitcher.Slot)iterator.next();
            slot.getAllStitchSlots(arraylist);
        }

        ArrayList arraylist1 = Lists.newArrayList();
        Iterator iterator1 = arraylist.iterator();

        while(iterator1.hasNext()){
            AtlasStitcher.Slot slot1 = (AtlasStitcher.Slot)iterator1.next();
            AtlasStitcher.Holder holder = slot1.getStitchHolder();
            Texture textureatlassprite = holder.getAtlasSprite();
            textureatlassprite.initSprite(this.currentWidth, this.currentHeight, slot1.getOriginX(), slot1.getOriginY(), rotate ? holder.isRotated() : false);
            arraylist1.add(textureatlassprite);
        }

        return arraylist1;
    }

    private static int getMipmapDimension(int p_147969_0_, int p_147969_1_){
        return p_147969_0_;//(p_147969_0_ >> p_147969_1_) + ((p_147969_0_ & (1 << p_147969_1_) - 1) == 0 ? 0 : 1) << p_147969_1_;
    }

    /**
     * Attempts to find space for specified tile
     */
    private boolean allocateSlot(AtlasStitcher.Holder p_94310_1_){
        for(int i = 0; i < this.stitchSlots.size(); ++i){
            if(((AtlasStitcher.Slot)this.stitchSlots.get(i)).addSlot(p_94310_1_)){
                return true;
            }

            if(rotate){
                p_94310_1_.rotate();

                if(((AtlasStitcher.Slot)this.stitchSlots.get(i)).addSlot(p_94310_1_)){
                    return true;
                }

                p_94310_1_.rotate();
            }
        }

        return this.expandAndAllocateSlot(p_94310_1_);
    }

    /**
     * Expand stitched texture in order to make space for specified tile
     */
    private boolean expandAndAllocateSlot(AtlasStitcher.Holder p_94311_1_){
        int i = Math.min(p_94311_1_.getWidth(), p_94311_1_.getHeight());
        boolean flag = this.currentWidth == 0 && this.currentHeight == 0;
        boolean flag1;
        int j;

        if(this.forcePowerOf2){
            j = MathHelper.roundUpToPowerOfTwo(this.currentWidth);
            int k = MathHelper.roundUpToPowerOfTwo(this.currentHeight);
            int l = MathHelper.roundUpToPowerOfTwo(this.currentWidth + i);
            int i1 = MathHelper.roundUpToPowerOfTwo(this.currentHeight + i);
            boolean flag2 = l <= this.maxWidth;
            boolean flag3 = i1 <= this.maxHeight;

            if(!flag2 && !flag3){
                return false;
            }

            boolean flag4 = j != l;
            boolean flag5 = k != i1;

            if(flag4 ^ flag5){
                flag1 = !flag4;
            }else{
                flag1 = flag2 && j <= k;
            }
        }else{
            boolean flag6 = this.currentWidth + i <= this.maxWidth;
            boolean flag7 = this.currentHeight + i <= this.maxHeight;

            if(!flag6 && !flag7){
                return false;
            }

            flag1 = flag6 && (flag || this.currentWidth <= this.currentHeight);
        }

        j = Math.max(p_94311_1_.getWidth(), p_94311_1_.getHeight());

        if(MathHelper.roundUpToPowerOfTwo((flag1 ? this.currentHeight : this.currentWidth) + j) > (flag1 ? this.maxHeight : this.maxWidth)){
            return false;
        }else{
            AtlasStitcher.Slot slot;

            if(flag1){
                if(p_94311_1_.getWidth() > p_94311_1_.getHeight()){
                    if(rotate){
                        p_94311_1_.rotate();
                    }
                }

                if(this.currentHeight == 0){
                    this.currentHeight = p_94311_1_.getHeight();
                }

                slot = new AtlasStitcher.Slot(this.currentWidth, 0, p_94311_1_.getWidth(), this.currentHeight);
                this.currentWidth += p_94311_1_.getWidth();
            }else{
                slot = new AtlasStitcher.Slot(0, this.currentHeight, this.currentWidth, p_94311_1_.getHeight());
                this.currentHeight += p_94311_1_.getHeight();
            }

            slot.addSlot(p_94311_1_);
            this.stitchSlots.add(slot);
            return true;
        }
    }

    @SideOnly(Side.CLIENT)
    public class Holder implements Comparable{

        private final Texture theTexture;
        private final int width;
        private final int height;
        private final int mipmapLevelHolder;
        private boolean rotated;
        private float scaleFactor = 1.0F;
        private static final String __OBFID = "CL_00001055";

        public Holder(Texture p_i45094_1_, int p_i45094_2_){
            this.theTexture = p_i45094_1_;
            this.width = p_i45094_1_.getIconWidth();
            this.height = p_i45094_1_.getIconHeight();
            this.mipmapLevelHolder = p_i45094_2_;
            this.rotated = rotate ? AtlasStitcher.getMipmapDimension(this.height, p_i45094_2_) > AtlasStitcher.getMipmapDimension(this.width, p_i45094_2_) : false;
        }

        public Texture getAtlasSprite(){
            return this.theTexture;
        }

        public int getWidth(){
            return rotate ? (this.rotated ? AtlasStitcher.getMipmapDimension((int)((float)this.height * this.scaleFactor), this.mipmapLevelHolder) : AtlasStitcher.getMipmapDimension((int)((float)this.width * this.scaleFactor), this.mipmapLevelHolder)) : AtlasStitcher.getMipmapDimension((int)((float)this.width * this.scaleFactor), this.mipmapLevelHolder);
        }

        public int getHeight(){
            return rotate ? (this.rotated ? AtlasStitcher.getMipmapDimension((int)((float)this.width * this.scaleFactor), this.mipmapLevelHolder) : AtlasStitcher.getMipmapDimension((int)((float)this.height * this.scaleFactor), this.mipmapLevelHolder)) : AtlasStitcher.getMipmapDimension((int)((float)this.height * this.scaleFactor), this.mipmapLevelHolder);
        }

        public void rotate(){
            if(rotate){
                this.rotated = !this.rotated;
            }
        }

        public boolean isRotated(){
            return rotate ? this.rotated : false;
        }

        public void setNewDimension(int p_94196_1_){
            if(this.width > p_94196_1_ && this.height > p_94196_1_){
                this.scaleFactor = (float)p_94196_1_ / (float)Math.min(this.width, this.height);
            }
        }

        public String toString(){
            return "Holder{width=" + this.width + ", height=" + this.height + '}';
        }

        public int compareTo(AtlasStitcher.Holder p_compareTo_1_){
            int i;

            if(this.getHeight() == p_compareTo_1_.getHeight()){
                if(this.getWidth() == p_compareTo_1_.getWidth()){
                    if(this.theTexture.getIconName() == null){
                        return p_compareTo_1_.theTexture.getIconName() == null ? 0 : -1;
                    }

                    return this.theTexture.getIconName().compareTo(p_compareTo_1_.theTexture.getIconName());
                }

                i = this.getWidth() < p_compareTo_1_.getWidth() ? 1 : -1;
            }else{
                i = this.getHeight() < p_compareTo_1_.getHeight() ? 1 : -1;
            }

            return i;
        }

        public int compareTo(Object p_compareTo_1_){
            return this.compareTo((AtlasStitcher.Holder)p_compareTo_1_);
        }
    }

    @SideOnly(Side.CLIENT)
    public static class Slot{

        private final int originX;
        private final int originY;
        private final int width;
        private final int height;
        private List subSlots;
        private AtlasStitcher.Holder holder;
        private static final String __OBFID = "CL_00001056";

        public Slot(int p_i1277_1_, int p_i1277_2_, int p_i1277_3_, int p_i1277_4_){
            this.originX = p_i1277_1_;
            this.originY = p_i1277_2_;
            this.width = p_i1277_3_;
            this.height = p_i1277_4_;
        }

        public AtlasStitcher.Holder getStitchHolder(){
            return this.holder;
        }

        public int getOriginX(){
            return this.originX;
        }

        public int getOriginY(){
            return this.originY;
        }

        public boolean addSlot(AtlasStitcher.Holder p_94182_1_){
            if(this.holder != null){
                return false;
            }else{
                int i = p_94182_1_.getWidth();
                int j = p_94182_1_.getHeight();

                if(i <= this.width && j <= this.height){
                    if(i == this.width && j == this.height){
                        this.holder = p_94182_1_;
                        return true;
                    }else{
                        if(this.subSlots == null){
                            this.subSlots = new ArrayList(1);
                            this.subSlots.add(new AtlasStitcher.Slot(this.originX, this.originY, i, j));
                            int k = this.width - i;
                            int l = this.height - j;

                            if(l > 0 && k > 0){
                                int i1 = Math.max(this.height, k);
                                int j1 = Math.max(this.width, l);

                                if(i1 >= j1){
                                    this.subSlots.add(new AtlasStitcher.Slot(this.originX, this.originY + j, i, l));
                                    this.subSlots.add(new AtlasStitcher.Slot(this.originX + i, this.originY, k, this.height));
                                }else{
                                    this.subSlots.add(new AtlasStitcher.Slot(this.originX + i, this.originY, k, j));
                                    this.subSlots.add(new AtlasStitcher.Slot(this.originX, this.originY + j, this.width, l));
                                }
                            }else if(k == 0){
                                this.subSlots.add(new AtlasStitcher.Slot(this.originX, this.originY + j, i, l));
                            }else if(l == 0){
                                this.subSlots.add(new AtlasStitcher.Slot(this.originX + i, this.originY, k, j));
                            }
                        }

                        Iterator iterator = this.subSlots.iterator();
                        AtlasStitcher.Slot slot;

                        do{
                            if(!iterator.hasNext()){
                                return false;
                            }

                            slot = (AtlasStitcher.Slot)iterator.next();
                        }while(!slot.addSlot(p_94182_1_));

                        return true;
                    }
                }else{
                    return false;
                }
            }
        }

        /**
         * Gets the slot and all its subslots
         */
        public void getAllStitchSlots(List p_94184_1_){
            if(this.holder != null){
                p_94184_1_.add(this);
            }else if(this.subSlots != null){
                Iterator iterator = this.subSlots.iterator();

                while(iterator.hasNext()){
                    AtlasStitcher.Slot slot = (AtlasStitcher.Slot)iterator.next();
                    slot.getAllStitchSlots(p_94184_1_);
                }
            }
        }

        public String toString(){
            return "Slot{originX=" + this.originX + ", originY=" + this.originY + ", width=" + this.width + ", height=" + this.height + ", texture=" + this.holder + ", subSlots=" + this.subSlots + '}';
        }
    }
}
Код:
import com.google.common.collect.Lists;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.data.AnimationFrame;
import net.minecraft.client.resources.data.AnimationMetadataSection;
import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory;
import net.minecraft.util.IIcon;
import net.minecraft.util.ReportedException;
import net.minecraft.util.ResourceLocation;

@SideOnly(Side.CLIENT)
public class Texture implements IIcon{

    private final String iconName;
    public BufferedImage data;
    private AnimationMetadataSection animationMetadata;
    protected boolean rotated;
    private boolean useAnisotropicFiltering;
    protected int originX;
    protected int originY;
    protected int width;
    protected int height;
    private float minU;
    private float maxU;
    private float minV;
    private float maxV;
    protected int frameCounter;
    protected int tickCounter;
    private static final String __OBFID = "CL_00001062";

    protected Texture(String p_i1282_1_, BufferedImage image, int offset){
        this.iconName = p_i1282_1_;
        loadSprite(image, offset);
    }

    public void initSprite(int p_110971_1_, int p_110971_2_, int p_110971_3_, int p_110971_4_, boolean p_110971_5_){
        this.originX = p_110971_3_;
        this.originY = p_110971_4_;
        this.rotated = p_110971_5_;
        float f = (float)(0.009999999776482582D / (double)p_110971_1_);
        float f1 = (float)(0.009999999776482582D / (double)p_110971_2_);
        this.minU = (float)p_110971_3_ / (float)((double)p_110971_1_) + f;
        this.maxU = (float)(p_110971_3_ + this.width) / (float)((double)p_110971_1_) - f;
        this.minV = (float)p_110971_4_ / (float)p_110971_2_ + f1;
        this.maxV = (float)(p_110971_4_ + this.height) / (float)p_110971_2_ - f1;

        if(this.useAnisotropicFiltering){
            float f2 = 8.0F / (float)p_110971_1_;
            float f3 = 8.0F / (float)p_110971_2_;
            this.minU += f2;
            this.maxU -= f2;
            this.minV += f3;
            this.maxV -= f3;
        }
    }

    public void copyFrom(Texture p_94217_1_){
        this.originX = p_94217_1_.originX;
        this.originY = p_94217_1_.originY;
        this.width = p_94217_1_.width;
        this.height = p_94217_1_.height;
        this.rotated = p_94217_1_.rotated;
        this.minU = p_94217_1_.minU;
        this.maxU = p_94217_1_.maxU;
        this.minV = p_94217_1_.minV;
        this.maxV = p_94217_1_.maxV;
    }

    /**
     * Returns the X position of this icon on its texture sheet, in pixels.
     */
    public int getOriginX(){
        return this.originX;
    }

    /**
     * Returns the Y position of this icon on its texture sheet, in pixels.
     */
    public int getOriginY(){
        return this.originY;
    }

    /**
     * Returns the width of the icon, in pixels.
     */
    public int getIconWidth(){
        return this.width;
    }

    /**
     * Returns the height of the icon, in pixels.
     */
    public int getIconHeight(){
        return this.height;
    }

    /**
     * Returns the minimum U coordinate to use when rendering with this icon.
     */
    public float getMinU(){
        return this.minU;
    }

    /**
     * Returns the maximum U coordinate to use when rendering with this icon.
     */
    public float getMaxU(){
        return this.maxU;
    }

    /**
     * Gets a U coordinate on the icon. 0 returns uMin and 16 returns uMax. Other
     * arguments return in-between values.
     */
    public float getInterpolatedU(double p_94214_1_){
        float f = this.maxU - this.minU;
        return this.minU + f * (float)p_94214_1_ / 16.0F;
    }

    /**
     * Returns the minimum V coordinate to use when rendering with this icon.
     */
    public float getMinV(){
        return this.minV;
    }

    /**
     * Returns the maximum V coordinate to use when rendering with this icon.
     */
    public float getMaxV(){
        return this.maxV;
    }

    /**
     * Gets a V coordinate on the icon. 0 returns vMin and 16 returns vMax. Other
     * arguments return in-between values.
     */
    public float getInterpolatedV(double p_94207_1_){
        float f = this.maxV - this.minV;
        return this.minV + f * ((float)p_94207_1_ / 16.0F);
    }

    public String getIconName(){
        return this.iconName;
    }

    public void setIconWidth(int p_110966_1_){
        this.width = p_110966_1_;
    }

    public void setIconHeight(int p_110969_1_){
        this.height = p_110969_1_;
    }

    public void loadSprite(BufferedImage image, int offset){
        this.resetSprite();
        int i = image.getWidth();
        int j = image.getHeight();
        this.width = i + offset;
        this.height = j + offset;

        data = image;
    }

    private void resetSprite(){
        this.animationMetadata = null;
        this.frameCounter = 0;
        this.tickCounter = 0;
    }

    /**
     * The result of this function determines is the below 'load' function is
     * called, and the default vanilla loading code is bypassed completely.
     *
     * @param manager
     * @param location
     * @return True to use your own custom load code and bypass vanilla loading.
     */
    public boolean hasCustomLoader(IResourceManager manager, ResourceLocation location){
        return false;
    }

    /**
     * Load the specified resource as this sprite's data. Returning false from this
     * function will prevent this icon from being stitched onto the master texture.
     *
     * @param manager
     *            Main resource manager
     * @param location
     *            File resource location
     * @return False to prevent this Icon from being stitched
     */
    public boolean load(IResourceManager manager, ResourceLocation location){
        return true;
    }
}
Использую так:
Код:
File[] imageFiles = new File("textures/").listFiles(filter -> !filter.getName().equals("atlas.png") && filter.getName().endsWith(".png"));

        List<BufferedImage> images = Lists.newArrayListWithCapacity(imageFiles.length);
        for(File f : imageFiles){
            images.add(ImageIO.read(f));
        }

        AtlasStitcher stitcher = new AtlasStitcher(8192, 8192, true, 0, 0);
        stitcher.rotate = false;

        for(int i = 0; i < images.size(); i++){
            BufferedImage image = images.get(i);
            stitcher.addSprite(new Texture(imageFiles[i].getName(), image, 0));
        }

        stitcher.doStitch();

        System.out.println(stitcher.getCurrentWidth() + " :: " + stitcher.getCurrentHeight());

        BufferedImage atlasImage = new BufferedImage(stitcher.getCurrentWidth(), stitcher.getCurrentHeight(), BufferedImage.TYPE_INT_ARGB);
        Iterator<Texture> i = stitcher.getStichSlots().iterator();
        while(i.hasNext()){
            Texture texture = i.next();
            atlasImage.createGraphics().drawImage(texture.data, null, texture.getOriginX(), texture.getOriginY());
        }
        ImageIO.write(atlasImage, "PNG", new File("atlas.png"));
Потом гружу модель(у меня свой формат), пробегаюсь по всем вершинам и применяю код, который в посту вышел кинул.
 
Сверху