JME JFX Integration

This is dead, go to here
http://hub.jmonkeyengine.org/t/javafx-embedded-in-jme3/27747

Well I’m currently doing a JFX integration for jme, to allow the useage of openid and an embedded help wiki viwer for my projects.
-> Rendering works
-> Events missing so far
-> Currently performance is around 10fps with a youtube video in a website running.
-> Decoupled all processing(image converting) happens outside of JME, except the texture setting (You all have multicores anyway, so its working qutie good)

Will clean it up the following days, and the post

Teaser:

5 Likes

Looks cool! Good luck!

Woot, that looks really cool! I can think in 2 different uses on my end!

Good work! I’m really interrested in this =)

Well as it seems it’s somewhat impossible to get the events into jfx.

Here is what i have so far, the problem is, the jfx/swing stuff only reacts to events if its focused (except mousemove events, those work fine already)
For the jfx stuff to be focused it needs to be attached to a window of some kind, wich itself must be focused, resulting in the loss of the focus for the jme application.

Maybee someone knows a solution?

package jme3test.jfx;

import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.Semaphore;

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;

import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.material.Material;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.scene.shape.Quad;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.plugins.AWTLoader;

public class JFXQuad extends Node {
	private JFXPanel		fxPanel;
	private Scene			scene;
	private boolean			alive						= true;
	private Texture			texture;

	private Material		mat;
	// caches
	private BufferedImage	awtImage;
	private WritableImage	fxImage;
	Semaphore				mutalExclusiveImageAccess	= new Semaphore(1);
	private JFXInitCallback	initJFX;
	private Mesh			displayMesh;
	private Geometry		displayGeometry;

	public JFXQuad(final JFXInitCallback initJFX, final int quadWidth, final int quadHeight) {
		this.displayMesh = new Quad(quadWidth, quadHeight);
		this.displayGeometry = new Geometry("JFXQuad", this.displayMesh);

		this.initJFX = initJFX;
		this.initRenderTarget();

		this.fxPanel = new JFXPanel(); // init jfx integration

		Platform.runLater(new Runnable() {
			@Override
			public void run() {
				JFXQuad.this.scene = initJFX.getScene();
				JFXQuad.this.fxPanel.setScene(JFXQuad.this.scene);
			}
		});

		final Thread updateThread = new Thread() {
			@Override
			public void run() {
				while (JFXQuad.this.alive) {
					JFXQuad.this.synchronizedImage();
					try {
						Thread.sleep(initJFX.getUpdateDelay());
					} catch (final InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		};
		updateThread.start();

		this.addControl(new Control() {

			@Override
			public void write(final JmeExporter ex) throws IOException {
				throw new RuntimeException("Not supported");
			}

			@Override
			public void read(final JmeImporter im) throws IOException {
				throw new RuntimeException("Not supported");
			}

			@Override
			public void update(final float tpf) {
				if (JFXQuad.this.texture != null) {
					JFXQuad.this.fxPanel.getFocusListeners();
					JFXQuad.this.mat.setTexture("ColorMap", JFXQuad.this.texture);
				}
			}

			@Override
			public void setSpatial(final Spatial spatial) {
			}

			@Override
			public void render(final RenderManager rm, final ViewPort vp) {
			}

			@Override
			public Control cloneForSpatial(final Spatial spatial) {
				return null;
			}
		});
	}

	private void initRenderTarget() {
		this.texture = new Texture2D(2, 2, Format.ABGR8);
		this.mat = new Material(this.initJFX.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
		this.mat.setTexture("ColorMap", this.texture);
		this.displayGeometry.setMaterial(this.mat);
		this.attachChild(this.displayGeometry);
	}

	public void dispose() {
		this.alive = false;
		this.removeFromParent();
		this.fxPanel.removeAll();
	}

	void synchronizedImage() {

		Platform.runLater(new Runnable() {
			@Override
			public void run() {
				try {
					JFXQuad.this.mutalExclusiveImageAccess.acquire();
					final SnapshotParameters params = new SnapshotParameters();
					params.setFill(Color.ALICEBLUE);
					JFXQuad.this.fxImage = JFXQuad.this.scene.snapshot(JFXQuad.this.fxImage);
					JFXQuad.this.mutalExclusiveImageAccess.release();
				} catch (final InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		try {

			if (this.fxImage != null) {
				this.mutalExclusiveImageAccess.acquire();
				this.awtImage = SwingFXUtils.fromFXImage(this.fxImage, this.awtImage);
				JFXQuad.this.texture = JFXQuad.awtImageToTexture(this.awtImage);
				this.mutalExclusiveImageAccess.release();
			}

		} catch (final InterruptedException e) {
			e.printStackTrace();
		}

	}

	public static Texture awtImageToTexture(final BufferedImage img) {
		final AWTLoader loader = new AWTLoader();
		final Texture tex = new Texture2D();
		tex.setImage(loader.load(img, true));
		return tex;
	}

	public void fireKeyEvent(final KeyInputEvent evt) {
		final KeyEvent virtualEvent = new KeyEvent(this.fxPanel, KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0, evt.getKeyCode(), evt.getKeyChar());
		System.out.println(virtualEvent);
		this.fxPanel.dispatchEvent(virtualEvent);
	}

	public void fireMouseEvent(final MouseMotionEvent evt) {
		final MouseEvent virtualEvent = new java.awt.event.MouseEvent(this.fxPanel, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, evt.getX(), this.initJFX.getScreenHeight() - evt.getY(), 1, false);
		this.fxPanel.dispatchEvent(virtualEvent);
	}

	public void fireMouseClick(final MouseButtonEvent evt) {
		final MouseEvent virtualEvent = new java.awt.event.MouseEvent(this.fxPanel, MouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), 0, evt.getX(), this.initJFX.getScreenHeight() - evt.getY(), 1, false, evt.getButtonIndex());
		System.out.println(virtualEvent);
		this.fxPanel.dispatchEvent(virtualEvent);
	}
}
package jme3test.jfx;

import javafx.event.Event;
import javafx.scene.Scene;
import javafx.scene.paint.Color;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.AssetManager;
import com.jme3.input.RawInputListener;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import com.jme3.renderer.queue.RenderQueue.Bucket;

public class TestJFXInteration extends SimpleApplication {

	private JFXInitCallback	initJFX;

	@Override
	public void simpleInitApp() {
		this.setPauseOnLostFocus(false);

		this.flyCam.setEnabled(false);

		this.initJFX = new JFXInitCallback() {

			private JfxBrowser	element;

			@Override
			public long getUpdateDelay() {
				return 50;
			}

			@Override
			public int getTargetWidth() {
				return TestJFXInteration.this.settings.getWidth();
			}

			@Override
			public int getTargetHeight() {
				return TestJFXInteration.this.settings.getHeight();
			}

			@Override
			public Scene getScene() {
				this.element = new JfxBrowser("http://www.dict.cc/?s=key+input");
				return new Scene(this.element, this.getTargetWidth(), this.getTargetHeight(), Color.web("#666970"));
			}

			@Override
			public AssetManager getAssetManager() {
				return TestJFXInteration.this.getAssetManager();
			}

			@Override
			public void fireEvent(final Event evt) {
				this.element.fireEvent(evt);
			}

			@Override
			public int getScreenHeight() {
				return TestJFXInteration.this.settings.getHeight();
			}
		};

		final JFXQuad jfXQuad = new JFXQuad(this.initJFX, this.settings.getWidth(), this.settings.getHeight());
		jfXQuad.setQueueBucket(Bucket.Gui);

		this.rootNode.attachChild(jfXQuad);

		this.inputManager.addRawInputListener(new RawInputListener() {

			@Override
			public void onTouchEvent(final TouchEvent evt) {
				// TODO Auto-generated method stub

			}

			@Override
			public void onMouseMotionEvent(final MouseMotionEvent evt) {
				jfXQuad.fireMouseEvent(evt);
			}

			@Override
			public void onMouseButtonEvent(final MouseButtonEvent evt) {
				jfXQuad.fireMouseClick(evt);
			}

			@Override
			public void onKeyEvent(final KeyInputEvent evt) {
				jfXQuad.fireKeyEvent(evt);
			}

			@Override
			public void onJoyButtonEvent(final JoyButtonEvent evt) {
				// TODO Auto-generated method stub

			}

			@Override
			public void onJoyAxisEvent(final JoyAxisEvent evt) {
				// TODO Auto-generated method stub

			}

			@Override
			public void endInput() {
				// TODO Auto-generated method stub

			}

			@Override
			public void beginInput() {
				// TODO Auto-generated method stub

			}
		});
	}

	@Override
	public void destroy() {
		super.destroy();
	}

	public static void main(final String[] args) {
		new TestJFXInteration().start();
	}

}
package jme3test.jfx;

import javafx.event.Event;
import javafx.scene.Scene;

import com.jme3.asset.AssetManager;

public interface JFXInitCallback {

	Scene getScene();

	long getUpdateDelay();

	int getTargetWidth();

	int getTargetHeight();

	AssetManager getAssetManager();

	void fireEvent(Event evt);

	int getScreenHeight();

}
package jme3test.jfx;

import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.layout.Region;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;

class JfxBrowser extends Region {

	final WebView	browser		= new WebView();
	final WebEngine	webEngine	= this.browser.getEngine();

	public JfxBrowser(final String website) {
		// apply the styles
		this.getStyleClass().add("browser");
		// load the web page
		this.webEngine.load(website);

		// add the web view to the scene
		this.getChildren().add(this.browser);

	}

	@Override
	protected void layoutChildren() {
		final double w = this.getWidth();
		final double h = this.getHeight();
		this.layoutInArea(this.browser, 0, 0, w, h, 0, HPos.CENTER, VPos.CENTER);
	}

	@Override
	protected double computePrefWidth(final double height) {
		return 750;
	}

	@Override
	protected double computePrefHeight(final double width) {
		return 500;
	}
}

Nice! you definitly need to keep going on this XD
Some time ago i decided to gave jfx a chance but migrating neither lwjgl canvas nor awtpanel was possible since JavaFX isn’t based on swing anymore but a complete reimplementation. Hope to see this in jme soon :wink:

People are working on integrating LWJGL and JOGL into JavaFX.
There are technical and legal difficulties. Most stem from Oracle’s restrictions and/or Oracle not talking about future plans.
There are various attempts to motivate Oracle into changing this. @erlend_sh is involved in some of them, so he’d be the right person to ask about details. Personally, I’m not holding my breath; even if Oracle totally agreed today and actually intended to open source everything, it would take them months to sort out the legal paperwork and technical changes where the paperwork won’t help (e.g. to deal with code bought or inherited under a restrictive license - they’d have to renegotiate or rewrite that code).

<cite>@Empire Phoenix said:</cite> Well as it seems it's somewhat impossible to get the events into jfx.

Here is what i have so far, the problem is, the jfx/swing stuff only reacts to events if its focused (except mousemove events, those work fine already)
For the jfx stuff to be focused it needs to be attached to a window of some kind, wich itself must be focused, resulting in the loss of the focus for the jme application.

Maybee someone knows a solution?

The scene composer has the same problem. I’m sure there must be a solution some how.