A swing compatible canvas

I think it is necessary to provide an equivalent of the java3d JCanvas.



Currently you cannot add with good results a canvas in a swing component…



Is such a feature planned ? when ?

Our current JMECanvas works just fine for all of our Swing tool work here.  Not sure what more you want?

I don't want to insert swing elements in my canvas,

I want to put my canvas in a JPanel for example.



Currently the canvas behaves like an awt component, or awt is from far less used than swing.

Both Xith3D and Java3D provide swing-compatible canvas.



'hope it's more clear ??

samicho said:

Currently the canvas behaves like an awt component


Do you want JMECanvas to behave like a lightweight component? That is not possible with the current JME/Swing integration classes. The JMECanvas, being an OpenGL direct draw (huh, am I mixing things up here?) window, is just about as heavyweight as it gets :)

To work around that, you could read the following link to get a basic understanding on how to mix lightweight and heavyweight components without too much headache:
http://java.sun.com/products/jfc/tsc/articles/mixing/

Or, you could make a lightweight JMECanvas implementation yourself, however I am not sure if that is even possible with java 5...

I suppose you could go back to the old jME canvas stuff I wrote a couple years ago that did off-screen rendering and copied that to an image that was drawn to a panel…  Real nice and slow though…  :slight_smile:

Does that mean that you cannot add jME window as a client window in an application?



I've been messing around with the DisplaySystem.createCanvas() method (using lwjgl) and it doesn't seem to work.  For one the renderer is not initialized like it is in the createWindow() method.



My requirements are to add a 3D client window into an existing Swing application.  Am I barking up the wrong tree here?



thanks.



P.S. I think this is an awesome package you've created here.  I just hope I can use it.

Well, we add jme canvases to swing apps all the time here, so you definitely can.  If you check out RenParticleEditor you can see an example of this.

renanse said:

Well, we add jme canvases to swing apps all the time here, so you definitely can.  If you check out RenParticleEditor you can see an example of this.


I appreciate the response and I don't want to sound like an ingrate but...

That's a lot of code.  Is there something that you could point me to that boils it down a little?

thanks.

The jmetest.util.JMESwingTest is a lot smaller.

renanse said:

The jmetest.util.JMESwingTest is a lot smaller.


Thanks.  I really appreciate your help.

OK, after speaking with my client at length on this, he wants the 3D views to be displayed in JInternalFrames.  This is more important than the downside that this will be slower.



I found a thread on the LWJGL forum showing how to render into a JPanel.  I tried to adapt this to work kind of the way SimpleCanvasImpl works.  I feel like I am really close but it's just not working properly.  I get something in the view but it looks terrible.  Instead of the clear hills and valleys of the AWT version, I get abstract blobs that change as I move but don't give me any sense of where I am going.  I'm clearly missing something here.  Can anyone clue me in.  Also renanse, if you have implemented this already, could you point me to your code?



thanks.



import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

import javax.swing.JPanel;

import org.apache.log4j.Logger;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.PixelFormat;

import com.jme.renderer.Camera;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.system.lwjgl.LWJGLDisplaySystem;
import com.jme.util.Timer;

public abstract class ThreeDeeJPanel extends JPanel {
    private static final Logger LOGGER = Logger.getLogger(ThreeDeeJPanel.class);
   
    private static final long serialVersionUID = 1L;

    static {
        Sys.initialize();
    }

    /** the timer that controls animation */
    private javax.swing.Timer animationTimer = new javax.swing.Timer(10, new ActionListener() {
        /**
         * Instruct the component to paint its entire bounds
         *
         * @param e
         */
        public void actionPerformed(ActionEvent e) {
            paintImmediately(0, 0, getWidth(), getHeight());
        }
    });

    private Timer timer =  Timer.getTimer();
    /** the image that will be rendered to the swing component */
    private BufferedImage renderedImage;
    /** the int buffer that will host the captured opengl data */
    private IntBuffer intBuffer;
   
    private boolean isInitialized = false;
   
    protected int width;
    protected int height;
    private int bitDepth = 32;
    private int refreshRate = 60;
   
    private final int alphaBits = 0;
    private final int stencilBits = 0;
    private final int depthBufferBits = 0;
    private final int sampleBits = 0;

    protected Renderer renderer;
    protected Camera camera;
    protected Node rootNode;
   
    /**
     * Creates a JPanel that can be rendered to by LWJGL. It sets itself to
     * opaque by default so that Swing knows that we will render our entire
     * bounds and therefore doesn't need to do any checks
     */
    public ThreeDeeJPanel(final int width, final int height) {
        this.setSize(width, height);
        setOpaque(true);
    }

    private DisplayMode getMode() throws LWJGLException {

        DisplayMode[] modes = Display.getAvailableDisplayModes();

        int width = 800;
        int height = 600;
       
        for (int i = 0; i < modes.length; i++) {
            System.out.println(modes[i].getWidth() + " / " + modes[i].getHeight() + " / " + modes[i].getBitsPerPixel());

            if (modes[i].getWidth() == width && modes[i].getHeight() == height
                    && modes[i].getBitsPerPixel() == bitDepth
                    && (refreshRate == 0 || modes[i].getFrequency() == refreshRate)) {
                return modes[i];
            }
        }

        throw new RuntimeException("no mode found for width " + width + ", height " + height
                + ", bitdepth " + bitDepth + ", frequency " + refreshRate);
    }
   
    /**
     * @throws LWJGLException
     *
     */
    private void initComponent() throws LWJGLException {
        width = getWidth();
        height = getHeight();
       
        LOGGER.debug("Creating headless window " + width + "/" + height + "/" + bitDepth);
       
        DisplayMode mode = getMode();

        PixelFormat format = new PixelFormat(bitDepth, alphaBits, depthBufferBits, stencilBits,
                sampleBits);
       
        LWJGLDisplaySystem display = (LWJGLDisplaySystem) DisplaySystem.getDisplaySystem("lwjgl");
               
        display.createHeadlessWindow(width, height, bitDepth);
       
        renderer = display.getRenderer();
        LOGGER.debug("renderer: " + renderer);
       
        /**
         * Create a camera specific to the DisplaySystem that works with the
         * width and height
         */
        camera = renderer.createCamera(width, height);

        renderer.setCamera(camera);
       
        /*
         * create a buffered image that is of the size of this component
         * we will render directly into this buffered image in Swing
         * fashion so we can render our interface in Swing properly and get
         * the image data that is backing this buffered image so we can write
         * directly to the internal data structure with no unnecessary copy
         */
        renderedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        /* create an int buffer to store the captured data from OpenGL */
        intBuffer = ByteBuffer.allocateDirect(width * height * 4)
            .order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();

        /** Create rootNode */
        rootNode = new Node("rootNode");

        /**
         * Create a ZBuffer to display pixels closest to the camera above
         * farther ones.
         */
        ZBufferState buf = renderer.createZBufferState();
        buf.setEnabled(true);
        buf.setFunction(ZBufferState.CF_LEQUAL);

        init();
       
        rootNode.updateGeometricState(0.0f, true);
        rootNode.updateRenderState();
       
        animationTimer.start();
       
        isInitialized = true;
    }

    /**
     * override update to avoid clearing
     */
    public void update(Graphics g) {
        paint(g);
    }

    public void stop() {
        LOGGER.debug("Stopping animation timer");
        animationTimer.stop();
    }
   
    public void paintComponent(Graphics g) {
        LOGGER.trace("rendering");
        if (!isInitialized) {
            try {
                initComponent();
            } catch (LWJGLException e) {
                throw new RuntimeException(e);
            }
        } else {
            timer.update();
            float tpf = timer.getTimePerFrame();
           
            update();
           
            rootNode.updateGeometricState(tpf, true);
           
            renderer.clearBuffers();
            renderer.draw(rootNode);
           
            render();
           
            renderer.displayBackBuffer();
           
            /* make sure we're done drawing before capturing the frame */
            renderer.flush();
           
            intBuffer.clear();
           
            /* capture the result */
            renderer.grabScreenContents(intBuffer, 0, 0, width, height);
           
            /* Grab each pixel information and set it to the BufferedImage info */
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
                    renderedImage.setRGB(x, y, intBuffer.get((height - y - 1) * width + x));
                }
            }
           
            intBuffer.flip();
           
            /* draw the result to this component */
            g.drawImage(renderedImage, 0, 0, null);
        }
    }
   
    protected abstract void init();
   
    protected abstract void update();
   
    protected abstract void render();
}

If anyone is interested, it appears that Java 7 will support heavyweight containers in JInternalFrames.

dubwai said:

Also renanse, if you have implemented this already, could you point me to your code?


I am interested by any result where a 3D view is displayed in a JPanel, even slow.
Renanse, could you please give a link to the implementation of your old class that you mentionned earlier?

This should be it:



https://jme.dev.java.net/source/browse/jme/src/com/jme/util/JMEComponent.java?hideattic=0&rev=1.12&view=markup



It's old and may not compile against the current jME… and it is extending Component, but hopefully you should be able to get something out of it.

renanse said:

It's old and may not compile against the current jME... and it is extending Component, but hopefully you should be able to get something out of it.


Thank you, I will give it a try and let you know if I can make it work.
Happy new year!

Edit: After a closer look at the class, it doesn't help that much, it doesn't contain what I am looking for:
- How to draw to a buffer.
- Why my component became black when I changed the layout.

Let me a few hours to read the current source code, maybe I will find what I need there,