Drawing on one face of a cube

Hi all,

Is there a way to paint on one side of a cube? I am almost sure there is but how??

then, can I do my painting in a way like painting on a swing panel (using a graphics object)?

I know I can paint on an image then load this image but this solution costs memory since "BufferedImage"s consumes a lot of memory (4 bytes for a pixel), is there a better solution?

thanks in advance

How many bytes per pixel do you think a texture costs?  :smiley:



You can use Irrisor's ImageGraphics class to use Java 2D to paint something and then put it on a texture. If not already possible with that class, it should be easy to add that you can discard the Java2D Image after that…

You will need a BufferedImage either way. So optimizing memory is usually not the problem. ImageGraphics does not optimize that, also. But ImageGraphics optimizes performance regarding the update process, if you want to draw interactively. What exactly do you want to do? Paint with the mouse - interactively? Or do you want to paint programmatically and put the whole image on the cube at once?

I want to draw programatically on one face of cube not on all faces. textures will put the image on all faces. can I do painting on only one face?

then I speak about memory optimization no performance optimization so, I want to avoid BufferedImage completely.



thanks all.

Well you can draw directly into a ByteBuffer if you want to be the most efficient woith regards to memory :wink:



Textures are only applied where you tell them to be applied, using texture coordinates. For an easy fix you could just put a quad where you want to draw.

can you please give some code on how to paint to ByteBuffer and how to apply a texture to one face?

Well, look in the javadoc of ByteBuffer… you'll have to set the bytes by hand. I'd seriously suggest using some kind of intermidiate API like Java2D (and thus BufferedImage), or something else.



I'd also suggest reading up a bit on OpenGL (how texturing works), and doing some jME tutorials.

The cube primitive uses shared vertices with shared texcoords, so painting to just one face is not really possible.  Instead, you should create a cube of quads (similar to skybox, but with outward normals).

It is possible. You can change the Texture coordinates, so that different faces use different parts of the texture, and then draw just in a part of the Texture belonging to the right face.



See http://www.jmonkeyengine.com/jmeforum/index.php?topic=5660.0 for a helper class.

I stand corrected… But if you want to change all the faces, it's not.

Agreed, the solution with 6 Quads is much easier, if you want to draw to all faces. But with the right remapping all faces would point to different parts of the texture, so technically it is possible. Managing this is another question (and probably a nightmare). Don't force me to write a proof of concept  :stuck_out_tongue:

I speak about memory optimization no performance optimization


All my proffesors always tried to conviced me and never had any success... Maybe I was the stubborn one or maybe it's something you have to learn on your own... But here it goes:
Stop worrying about optimizing (memory or otherwise) until you have something working. When you have a working piece, then you can do some bechmarks and optimize if needed. Premature optimization is something that always gets people in trouble.
Landei said:

Don't force me to write a proof of concept  :P


Please do!  (Or at least just give the uvs :) )  If there is a way to map the existing uv values so that a Box object has a unique same-ratioed image on each face all from the same texture in the same texture unit, (without using shaders or multitexturing in other words) then I'd love to make that an option in Box.

This is a little test class. It organizes the 6 faces in a rectangle of 64 by 664 (will be rescaled to 64512). If you hit the key 0…5, the corresponding face gets a random color. Not exactly "drawing", but good enough as starting point…



Note that you need the BoxRemapper from http://www.jmonkeyengine.com/jmeforum/index.php?topic=5660.0 as helper class.



import com.jme.app.SimpleGame;
import com.jme.image.Texture;
import com.jme.input.KeyInput;
import com.jme.input.controls.GameControl;
import com.jme.input.controls.GameControlManager;
import com.jme.input.controls.binding.KeyboardBinding;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Spatial;
import com.jme.scene.shape.Box;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.GameTaskQueueManager;
import com.jme.util.TextureManager;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.ConcurrentModificationException;
import java.util.Random;
import java.util.concurrent.Callable;

/**
 *
 * @author DGronau
 */
public class BoxTest extends SimpleGame {

    private static final Random RANDOM = new Random();
    private static final Font FONT = new Font("Arial Unicode MS", Font.PLAIN, 30);
    private Color[] colors = new Color[]{ Color.red, Color.green,
       Color.blue, Color.yellow, Color.white, Color.orange };
    private Box box;
    private int[] keys = new int[]{
        KeyInput.KEY_0, KeyInput.KEY_1, KeyInput.KEY_2,
        KeyInput.KEY_3, KeyInput.KEY_4, KeyInput.KEY_5
    };
   
    private GameControl[] control = new GameControl[6];
   
    protected void simpleInitGame() {
        box = new Box("box", new Vector3f(), 10, 10, 10);
        BoxRemapper remapper = new BoxRemapper();
        GameControlManager manager = new GameControlManager();
        for (int i = 0; i < 6; i++) {
           float top = i / 6f;
           float bottom = (i+1) / 6f;
           remapper.setSide(1 << i, 1, bottom, 0, bottom, 0, top, 1, top);
           control[i] = manager.addControl("control" + 1);
           control[i].addBinding(new KeyboardBinding(keys[i]));
        } 
        MaterialState ms = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
        ms.setEmissive(ColorRGBA.white);
        box.setRenderState(ms);
        setTexture(box);
        remapper.remap(box);
        rootNode.attachChild(box);
    }
   
    public static void main(String... args) {
        BoxTest app = new BoxTest();
        app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
        app.start();
    }
   
    private void setTexture(final Spatial s) {
        final BufferedImage bi = new BufferedImage(64, 6*64, BufferedImage.TYPE_INT_ARGB);
        Graphics2D bg = (Graphics2D) bi.getGraphics();
        bg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        bg.setFont(FONT);
        for (int i = 0; i < 6; i++) {
            bg.setColor(colors[i]);
            bg.fillRect(0, i*64, 64, (i+1) *64);
            bg.setColor(Color.black);
            bg.drawString(""+i, 28, 64*i + 38);
        }
        bg.dispose();
        GameTaskQueueManager.getManager().update(new Callable<Object>() {
            public Object call() throws Exception {
                try {
                    TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
                    Texture t = TextureManager.loadTexture(bi, Texture.MM_LINEAR, Texture.MM_LINEAR, 1, false);
                    ts.setTexture(t);
                    s.setRenderState(ts);
                    s.updateRenderState();
                    TextureState oldTs = (TextureState)s.getRenderState(RenderState.RS_TEXTURE);
                    if (oldTs != null) {
                        TextureManager.releaseTexture(oldTs.getTexture());
                        oldTs.deleteAll(true);
                    }
                } catch (ConcurrentModificationException ex) {
                    ex.printStackTrace();
                }
                return null;
            }
        });
    }
   
    @Override
    public void simpleUpdate() {
        for (int i = 0; i < 6; i++) {
            if (control[i].getValue() != 0) {
                System.out.println(i);
                colors[i] = new Color(RANDOM.nextInt(256), RANDOM.nextInt(256), RANDOM.nextInt(256));
                setTexture(box);
            }
        }
    }

}

to avoid rescaling, use:



        for (int i = 0; i < 6; i++) {
           float top = i / 8f;
           float bottom = (i+1) / 8f;
           remapper.setSide(1 << i, 1, bottom, 0, bottom, 0, top, 1, top);
           control[i] = manager.addControl("control" + 1);
           control[i].addBinding(new KeyboardBinding(keys[i]));
        } 


and


final BufferedImage bi = new BufferedImage(64, 512, BufferedImage.TYPE_INT_ARGB);



This gives better results for the edges, too.

OK, I derived a MultiFaceBox class from Box, which uses a fixed mapping (8 squares stacked vertically, the faces map to the first 6 squares). The class needs no BoxRemapper and is very simple:


import com.jme.math.Vector3f;
import com.jme.scene.shape.Box;
import java.nio.FloatBuffer;

public class MultiFaceBox extends Box {

Very cool.  Thanks for rising to the challenge and proving me wrong.  ;)  Mind if we donate this to the code base?

I'd feel honoured  :smiley: