How does Mindcraft turn 2D image into 3D Object

Can anyone tell me how the Mindcraft weapon images (flat) turn into 3D objects.

If you look at the Mindcraft weapons that are attached to the View they have a thinkness to them.

I am trying to figure out if they are new models that are created or some how they take a 2D image and use its transparency to build a thick 3D model.

If it is in fact a 2D image with transparency and just displayed as a object in the game can someone tell me how to do that in jMonkey.

Thanks,
Greg

I’m 99.9% sure that it is generated. I’m pretty sure all they do is create a voxel shape based on the pixels. All you would have to do is go pixel by pixel and generate a mesh with the vertex colors of the given pixel if it wasn’t transparent. You could probably do something similar with a displacement filter if you wanted to, but it would probably be more of a performance hit per frame.

1 Like

Minecraft creates “thick textures” by generating a 16x16 sheet of voxels, and coloring them based on the pixel color of the defined sprite.

Transparency is a lack of color, so the transparent voxels are invisible. It’s better to optimize it further by generating “box shapes”, attaching them to a node and translating them locally to have them correctly displayed. You could use Java’s ImageIO to load a texture, and extract the color information of the pixels by iterating through them and storing them into an array. Then you could use these colors when generating the box shapes, and using the color on an unshaded material.

2 Likes

im sure theyre just regular 3d models !?!?! anything other than that would seem way too complicated and overkill to me.

@icamefromspace said: im sure theyre just regular 3d models !?!?! anything other than that would seem way too complicated and overkill to me.

I’m sure you are wrong :slight_smile: Make your own texture pack and you’ll see it happens like @TrashCaster describes.

Nope. Just voxel sheets rendered on the fly. I’ve seen the source code. I make Minecraft mods.

Edit:Can someone help me figure out how to do the texture Coordinates please on a 16x16 pixel png

I got a test case working except for texturing
Does anyone know how to use a 16x16 image as a texture

I am following this link on making a custom mesh but cant figure out how to uv map a texture that is a single pixel.
https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:custom_meshes

It just seems to bleed to the next pixel instead of using it as a full color
it might also be a rounding issue by using float but I am not sure

From what I understand the texture is normalized to 1 so 1x1 instead of 16x16 . To convert I divide each pixel position by 16

here is the image notice how the colors are off.

Here is the output texture coordinates
[java]
-Front-
0.4375,0.4375 0.4375,0.4375 0.4375,0.4375 0.4375,0.4375
-Back-
0.4375,0.4375 0.4375,0.4375 0.4375,0.4375 0.4375,0.4375
-Front-
0.5,0.4375 0.5,0.4375 0.5,0.4375 0.5,0.4375
-Back-
0.5,0.4375 0.5,0.4375 0.5,0.4375 0.5,0.4375
-Front-
0.5625,0.4375 0.5625,0.4375 0.5625,0.4375 0.5625,0.4375
-Back-
0.5625,0.4375 0.5625,0.4375 0.5625,0.4375 0.5625,0.4375
-Front-
0.625,0.4375 0.625,0.4375 0.625,0.4375 0.625,0.4375
-Back-
0.625,0.4375 0.625,0.4375 0.625,0.4375 0.625,0.4375
[/java]

[java]

package imageprocess;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.texture.Texture;
import com.jme3.util.BufferUtils;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.IOException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;

/**
*
*
*/
public class ImageProcessTestMain extends SimpleApplication {

// String IMAGE_NAME = “diamond_pickaxe.png”;
String IMAGE_NAME = “test.png”;

public static void main(String[] args) {
    ImageProcessTestMain app = new ImageProcessTestMain();
    app.start();
}

//Code From internet to get Pixels from image
private Sprite convertTo2DWithoutUsingGetRGB(BufferedImage image) {

    final byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
    final int width = image.getWidth();
    final int height = image.getHeight();
    final boolean hasAlphaChannel = image.getAlphaRaster() != null;

    int[][] result = new int[height][width];
    if (hasAlphaChannel) {
        final int pixelLength = 4;
        for (int pixel = 0, row = 0, col = 0; pixel < pixels.length; pixel += pixelLength) {
            int argb = 0;
            argb += (((int) pixels[pixel] & 0xff) << 24); // alpha
            argb += ((int) pixels[pixel + 1] & 0xff); // blue
            argb += (((int) pixels[pixel + 2] & 0xff) << 8); // green
            argb += (((int) pixels[pixel + 3] & 0xff) << 16); // red
            result[row][col] = argb;
            col++;
            if (col == width) {
                col = 0;
                row++;
            }
        }
    } else {
        final int pixelLength = 3;

        //Added +3
        for (int pixel = 0, row = 0, col = 0; pixel + 3 < pixels.length; pixel += pixelLength) {
            int argb = 0;
            argb += -16777216; // 255 alpha
            argb += ((int) pixels[pixel] & 0xff); // blue
            argb += (((int) pixels[pixel + 1] & 0xff) << 8); // green
            argb += (((int) pixels[pixel + 2] & 0xff) << 16); // red
            result[row][col] = argb;
            col++;
            if (col == width) {
                col = 0;
                row++;
            }
        }
    }
    Sprite sprite = new Sprite();
    sprite.width = width;
    sprite.height = height;
    sprite.array = result;
    sprite.hasAlpha = hasAlphaChannel;
    return sprite;
}

@Override
public void simpleInitApp() {
    cam.setLocation(new Vector3f(10, 10, 20));
    try {
        URL url = ImageProcessTestMain.class.getResource(IMAGE_NAME);
        System.out.println("URL " + url);
        BufferedImage voxelTestImage = ImageIO.read(url);
        Sprite sprite = convertTo2DWithoutUsingGetRGB(voxelTestImage);
        this.createMesh(sprite);
        	
    viewPort.setBackgroundColor(ColorRGBA.White);
        System.out.println("Done");
        flyCam.setMoveSpeed(20f);
    } catch (IOException ex) {
        Logger.getLogger(ImageProcessTestMain.class.getName()).log(Level.SEVERE, null, ex);
    }
}

private void createMesh(Sprite sprite) {
    Mesh mesh = new Mesh();
    int pixelCount = 0;
    for (int row = 0; row < sprite.height; row++) {
        for (int column = 0; column < sprite.width; column++) {
            if (sprite.array[row][column] != 0) {
                pixelCount++;
            }
        }
    }
    //Verticies  - TODO Optimized to not have duplicate verticies
    int vertexCountTwo = 0;
    Vector3f[] vertices = new Vector3f[pixelCount * 8];
    for (int row = 0; row < sprite.height; row++) {
        for (int column = 0; column < sprite.width; column++) {
            if (sprite.array[row][column] != 0) {
                //Front Side
                vertices[vertexCountTwo++] = new Vector3f(column, row, 0);
                vertices[vertexCountTwo++] = new Vector3f(column + 1, row, 0);
                vertices[vertexCountTwo++] = new Vector3f(column, row + 1, 0);
                vertices[vertexCountTwo++] = new Vector3f(column + 1, row + 1, 0);
                //Back Side 
                vertices[vertexCountTwo++] = new Vector3f(column, row, -1);
                vertices[vertexCountTwo++] = new Vector3f(column + 1, row, -1);
                vertices[vertexCountTwo++] = new Vector3f(column, row + 1, -1);
                vertices[vertexCountTwo++] = new Vector3f(column + 1, row + 1, -1);                   
            }
        }
    }
    //Texture  - 1 x 1 is the whole image
    Vector2f[] texCoord = new Vector2f[pixelCount * 8];
    int texCoordCount = 0;
    int texX = 0;
    int texY = 0;
    for (int row = 0; row < sprite.height; row++) {
        for (int column = 0; column < sprite.width; column++) {

            if (sprite.array[row][column] != 0) {
                texX  = column+1;
                texY = row+1;
                 System.out.println("\n-Front-");
                //Front Side
                texCoord[texCoordCount++] = convertTexCoor(texX, texY, sprite.height, sprite.width);
                texCoord[texCoordCount++] = convertTexCoor(texX, texY, sprite.height, sprite.width);
                texCoord[texCoordCount++] = convertTexCoor(texX, texY , sprite.height, sprite.width);
                texCoord[texCoordCount++] = convertTexCoor(texX, texY, sprite.height, sprite.width);
                 System.out.println("\n-Back-");
                //Back Side
                texCoord[texCoordCount++] = convertTexCoor(texX, texY, sprite.height, sprite.width);
                texCoord[texCoordCount++] = convertTexCoor(texX, texY, sprite.height, sprite.width);
                texCoord[texCoordCount++] = convertTexCoor(texX, texY , sprite.height, sprite.width);
                texCoord[texCoordCount++] = convertTexCoor(texX, texY, sprite.height, sprite.width);
               
                
            } 
        }
    }

    //Index - Convert to Triangles

    int[] indexes = new int[pixelCount * (6 + 6+ 6 +6 +6 +6)]; //Front + Back + Right + Left + Top + Bottom

    int indexCount = 0;
    int newPixelCount = 0;
    for (int row = 0; row < sprite.height; row++) {
        for (int column = 0; column < sprite.width; column++) {
            if (sprite.array[row][column] != 0) {
                int offset = newPixelCount * 8;
                //Front Side
                indexes[indexCount++] = 2 + offset;
                indexes[indexCount++] = 0 + offset;
                indexes[indexCount++] = 1 + offset;

                indexes[indexCount++] = 1 + offset;
                indexes[indexCount++] = 3 + offset;
                indexes[indexCount++] = 2 + offset;
                
                //Back Side - Reverse
                indexes[indexCount++] = 5 + offset;
                indexes[indexCount++] = 4 + offset;
                indexes[indexCount++] = 6 + offset;

                indexes[indexCount++] = 6 + offset;
                indexes[indexCount++] = 7 + offset;
                indexes[indexCount++] = 5 + offset;
                
                //Right Side
                indexes[indexCount++] = 3 + offset;
                indexes[indexCount++] = 1 + offset;
                indexes[indexCount++] = 5 + offset;

                indexes[indexCount++] = 5 + offset;
                indexes[indexCount++] = 7 + offset;
                indexes[indexCount++] = 3 + offset;
                
                
                 //Left Side
                indexes[indexCount++] = 6 + offset;
                indexes[indexCount++] = 4 + offset;
                indexes[indexCount++] = 0 + offset;

                indexes[indexCount++] = 0 + offset;
                indexes[indexCount++] = 2 + offset;
                indexes[indexCount++] = 6 + offset;
                
                
                //Top Side
                indexes[indexCount++] = 6 + offset;
                indexes[indexCount++] = 2 + offset;
                indexes[indexCount++] = 3 + offset;

                indexes[indexCount++] = 3 + offset;
                indexes[indexCount++] = 7 + offset;
                indexes[indexCount++] = 6 + offset;
                
                 //Bottom Side
                indexes[indexCount++] = 0 + offset;
                indexes[indexCount++] = 4 + offset;
                indexes[indexCount++] = 5 + offset;

                indexes[indexCount++] = 5 + offset;
                indexes[indexCount++] = 1 + offset;
                indexes[indexCount++] = 0 + offset;
                
                newPixelCount++;

            }
        }
    }
    System.out.println("newPixelCount = " + newPixelCount);
    System.out.println("indexCount = " + indexCount);

    //Triangle Index - The indices 0,1,2,3 stand for the four vertices 
    //that you specified for the quad in vertices[].
    //int [] indexes = { 2,0,1, 1,3,2 , 6,4,5, 5,7,6};

    mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
    mesh.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoord));
    mesh.setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(indexes));
    mesh.updateBound();

    //Create Spacial
    Geometry geo = new Geometry("OurMesh", mesh); // using our custom mesh object
    Material mat = new Material(assetManager,
            "Common/MatDefs/Misc/Unshaded.j3md");

// TextureKey tk = new TextureKey(“Textures/diamond_pickaxe.png”, false);
TextureKey tk = new TextureKey(“Textures/test.png”, false);
Texture cube1Tex = assetManager.loadTexture(tk);
mat.setTexture(“ColorMap”, cube1Tex);
geo.setMaterial(mat);
rootNode.attachChild(geo);
}

//Take the column and row and divide by the height and width
private Vector2f convertTexCoor(int column, int row, int height, int width) {
    float x = (float) column / width;  //cast to float otherwise it will always be zero
    float y = (float) row / height;
    System.out.print(x+","+y + " ");
    return new Vector2f(x, y);
}

private class Sprite {
    public int[][] array;
    public boolean hasAlpha = false;
    public int width;
    public int height;
}

}
[/java]

Here is a video showing the pickaxe from minecraft that I loaded using the above code a 16x16 pixel png image look how the color is off

[video]jMonkey 2D Image to 3D mesh - YouTube

That’s odd how the color is off. Try to open the texture in GIMP or something like that, and check the image mode. If it says RGB then it should be fine, but if it says Indexed, that may be why.

youd probably want to disable mipmapping for minifaction/magnification

http://www.jmonkeyengine.org/doc/com/jme/image/Texture.html#setMinificationFilter(com.jme.image.Texture.MinificationFilter)

1 Like

Finally Working !!
@wezule you da man!
I added
cube1Tex.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
cube1Tex.setMagFilter(Texture.MagFilter.Nearest);

[java]
Material mat = new Material(assetManager,
“Common/MatDefs/Misc/Unshaded.j3md”);
TextureKey tk = new TextureKey(“Textures/diamond_pickaxe.png”, false);

    Texture cube1Tex = assetManager.loadTexture(tk);
    cube1Tex.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
    cube1Tex.setMagFilter(Texture.MagFilter.Nearest);
    mat.setTexture("ColorMap", cube1Tex);
    geo.setMaterial(mat);

[/java]

[video]jMonkey 2D image to 3D Image finally working - YouTube