diff --git a/jme3-desktop/src/main/java/com/jme3/app/state/AWTComponentAppState.java b/jme3-desktop/src/main/java/com/jme3/app/state/AWTComponentAppState.java
new file mode 100644
index 000000000..0227aff0b
--- /dev/null
+++ b/jme3-desktop/src/main/java/com/jme3/app/state/AWTComponentAppState.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2009-2018 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.app.state;
+
+import java.awt.Component;
+
+import com.jme3.app.Application;
+import com.jme3.app.state.AbstractAppState;
+import com.jme3.app.state.AppStateManager;
+import com.jme3.system.AWTFrameProcessor;
+import com.jme3.system.AWTTaskExecutor;
+
+/**
+ * An app state dedicated to the rendering of a JMonkey application within an AWT component.
+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr
+ */
+public class AWTComponentAppState extends AbstractAppState {
+
+ private final AWTTaskExecutor executor = AWTTaskExecutor.getInstance();
+
+ private Component component = null;
+
+ private AWTFrameProcessor processor = null;
+
+ private AWTFrameProcessor.TransferMode transferMode = AWTFrameProcessor.TransferMode.ON_CHANGES;
+
+ @Override
+ public void initialize(AppStateManager stateManager, Application app) {
+ super.initialize(stateManager, app);
+ }
+
+ @Override
+ public void stateAttached(final AppStateManager stateManager) {
+ processor = new AWTFrameProcessor();
+ processor.setTransferMode(transferMode);
+
+ AWTTaskExecutor.getInstance().addToExecute(new Runnable() {
+
+ @Override
+ public void run() {
+ processor.bind(component, stateManager.getApplication(), stateManager.getApplication().getViewPort());
+ }
+
+ });
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ }
+
+ @Override
+ public void update(float tpf) {
+ executor.execute();
+ super.update(tpf);
+ }
+
+ @Override
+ public void cleanup() {
+ super.cleanup();
+ }
+
+ /**
+ * Create a new app state dedicated to the rendering of a JMonkey application within the given AWT component
.
+ * @param component the component that is used as rendering output.
+ */
+ public AWTComponentAppState(Component component) {
+ this.component = component;
+ }
+
+ /**
+ * Get the AWT component that is used as rendering output.
+ * @return the AWT component that is used as rendering output.
+ * @see #setComponent(Component)
+ */
+ public Component getComponent() {
+ return component;
+ }
+
+ /**
+ * Set the AWT component that is used as rendering output.
+ * @param component the AWT component that is used as rendering output.
+ * @see #getComponent()
+ */
+ public void setComponent(Component component) {
+ this.component = component;
+ }
+
+ /**
+ * Get the {@link AWTFrameProcessor.TransferMode transfer mode} that is used by the underlying frame processor.
+ * @return the {@link AWTFrameProcessor.TransferMode transfer mode} that is used by the underlying frame processor.
+ * @see #setTransferMode(com.jme3.system.AWTFrameProcessor.TransferMode)
+ */
+ public AWTFrameProcessor.TransferMode getTransferMode(){
+ return transferMode;
+ }
+
+ /**
+ * Set the {@link AWTFrameProcessor.TransferMode transfer mode} that is used by the underlying frame processor.
+ * @param mode the {@link AWTFrameProcessor.TransferMode transfer mode} that is used by the underlying frame processor.
+ * @see #getTransferMode()
+ */
+ public void setTransferMode(AWTFrameProcessor.TransferMode mode) {
+ this.transferMode = mode;
+ }
+}
diff --git a/jme3-desktop/src/main/java/com/jme3/input/AWTInput.java b/jme3-desktop/src/main/java/com/jme3/input/AWTInput.java
new file mode 100644
index 000000000..cceab1d6e
--- /dev/null
+++ b/jme3-desktop/src/main/java/com/jme3/input/AWTInput.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2009-2018 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.input;
+
+import java.awt.Component;
+import java.util.Objects;
+
+import com.jme3.app.Application;
+import com.jme3.input.Input;
+import com.jme3.input.RawInputListener;
+import com.jme3.system.AWTContext;
+import com.jme3.system.AWTTaskExecutor;
+
+/**
+ * The implementation of the {@link Input} dedicated to AWT {@link Component component}.
+ *
+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *
+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTInput implements Input { + + protected static final AWTTaskExecutor EXECUTOR = AWTTaskExecutor.getInstance(); + + /** + * The context. + */ + protected final AWTContext context; + + /** + * The raw listener. + */ + protected RawInputListener listener; + + /** + * The input node. + */ + protected Component component; + + /** + * The {@link Application JMonkey application} that provide the context. + */ + protected Application application; + + /** + * Initializes is it. + */ + protected boolean initialized; + + public AWTInput(final AWTContext context) { + this.context = context; + } + + public void bind(final Component component) { + this.component = component; + Objects.requireNonNull(this.component, "binded Component cannot be null"); + } + + public void unbind() { + this.component = null; + } + + @Override + public void initialize() { + if (isInitialized()) return; + initializeImpl(); + initialized = true; + } + + protected void initializeImpl() { + } + + @Override + public void update() { + if (!context.isRenderable()) return; + updateImpl(); + } + + protected void updateImpl() { + } + + @Override + public void destroy() { + unbind(); + } + + @Override + public boolean isInitialized() { + return initialized; + } + + @Override + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + @Override + public long getInputTimeNanos() { + return System.nanoTime(); + } +} \ No newline at end of file diff --git a/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java b/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java new file mode 100644 index 000000000..0280d8aed --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import java.awt.Component; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import com.jme3.input.KeyInput; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.system.AWTContext; + + +/** + * The implementation of the {@link KeyInput} dedicated to AWT {@link Component component}. + *+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *
+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTKeyInput extends AWTInput implements KeyInput, KeyListener{ + + private static final Map+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *
+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTMouseInput extends AWTInput implements MouseInput, MouseListener, MouseMotionListener, MouseWheelListener { + + private static final Map+ * This class enables to update graphics of an AWT component with the result of JMonkey 3D rendering. + *
+ *+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *
+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + * + */ +public class AWTComponentRenderer { + + /** + * The constant RUNNING_STATE. + */ + protected static final int RUNNING_STATE = 1; + /** + * The constant WAITING_STATE. + */ + protected static final int WAITING_STATE = 2; + /** + * The constant DISPOSING_STATE. + */ + protected static final int DISPOSING_STATE = 3; + /** + * The constant DISPOSED_STATE. + */ + protected static final int DISPOSED_STATE = 4; + + /** + * The Frame state. + */ + protected final AtomicInteger frameState; + + /** + * The Image state. + */ + protected final AtomicInteger imageState; + + /** + * The Frame buffer. + */ + protected final FrameBuffer frameBuffer; + + /** + * The Pixel writer. + */ + protected final Graphics pixelWriter; + + /** + * The Frame byte buffer. + */ + protected final ByteBuffer frameByteBuffer; + + + /** + * The transfer mode. + */ + protected final TransferMode transferMode; + + /** + * The byte buffer. + */ + protected final byte[] byteBuffer; + + /** + * The image byte buffer. + */ + protected final int[] imageByteBuffer; + + /** + * The prev image byte buffer. + */ + protected final byte[] prevImageByteBuffer; + + /** + * How many frames need to write else. + */ + protected int frameCount; + + /** + * The width. + */ + private final int width; + + /** + * The height. + */ + private final int height; + + private ColorModel colorModel = null; + + private Component component = null; + + private Graphics2D offGraphics = null; + + /** + * Create a new component renderer attached to the given {@link Component destination}. + * The graphics of the destination are updated with the JMonkeyEngine rendering result. + * @param destination the AWT component to use as target of the JMonkeyEngine rendering. + * @param width the width of the component in pixels. + * @param height the height of the component in pixels. + * @param transferMode the rendering mode that can be {@link TransferMode#ALWAYS} if the component has to be rendered at each update + * or {@link TransferMode#ON_CHANGES} if the component has to be rendered only when changes are occurring. + */ + public AWTComponentRenderer(Component destination, final int width, final int height, TransferMode transferMode) { + this(destination, transferMode, null, width, height); + } + + /** + * Create a new component renderer attached to the given {@link Component destination}. + * The graphics of the destination are updated with the JMonkeyEngine rendering result. + * @param destination the AWT component to use as target of the JMonkeyEngine rendering. + * @param transferMode the rendering mode that can be {@link TransferMode#ALWAYS} if the component has to be rendered at each update or {@link TransferMode#ON_CHANGES} if the component has to be rendered only when changes are occurring. + * @param frameBuffer the JMonkey frame buffer to use (ifnull
is passed, a new default frame buffer is created)
+ * @param width the width of the component in pixels.
+ * @param height the height of the component in pixels.
+ */
+ public AWTComponentRenderer(Component destination, TransferMode transferMode, FrameBuffer frameBuffer, int width, int height) {
+ this.transferMode = transferMode;
+ this.frameState = new AtomicInteger(WAITING_STATE);
+ this.imageState = new AtomicInteger(WAITING_STATE);
+ this.width = frameBuffer != null ? frameBuffer.getWidth() : width;
+ this.height = frameBuffer != null ? frameBuffer.getHeight() : height;
+ this.frameCount = 0;
+
+ if (frameBuffer != null) {
+ this.frameBuffer = frameBuffer;
+ } else {
+ this.frameBuffer = new FrameBuffer(width, height, 1);
+ this.frameBuffer.setDepthBuffer(Image.Format.Depth);
+ this.frameBuffer.setColorBuffer(Image.Format.RGBA8);
+ this.frameBuffer.setSrgb(true);
+ }
+
+ colorModel = ColorModel.getRGBdefault();
+
+ frameByteBuffer = BufferUtils.createByteBuffer(getWidth() * getHeight() * 4);
+ byteBuffer = new byte[getWidth() * getHeight() * 4];
+ prevImageByteBuffer = new byte[getWidth() * getHeight() * 4];
+ imageByteBuffer = new int[getWidth() * getHeight()];
+ pixelWriter = getGraphics(destination);
+
+ this.component = destination;
+
+ }
+
+ /**
+ * Initialize the component renderer.
+ * @param renderer the JMonkey {@link Renderer renderer} to use.
+ * @param main true
if the attached frame buffer is the main one or false
otherwise.
+ */
+ public void init(Renderer renderer, boolean main) {
+ if (main) {
+ renderer.setMainFrameBufferOverride(frameBuffer);
+ }
+ }
+
+ /**
+ * Get the graphics context of the given component.
+ * @param destination the AWT component used for rendering.
+ * @return the graphics context of the given component.
+ */
+ protected Graphics getGraphics(Component destination) {
+ if (destination != null) {
+
+ if (destination.getGraphics() != null) {
+ return destination.getGraphics();
+ } else {
+ System.out.println("AWT component "+destination.getClass().getSimpleName()+" does not provide 2D graphics capabilities.");
+ return null;
+ //throw new IllegalArgumentException("AWT component "+destination.getClass().getSimpleName()+" does not provide 2D graphics capabilities.");
+ }
+ } else {
+ throw new NullPointerException("Component cannot be null.");
+ }
+ }
+
+ /**
+ * Get the width of the area to render.
+ * @return the width of the area to render.
+ * @see #getHeight()
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * Get the height of the area to render.
+ * @return the height of the area to render.
+ * @see #getWidth()
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * Copy the JMonkey frame buffer that has been rendered by the JMonkey engine and schedule the rendering of the component.
+ * @param renderManager the JMonkey render manager.
+ */
+ public void copyFrameBufferToImage(RenderManager renderManager) {
+
+ while (!frameState.compareAndSet(WAITING_STATE, RUNNING_STATE)) {
+ if (frameState.get() == DISPOSED_STATE) {
+ return;
+ }
+ }
+
+ // Convert screenshot.
+ try {
+
+ frameByteBuffer.clear();
+
+ final Renderer renderer = renderManager.getRenderer();
+ renderer.readFrameBufferWithFormat(frameBuffer, frameByteBuffer, Image.Format.RGBA8);
+
+ } finally {
+ if (!frameState.compareAndSet(RUNNING_STATE, WAITING_STATE)) {
+ throw new RuntimeException("unknown problem with the frame state");
+ }
+ }
+
+ synchronized (byteBuffer) {
+ frameByteBuffer.get(byteBuffer);
+
+ if (transferMode == TransferMode.ON_CHANGES) {
+
+ final byte[] prevBuffer = getPrevImageByteBuffer();
+
+ if (Arrays.equals(prevBuffer, byteBuffer)) {
+ if (frameCount == 0) return;
+ } else {
+ frameCount = 2;
+ System.arraycopy(byteBuffer, 0, prevBuffer, 0, byteBuffer.length);
+ }
+
+ frameByteBuffer.position(0);
+ frameCount--;
+ }
+ }
+
+ EventQueue.invokeLater(new Runnable() {
+
+ @Override
+ public void run() {
+ writeFrame();
+ }});
+ }
+
+ /**
+ * Write the current rendered frame to the component graphics contex.
+ */
+ protected void writeFrame() {
+
+
+ if (pixelWriter != null) {
+ while (!imageState.compareAndSet(WAITING_STATE, RUNNING_STATE)) {
+ if (imageState.get() == DISPOSED_STATE) {
+ return;
+ }
+ }
+
+ try {
+
+ final int[] imageDataBuffer = getImageByteBuffer();
+
+ synchronized (byteBuffer) {
+
+ for(int i = 0; i < width * height; i++) {
+ imageDataBuffer[i] = ((0xff & byteBuffer[i*4+3]) << 24) // Alpha
+ | ((0xff & byteBuffer[i*4]) << 16) // Red
+ | ((0xff & byteBuffer[i*4+1]) << 8) // Green
+ | ((0xff & byteBuffer[i*4+2])); // BLue
+
+ }
+ }
+
+ DataBuffer buffer = new DataBufferInt(imageDataBuffer, imageDataBuffer.length);
+
+ SampleModel sm = new SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, getWidth(), getHeight(), new int[] {0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000});
+
+ WritableRaster raster = Raster.createWritableRaster(sm, buffer, null);
+
+ BufferedImage img = new BufferedImage(colorModel, raster, false, null);
+
+ BufferedImage img2 = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
+ offGraphics = img2.createGraphics();
+ offGraphics.setColor(component.getBackground());
+ img2.createGraphics().fillRect(0, 0, getWidth(), getHeight());
+ img2.createGraphics().drawImage(img, null, null);
+
+ component.getGraphics().drawImage(img2, 0, 0, null);
+
+ } finally {
+ if (!imageState.compareAndSet(RUNNING_STATE, WAITING_STATE)) {
+ throw new RuntimeException("unknown problem with the image state");
+ }
+ }
+ } else {
+ System.out.println("No graphics context available for rendering.");
+ }
+
+
+ }
+
+ /**
+ * Get the image byte buffer.
+ * @return the image byte buffer.
+ */
+ protected int[] getImageByteBuffer() {
+ return imageByteBuffer;
+ }
+
+ /**
+ * Get the previous image byte buffer.
+ * @return the previous image byte buffer.
+ */
+ protected byte[] getPrevImageByteBuffer() {
+ return prevImageByteBuffer;
+ }
+
+ /**
+ * Dispose this renderer. The underlying frame buffer is also disposed.
+ */
+ public void dispose() {
+ while (!frameState.compareAndSet(WAITING_STATE, DISPOSING_STATE)) ;
+ while (!imageState.compareAndSet(WAITING_STATE, DISPOSING_STATE)) ;
+ frameBuffer.dispose();
+ BufferUtils.destroyDirectBuffer(frameByteBuffer);
+ frameState.compareAndSet(DISPOSING_STATE, DISPOSED_STATE);
+ imageState.compareAndSet(DISPOSING_STATE, DISPOSED_STATE);
+ }
+}
\ No newline at end of file
diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java
new file mode 100644
index 000000000..78cde8f74
--- /dev/null
+++ b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2009-2018 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+
+import com.jme3.input.AWTKeyInput;
+import com.jme3.input.AWTMouseInput;
+import com.jme3.input.JoyInput;
+import com.jme3.input.TouchInput;
+import com.jme3.opencl.Context;
+import com.jme3.renderer.Renderer;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeContext;
+import com.jme3.system.JmeSystem;
+import com.jme3.system.SystemListener;
+import com.jme3.system.Timer;
+
+/**
+ * A JMonkey {@link JmeContext context} that is dedicated to AWT component rendering.
+ * + * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *
+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTContext implements JmeContext { + + /** + * The settings. + */ + protected final AppSettings settings; + + /** + * The key input. + */ + protected final AWTKeyInput keyInput; + + /** + * The mouse input. + */ + protected final AWTMouseInput mouseInput; + + /** + * The current width. + */ + private volatile int width; + + /** + * The current height. + */ + private volatile int height; + + /** + * The background context. + */ + protected JmeContext backgroundContext; + + public AWTContext() { + this.keyInput = new AWTKeyInput(this); + this.mouseInput = new AWTMouseInput(this); + this.settings = createSettings(); + this.backgroundContext = createBackgroundContext(); + this.height = 1; + this.width = 1; + } + + /** + * @return the current height. + */ + public int getHeight() { + return height; + } + + /** + * @param height the current height. + */ + public void setHeight(final int height) { + this.height = height; + } + + /** + * @return the current width. + */ + public int getWidth() { + return width; + } + + /** + * @param width the current width. + */ + public void setWidth(final int width) { + this.width = width; + } + + /** + * @return new settings. + */ + protected AppSettings createSettings() { + final AppSettings settings = new AppSettings(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL3); + return settings; + } + + /** + * @return new context/ + */ + protected JmeContext createBackgroundContext() { + return JmeSystem.newContext(settings, Type.OffscreenSurface); + } + + @Override + public Type getType() { + return Type.OffscreenSurface; + } + + @Override + public void setSettings(AppSettings settings) { + this.settings.copyFrom(settings); + this.settings.setRenderer(AppSettings.LWJGL_OPENGL3); + this.backgroundContext.setSettings(settings); + } + + @Override + public void setSystemListener(final SystemListener listener) { + backgroundContext.setSystemListener(listener); + } + + @Override + public AppSettings getSettings() { + return settings; + } + + @Override + public Renderer getRenderer() { + return backgroundContext.getRenderer(); + } + + @Override + public Context getOpenCLContext() { + return null; + } + + @Override + public AWTMouseInput getMouseInput() { + return mouseInput; + } + + @Override + public AWTKeyInput getKeyInput() { + return keyInput; + } + + @Override + public JoyInput getJoyInput() { + return null; + } + + @Override + public TouchInput getTouchInput() { + return null; + } + + @Override + public Timer getTimer() { + return backgroundContext.getTimer(); + } + + @Override + public void setTitle(final String title) { + } + + @Override + public boolean isCreated() { + return backgroundContext != null && backgroundContext.isCreated(); + } + + @Override + public boolean isRenderable() { + return backgroundContext != null && backgroundContext.isRenderable(); + } + + @Override + public void setAutoFlushFrames(final boolean enabled) { + // TODO Auto-generated method stub + } + + @Override + public void create(final boolean waitFor) { + String render = System.getProperty("awt.background.render", AppSettings.LWJGL_OPENGL33); + backgroundContext.getSettings().setRenderer(render); + backgroundContext.create(waitFor); + } + + @Override + public void restart() { + } + + @Override + public void destroy(final boolean waitFor) { + if (backgroundContext == null) throw new IllegalStateException("Not created"); + // destroy wrapped context + backgroundContext.destroy(waitFor); +} + +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java b/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java new file mode 100644 index 000000000..81dc7b029 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java @@ -0,0 +1,659 @@ +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import java.awt.Component; +import java.awt.EventQueue; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import com.jme3.app.Application; +import com.jme3.input.AWTKeyInput; +import com.jme3.input.AWTMouseInput; +import com.jme3.post.SceneProcessor; +import com.jme3.profile.AppProfiler; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; + +/** + * A frame processor that enables to render JMonkey frame buffer onto an AWT component. + *+ * This class is based on the JavaFX original code provided by Alexander Brui (see JME3-FX) + *
+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + * + */ +public class AWTFrameProcessor implements SceneProcessor, PropertyChangeListener { + + public enum TransferMode { + ALWAYS, + ON_CHANGES + } + + private Application application = null; + + /** + * The width listener. + */ + protected PropertyChangeListener widthListener; + + /** + * The height listener. + */ + protected PropertyChangeListener heightListener; + + /** + * The ration listener. + */ + protected PropertyChangeListener rationListener; + + /** + * The flag to decide when we should resize. + */ + private final AtomicInteger reshapeNeeded; + + /** + * The render manager. + */ + private RenderManager renderManager; + + /** + * The source view port. + */ + private ViewPort viewPort; + + /** + * The frame transfer. + */ + private AWTComponentRenderer frameTransfer; + + /** + * The transfer mode. + */ + private TransferMode transferMode; + + /** + * The destination of jMe frames. + */ + protected volatile Component destination; + + /** + * The flag is true if this processor is main. + */ + private volatile boolean main; + + private int askWidth; + private int askHeight; + + private boolean askFixAspect; + private boolean enabled; + + @Override + public void initialize(RenderManager rm, ViewPort vp) { + this.renderManager = rm; + } + + @Override + public void reshape(ViewPort vp, int w, int h) { + // TODO Auto-generated method stub + } + + @Override + public boolean isInitialized() { + return frameTransfer != null; + } + + @Override + public void preFrame(float tpf) { + // TODO Auto-generated method stub + + } + + @Override + public void postQueue(RenderQueue rq) { + // TODO Auto-generated method stub + + } + + @Override + public void postFrame(FrameBuffer out) { + if (!isEnabled()) { + return; + } + + final AWTComponentRenderer frameTransfer = getFrameTransfer(); + if (frameTransfer != null) { + frameTransfer.copyFrameBufferToImage(getRenderManager()); + } + + // for the next frame + if (hasDestination() && reshapeNeeded.get() > 0 && reshapeNeeded.decrementAndGet() >= 0) { + + if (frameTransfer != null) { + frameTransfer.dispose(); + } + + setFrameTransfer(reshapeInThread(askWidth, askHeight, askFixAspect)); + } + } + + @Override + public void cleanup() { + final AWTComponentRenderer frameTransfer = getFrameTransfer(); + + if (frameTransfer != null) { + frameTransfer.dispose(); + setFrameTransfer(null); + } + } + + @Override + public void setProfiler(AppProfiler profiler) { + // TODO Auto-generated method stub + + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + System.out.println("Property changed: "+evt.getPropertyName()+" "+evt.getOldValue()+" -> "+evt.getNewValue()); + } + + public AWTFrameProcessor() { + transferMode = TransferMode.ALWAYS; + askWidth = 1; + askHeight = 1; + main = true; + reshapeNeeded = new AtomicInteger(2); + } + + /** + * Notify about that the ratio was changed. + * + * @param newValue the new value of the ratio. + */ + protected void notifyChangedRatio(Boolean newValue) { + notifyComponentResized(destination.getWidth(), destination.getHeight(), newValue); + } + + /** + * Notify about that the height was changed. + * + * @param newValue the new value of the height. + */ + protected void notifyChangedHeight(Number newValue) { + notifyComponentResized(destination.getWidth(), newValue.intValue(), isPreserveRatio()); + } + + /** + * Notify about that the width was changed. + * + * @param newValue the new value of the width. + */ + protected void notifyChangedWidth(Number newValue) { + notifyComponentResized(newValue.intValue(), destination.getHeight(), isPreserveRatio()); + } + + /** + * Gets the application. + * + * @return the application. + */ + protected Application getApplication() { + return application; + } + + /** + * Sets the application. + * + * @param application the application. + */ + protected void setApplication(Application application) { + this.application = application; + } + + /** + * Gets the current destination. + * + * @return the current destination. + */ + protected Component getDestination() { + return destination; + } + + /** + * Sets the destination. + * + * @param destination the destination. + */ + protected void setDestination(Component destination) { + this.destination = destination; + } + + /** + * Checks of existing destination. + * @return true if destination is exists. + */ + protected boolean hasDestination() { + return destination != null; + } + + /** + * Checks of existing application. + * @return true if destination is exists. + */ + protected boolean hasApplication() { + return application != null; + } + + + /** + * Gets the frame transfer. + * @return the file transfer. + */ + protected AWTComponentRenderer getFrameTransfer() { + return frameTransfer; + } + + /** + * Sets the frame transfer. + * + * @param frameTransfer the file transfer. + */ + protected void setFrameTransfer(AWTComponentRenderer frameTransfer) { + this.frameTransfer = frameTransfer; + } + + /** + * Gets the view port. + * + * @return the view port. + */ + protected ViewPort getViewPort() { + return viewPort; + } + + /** + * Gets the render manager. + * + * @return the render manager. + */ + protected RenderManager getRenderManager() { + return renderManager; + } + + public boolean isMain() { + return main; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + /** + * Handle resizing. + * + * @param newWidth the new width. + * @param newHeight the new height. + * @param fixAspect true if need to fix aspect. + */ + protected void notifyComponentResized(int newWidth, int newHeight, boolean fixAspect) { + + newWidth = Math.max(newWidth, 1); + newHeight = Math.max(newHeight, 1); + + if (askWidth == newWidth && askWidth == newHeight && askFixAspect == fixAspect) { + return; + } + + askWidth = newWidth; + askHeight = newHeight; + askFixAspect = fixAspect; + reshapeNeeded.set(2); + } + + public void reshape() { + reshapeNeeded.set(2); + } + + /** + * Is preserve ratio. + * + * @return is preserve ratio. + */ + protected boolean isPreserveRatio() { + return false; + } + + /** + * Gets destination width. + * + * @return the destination width. + */ + protected int getDestinationWidth() { + return getDestination().getWidth(); + } + + /** + * Gets destination height. + * + * @return the destination height. + */ + protected int getDestinationHeight() { + return getDestination().getHeight(); + } + + /** + * Bind this processor. + * + * @param destination the destination. + * @param application the application. + */ + public void bind(Component destination, Application application) { + final RenderManager renderManager = application.getRenderManager(); + + if (renderManager == null) { + throw new RuntimeException("No render manager available from the application."); + } + + List+ * This class is dedicated to the queuing of AWT related tasks and their execution. + * It's enable to store tasks that have to be executed within an AWT context and execute them at the specified time. + *
+ *+ * This class is an AWT implementation of the JavaFX original code provided by Alexander Brui (see JME3-FX) + *
+ * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr + * @author Alexander Brui (JavaSaBr) + */ +public class AWTTaskExecutor { + + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + private static final AWTTaskExecutor INSTANCE = new AWTTaskExecutor(); + + /** + * Get the instance of the executor. + * @return the instance of executor. + */ + public static AWTTaskExecutor getInstance() { + return INSTANCE; + } + + /** + * The list of waiting tasks. + */ + private final List