>();
-
- /**
- * Create a new instance of Application
.
- */
- public Application(){
- initStateManager();
- }
+public interface Application {
/**
* Determine the application's behavior when unfocused.
- *
+ *
* @return The lost focus behavior of the application.
*/
- public LostFocusBehavior getLostFocusBehavior() {
- return lostFocusBehavior;
- }
-
+ public LostFocusBehavior getLostFocusBehavior();
+
/**
* Change the application's behavior when unfocused.
- *
- * By default, the application will
- * {@link LostFocusBehavior#ThrottleOnLostFocus throttle the update loop}
+ *
+ * By default, the application will
+ * {@link LostFocusBehavior#ThrottleOnLostFocus throttle the update loop}
* so as to not take 100% CPU usage when it is not in focus, e.g.
* alt-tabbed, minimized, or obstructed by another window.
- *
+ *
* @param lostFocusBehavior The new lost focus behavior to use.
- *
+ *
* @see LostFocusBehavior
*/
- public void setLostFocusBehavior(LostFocusBehavior lostFocusBehavior) {
- this.lostFocusBehavior = lostFocusBehavior;
- }
-
+ public void setLostFocusBehavior(LostFocusBehavior lostFocusBehavior);
+
/**
* Returns true if pause on lost focus is enabled, false otherwise.
*
* @return true if pause on lost focus is enabled
*
- * @see #getLostFocusBehavior()
+ * @see #getLostFocusBehavior()
*/
- public boolean isPauseOnLostFocus() {
- return getLostFocusBehavior() == LostFocusBehavior.PauseOnLostFocus;
- }
+ public boolean isPauseOnLostFocus();
/**
* Enable or disable pause on lost focus.
@@ -153,52 +94,10 @@ public class Application implements SystemListener {
*
* @param pauseOnLostFocus True to enable pause on lost focus, false
* otherwise.
- *
+ *
* @see #setLostFocusBehavior(com.jme3.app.LostFocusBehavior)
*/
- public void setPauseOnLostFocus(boolean pauseOnLostFocus) {
- if (pauseOnLostFocus) {
- setLostFocusBehavior(LostFocusBehavior.PauseOnLostFocus);
- } else {
- setLostFocusBehavior(LostFocusBehavior.Disabled);
- }
- }
-
- @Deprecated
- public void setAssetManager(AssetManager assetManager){
- if (this.assetManager != null)
- throw new IllegalStateException("Can only set asset manager"
- + " before initialization.");
-
- this.assetManager = assetManager;
- }
-
- private void initAssetManager(){
- URL assetCfgUrl = null;
-
- if (settings != null){
- String assetCfg = settings.getString("AssetConfigURL");
- if (assetCfg != null){
- try {
- assetCfgUrl = new URL(assetCfg);
- } catch (MalformedURLException ex) {
- }
- if (assetCfgUrl == null) {
- assetCfgUrl = Application.class.getClassLoader().getResource(assetCfg);
- if (assetCfgUrl == null) {
- logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg);
- return;
- }
- }
- }
- }
- if (assetCfgUrl == null) {
- assetCfgUrl = JmeSystem.getPlatformAssetConfigURL();
- }
- if (assetManager == null){
- assetManager = JmeSystem.newAssetManager(assetCfgUrl);
- }
- }
+ public void setPauseOnLostFocus(boolean pauseOnLostFocus);
/**
* Set the display settings to define the display created.
@@ -210,332 +109,83 @@ public class Application implements SystemListener {
*
* @param settings The settings to set.
*/
- public void setSettings(AppSettings settings){
- this.settings = settings;
- if (context != null && settings.useInput() != inputEnabled){
- // may need to create or destroy input based
- // on settings change
- inputEnabled = !inputEnabled;
- if (inputEnabled){
- initInput();
- }else{
- destroyInput();
- }
- }else{
- inputEnabled = settings.useInput();
- }
- }
+ public void setSettings(AppSettings settings);
/**
* Sets the Timer implementation that will be used for calculating
* frame times. By default, Application will use the Timer as returned
* by the current JmeContext implementation.
*/
- public void setTimer(Timer timer){
- this.timer = timer;
-
- if (timer != null) {
- timer.reset();
- }
+ public void setTimer(Timer timer);
- if (renderManager != null) {
- renderManager.setTimer(timer);
- }
- }
-
- public Timer getTimer(){
- return timer;
- }
-
- private void initDisplay(){
- // aquire important objects
- // from the context
- settings = context.getSettings();
-
- // Only reset the timer if a user has not already provided one
- if (timer == null) {
- timer = context.getTimer();
- }
-
- renderer = context.getRenderer();
- }
-
- private void initAudio(){
- if (settings.getAudioRenderer() != null && context.getType() != Type.Headless){
- audioRenderer = JmeSystem.newAudioRenderer(settings);
- audioRenderer.initialize();
- AudioContext.setAudioRenderer(audioRenderer);
-
- listener = new Listener();
- audioRenderer.setListener(listener);
- }
- }
-
- /**
- * Creates the camera to use for rendering. Default values are perspective
- * projection with 45° field of view, with near and far values 1 and 1000
- * units respectively.
- */
- private void initCamera(){
- cam = new Camera(settings.getWidth(), settings.getHeight());
-
- cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f);
- cam.setLocation(new Vector3f(0f, 0f, 10f));
- cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);
-
- renderManager = new RenderManager(renderer);
- //Remy - 09/14/2010 setted the timer in the renderManager
- renderManager.setTimer(timer);
-
- if (prof != null) {
- renderManager.setAppProfiler(prof);
- }
-
- viewPort = renderManager.createMainView("Default", cam);
- viewPort.setClearFlags(true, true, true);
-
- // Create a new cam for the gui
- Camera guiCam = new Camera(settings.getWidth(), settings.getHeight());
- guiViewPort = renderManager.createPostView("Gui Default", guiCam);
- guiViewPort.setClearFlags(false, false, false);
- }
-
- /**
- * Initializes mouse and keyboard input. Also
- * initializes joystick input if joysticks are enabled in the
- * AppSettings.
- */
- private void initInput(){
- mouseInput = context.getMouseInput();
- if (mouseInput != null)
- mouseInput.initialize();
-
- keyInput = context.getKeyInput();
- if (keyInput != null)
- keyInput.initialize();
-
- touchInput = context.getTouchInput();
- if (touchInput != null)
- touchInput.initialize();
-
- if (!settings.getBoolean("DisableJoysticks")){
- joyInput = context.getJoyInput();
- if (joyInput != null)
- joyInput.initialize();
- }
-
- inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput);
- }
-
- private void initStateManager(){
- stateManager = new AppStateManager(this);
-
- // Always register a ResetStatsState to make sure
- // that the stats are cleared every frame
- stateManager.attach(new ResetStatsState());
- }
+ public Timer getTimer();
/**
* @return The {@link AssetManager asset manager} for this application.
*/
- public AssetManager getAssetManager(){
- return assetManager;
- }
+ public AssetManager getAssetManager();
/**
* @return the {@link InputManager input manager}.
*/
- public InputManager getInputManager(){
- return inputManager;
- }
+ public InputManager getInputManager();
/**
* @return the {@link AppStateManager app state manager}
*/
- public AppStateManager getStateManager() {
- return stateManager;
- }
+ public AppStateManager getStateManager();
/**
* @return the {@link RenderManager render manager}
*/
- public RenderManager getRenderManager() {
- return renderManager;
- }
+ public RenderManager getRenderManager();
/**
* @return The {@link Renderer renderer} for the application
*/
- public Renderer getRenderer(){
- return renderer;
- }
+ public Renderer getRenderer();
/**
* @return The {@link AudioRenderer audio renderer} for the application
*/
- public AudioRenderer getAudioRenderer() {
- return audioRenderer;
- }
+ public AudioRenderer getAudioRenderer();
/**
* @return The {@link Listener listener} object for audio
*/
- public Listener getListener() {
- return listener;
- }
+ public Listener getListener();
/**
* @return The {@link JmeContext display context} for the application
*/
- public JmeContext getContext(){
- return context;
- }
+ public JmeContext getContext();
/**
- * @return The {@link Camera camera} for the application
+ * @return The main {@link Camera camera} for the application
*/
- public Camera getCamera(){
- return cam;
- }
-
- /**
- * Starts the application in {@link Type#Display display} mode.
- *
- * @see #start(com.jme3.system.JmeContext.Type)
- */
- public void start(){
- start(JmeContext.Type.Display, false);
- }
-
- /**
- * Starts the application in {@link Type#Display display} mode.
- *
- * @see #start(com.jme3.system.JmeContext.Type)
- */
- public void start(boolean waitFor){
- start(JmeContext.Type.Display, waitFor);
- }
+ public Camera getCamera();
/**
* Starts the application.
- * Creating a rendering context and executing
- * the main loop in a separate thread.
*/
- public void start(JmeContext.Type contextType) {
- start(contextType, false);
- }
-
+ public void start();
+
/**
* Starts the application.
- * Creating a rendering context and executing
- * the main loop in a separate thread.
*/
- public void start(JmeContext.Type contextType, boolean waitFor){
- if (context != null && context.isCreated()){
- logger.warning("start() called when application already created!");
- return;
- }
-
- if (settings == null){
- settings = new AppSettings(true);
- }
-
- logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
- context = JmeSystem.newContext(settings, contextType);
- context.setSystemListener(this);
- context.create(waitFor);
- }
+ public void start(boolean waitFor);
/**
* Sets an AppProfiler hook that will be called back for
* specific steps within a single update frame. Value defaults
* to null.
*/
- public void setAppProfiler(AppProfiler prof) {
- this.prof = prof;
- if (renderManager != null) {
- renderManager.setAppProfiler(prof);
- }
- }
-
- /**
- * Returns the current AppProfiler hook, or null if none is set.
- */
- public AppProfiler getAppProfiler() {
- return prof;
- }
+ public void setAppProfiler(AppProfiler prof);
/**
- * Initializes the application's canvas for use.
- *
- * After calling this method, cast the {@link #getContext() context} to
- * {@link JmeCanvasContext},
- * then acquire the canvas with {@link JmeCanvasContext#getCanvas() }
- * and attach it to an AWT/Swing Frame.
- * The rendering thread will start when the canvas becomes visible on
- * screen, however if you wish to start the context immediately you
- * may call {@link #startCanvas() } to force the rendering thread
- * to start.
- *
- * @see JmeCanvasContext
- * @see Type#Canvas
- */
- public void createCanvas(){
- if (context != null && context.isCreated()){
- logger.warning("createCanvas() called when application already created!");
- return;
- }
-
- if (settings == null){
- settings = new AppSettings(true);
- }
-
- logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
- context = JmeSystem.newContext(settings, JmeContext.Type.Canvas);
- context.setSystemListener(this);
- }
-
- /**
- * Starts the rendering thread after createCanvas() has been called.
- *
- * Same as calling startCanvas(false)
- *
- * @see #startCanvas(boolean)
- */
- public void startCanvas(){
- startCanvas(false);
- }
-
- /**
- * Starts the rendering thread after createCanvas() has been called.
- *
- * Calling this method is optional, the canvas will start automatically
- * when it becomes visible.
- *
- * @param waitFor If true, the current thread will block until the
- * rendering thread is running
- */
- public void startCanvas(boolean waitFor){
- context.create(waitFor);
- }
-
- /**
- * Determine if the application has already started.
- *
- * After {@link #start() } but before {@link #stop() }.
- *
- * @return if started
- */
- public boolean isStarted() {
- return context != null && context.isCreated();
- }
-
- /**
- * Internal use only.
+ * Returns the current AppProfiler hook, or null if none is set.
*/
- public void reshape(int w, int h){
- renderManager.notifyReshape(w, h);
- }
+ public AppProfiler getAppProfiler();
/**
* Restarts the context, applying any changed settings.
@@ -544,10 +194,7 @@ public class Application implements SystemListener {
* applied immediately; calling this method forces the context
* to restart, applying the new settings.
*/
- public void restart(){
- context.setSettings(settings);
- context.restart();
- }
+ public void restart();
/**
* Requests the context to close, shutting down the main loop
@@ -557,102 +204,14 @@ public class Application implements SystemListener {
*
* @see #stop(boolean)
*/
- public void stop(){
- stop(false);
- }
+ public void stop();
/**
* Requests the context to close, shutting down the main loop
* and making necessary cleanup operations.
* After the application has stopped, it cannot be used anymore.
*/
- public void stop(boolean waitFor){
- logger.log(Level.FINE, "Closing application: {0}", getClass().getName());
- context.destroy(waitFor);
- }
-
- /**
- * Do not call manually.
- * Callback from ContextListener.
- *
- * Initializes the Application
, by creating a display and
- * default camera. If display settings are not specified, a default
- * 640x480 display is created. Default values are used for the camera;
- * perspective projection with 45° field of view, with near
- * and far values 1 and 1000 units respectively.
- */
- public void initialize(){
- if (assetManager == null){
- initAssetManager();
- }
-
- initDisplay();
- initCamera();
-
- if (inputEnabled){
- initInput();
- }
- initAudio();
-
- // update timer so that the next delta is not too large
-// timer.update();
- timer.reset();
-
- // user code here..
- }
-
- /**
- * Internal use only.
- */
- public void handleError(String errMsg, Throwable t){
- // Print error to log.
- logger.log(Level.SEVERE, errMsg, t);
- // Display error message on screen if not in headless mode
- if (context.getType() != JmeContext.Type.Headless) {
- if (t != null) {
- JmeSystem.showErrorDialog(errMsg + "\n" + t.getClass().getSimpleName() +
- (t.getMessage() != null ? ": " + t.getMessage() : ""));
- } else {
- JmeSystem.showErrorDialog(errMsg);
- }
- }
-
- stop(); // stop the application
- }
-
- /**
- * Internal use only.
- */
- public void gainFocus(){
- if (lostFocusBehavior != LostFocusBehavior.Disabled) {
- if (lostFocusBehavior == LostFocusBehavior.PauseOnLostFocus) {
- paused = false;
- }
- context.setAutoFlushFrames(true);
- if (inputManager != null) {
- inputManager.reset();
- }
- }
- }
-
- /**
- * Internal use only.
- */
- public void loseFocus(){
- if (lostFocusBehavior != LostFocusBehavior.Disabled){
- if (lostFocusBehavior == LostFocusBehavior.PauseOnLostFocus) {
- paused = true;
- }
- context.setAutoFlushFrames(false);
- }
- }
-
- /**
- * Internal use only.
- */
- public void requestClose(boolean esc){
- context.destroy(false);
- }
+ public void stop(boolean waitFor);
/**
* Enqueues a task/callable object to execute in the jME3
@@ -661,15 +220,11 @@ public class Application implements SystemListener {
* Callables are executed right at the beginning of the main loop.
* They are executed even if the application is currently paused
* or out of focus.
- *
+ *
* @param callable The callable to run in the main jME3 thread
*/
- public Future enqueue(Callable callable) {
- AppTask task = new AppTask(callable);
- taskQueue.add(task);
- return task;
- }
-
+ public Future enqueue(Callable callable);
+
/**
* Enqueues a runnable object to execute in the jME3
* rendering thread.
@@ -677,110 +232,16 @@ public class Application implements SystemListener {
* Runnables are executed right at the beginning of the main loop.
* They are executed even if the application is currently paused
* or out of focus.
- *
+ *
* @param runnable The runnable to run in the main jME3 thread
- */
- public void enqueue(Runnable runnable){
- enqueue(new RunnableWrapper(runnable));
- }
-
- /**
- * Runs tasks enqueued via {@link #enqueue(Callable)}
*/
- protected void runQueuedTasks() {
- AppTask> task;
- while( (task = taskQueue.poll()) != null ) {
- if (!task.isCancelled()) {
- task.invoke();
- }
- }
- }
-
- /**
- * Do not call manually.
- * Callback from ContextListener.
- */
- public void update(){
- // Make sure the audio renderer is available to callables
- AudioContext.setAudioRenderer(audioRenderer);
-
- if (prof!=null) prof.appStep(AppStep.QueuedTasks);
- runQueuedTasks();
-
- if (speed == 0 || paused)
- return;
-
- timer.update();
-
- if (inputEnabled){
- if (prof!=null) prof.appStep(AppStep.ProcessInput);
- inputManager.update(timer.getTimePerFrame());
- }
-
- if (audioRenderer != null){
- if (prof!=null) prof.appStep(AppStep.ProcessAudio);
- audioRenderer.update(timer.getTimePerFrame());
- }
-
- // user code here..
- }
-
- protected void destroyInput(){
- if (mouseInput != null)
- mouseInput.destroy();
-
- if (keyInput != null)
- keyInput.destroy();
-
- if (joyInput != null)
- joyInput.destroy();
-
- if (touchInput != null)
- touchInput.destroy();
-
- inputManager = null;
- }
-
- /**
- * Do not call manually.
- * Callback from ContextListener.
- */
- public void destroy(){
- stateManager.cleanup();
-
- destroyInput();
- if (audioRenderer != null)
- audioRenderer.cleanup();
-
- timer.reset();
- context = null;
- }
+ public void enqueue(Runnable runnable);
/**
* @return The GUI viewport. Which is used for the on screen
* statistics and FPS.
*/
- public ViewPort getGuiViewPort() {
- return guiViewPort;
- }
-
- public ViewPort getViewPort() {
- return viewPort;
- }
-
- private class RunnableWrapper implements Callable{
- private final Runnable runnable;
-
- public RunnableWrapper(Runnable runnable){
- this.runnable = runnable;
- }
+ public ViewPort getGuiViewPort();
- @Override
- public Object call(){
- runnable.run();
- return null;
- }
-
- }
-
+ public ViewPort getViewPort();
}
diff --git a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java
new file mode 100644
index 000000000..89a81be5b
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java
@@ -0,0 +1,774 @@
+/*
+ * Copyright (c) 2009-2012 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;
+
+import com.jme3.app.state.AppStateManager;
+import com.jme3.asset.AssetManager;
+import com.jme3.audio.AudioContext;
+import com.jme3.audio.AudioRenderer;
+import com.jme3.audio.Listener;
+import com.jme3.input.*;
+import com.jme3.math.Vector3f;
+import com.jme3.profile.AppProfiler;
+import com.jme3.profile.AppStep;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.system.*;
+import com.jme3.system.JmeContext.Type;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Future;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The LegacyApplication
class represents an instance of a
+ * real-time 3D rendering jME application.
+ *
+ * An LegacyApplication
provides all the tools that are commonly used in jME3
+ * applications.
+ *
+ * jME3 applications *SHOULD NOT EXTEND* this class but extend {@link com.jme3.app.SimpleApplication} instead.
+ *
+ */
+public class LegacyApplication implements Application, SystemListener {
+
+ private static final Logger logger = Logger.getLogger(LegacyApplication.class.getName());
+
+ protected AssetManager assetManager;
+
+ protected AudioRenderer audioRenderer;
+ protected Renderer renderer;
+ protected RenderManager renderManager;
+ protected ViewPort viewPort;
+ protected ViewPort guiViewPort;
+
+ protected JmeContext context;
+ protected AppSettings settings;
+ protected Timer timer = new NanoTimer();
+ protected Camera cam;
+ protected Listener listener;
+
+ protected boolean inputEnabled = true;
+ protected LostFocusBehavior lostFocusBehavior = LostFocusBehavior.ThrottleOnLostFocus;
+ protected float speed = 1f;
+ protected boolean paused = false;
+ protected MouseInput mouseInput;
+ protected KeyInput keyInput;
+ protected JoyInput joyInput;
+ protected TouchInput touchInput;
+ protected InputManager inputManager;
+ protected AppStateManager stateManager;
+
+ protected AppProfiler prof;
+
+ private final ConcurrentLinkedQueue> taskQueue = new ConcurrentLinkedQueue>();
+
+ /**
+ * Create a new instance of LegacyApplication
.
+ */
+ public LegacyApplication(){
+ initStateManager();
+ }
+
+ /**
+ * Determine the application's behavior when unfocused.
+ *
+ * @return The lost focus behavior of the application.
+ */
+ public LostFocusBehavior getLostFocusBehavior() {
+ return lostFocusBehavior;
+ }
+
+ /**
+ * Change the application's behavior when unfocused.
+ *
+ * By default, the application will
+ * {@link LostFocusBehavior#ThrottleOnLostFocus throttle the update loop}
+ * so as to not take 100% CPU usage when it is not in focus, e.g.
+ * alt-tabbed, minimized, or obstructed by another window.
+ *
+ * @param lostFocusBehavior The new lost focus behavior to use.
+ *
+ * @see LostFocusBehavior
+ */
+ public void setLostFocusBehavior(LostFocusBehavior lostFocusBehavior) {
+ this.lostFocusBehavior = lostFocusBehavior;
+ }
+
+ /**
+ * Returns true if pause on lost focus is enabled, false otherwise.
+ *
+ * @return true if pause on lost focus is enabled
+ *
+ * @see #getLostFocusBehavior()
+ */
+ public boolean isPauseOnLostFocus() {
+ return getLostFocusBehavior() == LostFocusBehavior.PauseOnLostFocus;
+ }
+
+ /**
+ * Enable or disable pause on lost focus.
+ *
+ * By default, pause on lost focus is enabled.
+ * If enabled, the application will stop updating
+ * when it loses focus or becomes inactive (e.g. alt-tab).
+ * For online or real-time applications, this might not be preferable,
+ * so this feature should be set to disabled. For other applications,
+ * it is best to keep it on so that CPU usage is not used when
+ * not necessary.
+ *
+ * @param pauseOnLostFocus True to enable pause on lost focus, false
+ * otherwise.
+ *
+ * @see #setLostFocusBehavior(com.jme3.app.LostFocusBehavior)
+ */
+ public void setPauseOnLostFocus(boolean pauseOnLostFocus) {
+ if (pauseOnLostFocus) {
+ setLostFocusBehavior(LostFocusBehavior.PauseOnLostFocus);
+ } else {
+ setLostFocusBehavior(LostFocusBehavior.Disabled);
+ }
+ }
+
+ @Deprecated
+ public void setAssetManager(AssetManager assetManager){
+ if (this.assetManager != null)
+ throw new IllegalStateException("Can only set asset manager"
+ + " before initialization.");
+
+ this.assetManager = assetManager;
+ }
+
+ private void initAssetManager(){
+ URL assetCfgUrl = null;
+
+ if (settings != null){
+ String assetCfg = settings.getString("AssetConfigURL");
+ if (assetCfg != null){
+ try {
+ assetCfgUrl = new URL(assetCfg);
+ } catch (MalformedURLException ex) {
+ }
+ if (assetCfgUrl == null) {
+ assetCfgUrl = LegacyApplication.class.getClassLoader().getResource(assetCfg);
+ if (assetCfgUrl == null) {
+ logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg);
+ return;
+ }
+ }
+ }
+ }
+ if (assetCfgUrl == null) {
+ assetCfgUrl = JmeSystem.getPlatformAssetConfigURL();
+ }
+ if (assetManager == null){
+ assetManager = JmeSystem.newAssetManager(assetCfgUrl);
+ }
+ }
+
+ /**
+ * Set the display settings to define the display created.
+ *
+ * Examples of display parameters include display pixel width and height,
+ * color bit depth, z-buffer bits, anti-aliasing samples, and update frequency.
+ * If this method is called while the application is already running, then
+ * {@link #restart() } must be called to apply the settings to the display.
+ *
+ * @param settings The settings to set.
+ */
+ public void setSettings(AppSettings settings){
+ this.settings = settings;
+ if (context != null && settings.useInput() != inputEnabled){
+ // may need to create or destroy input based
+ // on settings change
+ inputEnabled = !inputEnabled;
+ if (inputEnabled){
+ initInput();
+ }else{
+ destroyInput();
+ }
+ }else{
+ inputEnabled = settings.useInput();
+ }
+ }
+
+ /**
+ * Sets the Timer implementation that will be used for calculating
+ * frame times. By default, Application will use the Timer as returned
+ * by the current JmeContext implementation.
+ */
+ public void setTimer(Timer timer){
+ this.timer = timer;
+
+ if (timer != null) {
+ timer.reset();
+ }
+
+ if (renderManager != null) {
+ renderManager.setTimer(timer);
+ }
+ }
+
+ public Timer getTimer(){
+ return timer;
+ }
+
+ private void initDisplay(){
+ // aquire important objects
+ // from the context
+ settings = context.getSettings();
+
+ // Only reset the timer if a user has not already provided one
+ if (timer == null) {
+ timer = context.getTimer();
+ }
+
+ renderer = context.getRenderer();
+ }
+
+ private void initAudio(){
+ if (settings.getAudioRenderer() != null && context.getType() != Type.Headless){
+ audioRenderer = JmeSystem.newAudioRenderer(settings);
+ audioRenderer.initialize();
+ AudioContext.setAudioRenderer(audioRenderer);
+
+ listener = new Listener();
+ audioRenderer.setListener(listener);
+ }
+ }
+
+ /**
+ * Creates the camera to use for rendering. Default values are perspective
+ * projection with 45° field of view, with near and far values 1 and 1000
+ * units respectively.
+ */
+ private void initCamera(){
+ cam = new Camera(settings.getWidth(), settings.getHeight());
+
+ cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f);
+ cam.setLocation(new Vector3f(0f, 0f, 10f));
+ cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);
+
+ renderManager = new RenderManager(renderer);
+ //Remy - 09/14/2010 setted the timer in the renderManager
+ renderManager.setTimer(timer);
+
+ if (prof != null) {
+ renderManager.setAppProfiler(prof);
+ }
+
+ viewPort = renderManager.createMainView("Default", cam);
+ viewPort.setClearFlags(true, true, true);
+
+ // Create a new cam for the gui
+ Camera guiCam = new Camera(settings.getWidth(), settings.getHeight());
+ guiViewPort = renderManager.createPostView("Gui Default", guiCam);
+ guiViewPort.setClearFlags(false, false, false);
+ }
+
+ /**
+ * Initializes mouse and keyboard input. Also
+ * initializes joystick input if joysticks are enabled in the
+ * AppSettings.
+ */
+ private void initInput(){
+ mouseInput = context.getMouseInput();
+ if (mouseInput != null)
+ mouseInput.initialize();
+
+ keyInput = context.getKeyInput();
+ if (keyInput != null)
+ keyInput.initialize();
+
+ touchInput = context.getTouchInput();
+ if (touchInput != null)
+ touchInput.initialize();
+
+ if (!settings.getBoolean("DisableJoysticks")){
+ joyInput = context.getJoyInput();
+ if (joyInput != null)
+ joyInput.initialize();
+ }
+
+ inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput);
+ }
+
+ private void initStateManager(){
+ stateManager = new AppStateManager(this);
+
+ // Always register a ResetStatsState to make sure
+ // that the stats are cleared every frame
+ stateManager.attach(new ResetStatsState());
+ }
+
+ /**
+ * @return The {@link AssetManager asset manager} for this application.
+ */
+ public AssetManager getAssetManager(){
+ return assetManager;
+ }
+
+ /**
+ * @return the {@link InputManager input manager}.
+ */
+ public InputManager getInputManager(){
+ return inputManager;
+ }
+
+ /**
+ * @return the {@link AppStateManager app state manager}
+ */
+ public AppStateManager getStateManager() {
+ return stateManager;
+ }
+
+ /**
+ * @return the {@link RenderManager render manager}
+ */
+ public RenderManager getRenderManager() {
+ return renderManager;
+ }
+
+ /**
+ * @return The {@link Renderer renderer} for the application
+ */
+ public Renderer getRenderer(){
+ return renderer;
+ }
+
+ /**
+ * @return The {@link AudioRenderer audio renderer} for the application
+ */
+ public AudioRenderer getAudioRenderer() {
+ return audioRenderer;
+ }
+
+ /**
+ * @return The {@link Listener listener} object for audio
+ */
+ public Listener getListener() {
+ return listener;
+ }
+
+ /**
+ * @return The {@link JmeContext display context} for the application
+ */
+ public JmeContext getContext(){
+ return context;
+ }
+
+ /**
+ * @return The {@link Camera camera} for the application
+ */
+ public Camera getCamera(){
+ return cam;
+ }
+
+ /**
+ * Starts the application in {@link Type#Display display} mode.
+ *
+ * @see #start(com.jme3.system.JmeContext.Type)
+ */
+ public void start(){
+ start(JmeContext.Type.Display, false);
+ }
+
+ /**
+ * Starts the application in {@link Type#Display display} mode.
+ *
+ * @see #start(com.jme3.system.JmeContext.Type)
+ */
+ public void start(boolean waitFor){
+ start(JmeContext.Type.Display, waitFor);
+ }
+
+ /**
+ * Starts the application.
+ * Creating a rendering context and executing
+ * the main loop in a separate thread.
+ */
+ public void start(JmeContext.Type contextType) {
+ start(contextType, false);
+ }
+
+ /**
+ * Starts the application.
+ * Creating a rendering context and executing
+ * the main loop in a separate thread.
+ */
+ public void start(JmeContext.Type contextType, boolean waitFor){
+ if (context != null && context.isCreated()){
+ logger.warning("start() called when application already created!");
+ return;
+ }
+
+ if (settings == null){
+ settings = new AppSettings(true);
+ }
+
+ logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
+ context = JmeSystem.newContext(settings, contextType);
+ context.setSystemListener(this);
+ context.create(waitFor);
+ }
+
+ /**
+ * Sets an AppProfiler hook that will be called back for
+ * specific steps within a single update frame. Value defaults
+ * to null.
+ */
+ public void setAppProfiler(AppProfiler prof) {
+ this.prof = prof;
+ if (renderManager != null) {
+ renderManager.setAppProfiler(prof);
+ }
+ }
+
+ /**
+ * Returns the current AppProfiler hook, or null if none is set.
+ */
+ public AppProfiler getAppProfiler() {
+ return prof;
+ }
+
+ /**
+ * Initializes the application's canvas for use.
+ *
+ * After calling this method, cast the {@link #getContext() context} to
+ * {@link JmeCanvasContext},
+ * then acquire the canvas with {@link JmeCanvasContext#getCanvas() }
+ * and attach it to an AWT/Swing Frame.
+ * The rendering thread will start when the canvas becomes visible on
+ * screen, however if you wish to start the context immediately you
+ * may call {@link #startCanvas() } to force the rendering thread
+ * to start.
+ *
+ * @see JmeCanvasContext
+ * @see Type#Canvas
+ */
+ public void createCanvas(){
+ if (context != null && context.isCreated()){
+ logger.warning("createCanvas() called when application already created!");
+ return;
+ }
+
+ if (settings == null){
+ settings = new AppSettings(true);
+ }
+
+ logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
+ context = JmeSystem.newContext(settings, JmeContext.Type.Canvas);
+ context.setSystemListener(this);
+ }
+
+ /**
+ * Starts the rendering thread after createCanvas() has been called.
+ *
+ * Same as calling startCanvas(false)
+ *
+ * @see #startCanvas(boolean)
+ */
+ public void startCanvas(){
+ startCanvas(false);
+ }
+
+ /**
+ * Starts the rendering thread after createCanvas() has been called.
+ *
+ * Calling this method is optional, the canvas will start automatically
+ * when it becomes visible.
+ *
+ * @param waitFor If true, the current thread will block until the
+ * rendering thread is running
+ */
+ public void startCanvas(boolean waitFor){
+ context.create(waitFor);
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void reshape(int w, int h){
+ renderManager.notifyReshape(w, h);
+ }
+
+ /**
+ * Restarts the context, applying any changed settings.
+ *
+ * Changes to the {@link AppSettings} of this Application are not
+ * applied immediately; calling this method forces the context
+ * to restart, applying the new settings.
+ */
+ public void restart(){
+ context.setSettings(settings);
+ context.restart();
+ }
+
+ /**
+ * Requests the context to close, shutting down the main loop
+ * and making necessary cleanup operations.
+ *
+ * Same as calling stop(false)
+ *
+ * @see #stop(boolean)
+ */
+ public void stop(){
+ stop(false);
+ }
+
+ /**
+ * Requests the context to close, shutting down the main loop
+ * and making necessary cleanup operations.
+ * After the application has stopped, it cannot be used anymore.
+ */
+ public void stop(boolean waitFor){
+ logger.log(Level.FINE, "Closing application: {0}", getClass().getName());
+ context.destroy(waitFor);
+ }
+
+ /**
+ * Do not call manually.
+ * Callback from ContextListener.
+ *
+ * Initializes the Application
, by creating a display and
+ * default camera. If display settings are not specified, a default
+ * 640x480 display is created. Default values are used for the camera;
+ * perspective projection with 45° field of view, with near
+ * and far values 1 and 1000 units respectively.
+ */
+ public void initialize(){
+ if (assetManager == null){
+ initAssetManager();
+ }
+
+ initDisplay();
+ initCamera();
+
+ if (inputEnabled){
+ initInput();
+ }
+ initAudio();
+
+ // update timer so that the next delta is not too large
+// timer.update();
+ timer.reset();
+
+ // user code here..
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void handleError(String errMsg, Throwable t){
+ // Print error to log.
+ logger.log(Level.SEVERE, errMsg, t);
+ // Display error message on screen if not in headless mode
+ if (context.getType() != JmeContext.Type.Headless) {
+ if (t != null) {
+ JmeSystem.showErrorDialog(errMsg + "\n" + t.getClass().getSimpleName() +
+ (t.getMessage() != null ? ": " + t.getMessage() : ""));
+ } else {
+ JmeSystem.showErrorDialog(errMsg);
+ }
+ }
+
+ stop(); // stop the application
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void gainFocus(){
+ if (lostFocusBehavior != LostFocusBehavior.Disabled) {
+ if (lostFocusBehavior == LostFocusBehavior.PauseOnLostFocus) {
+ paused = false;
+ }
+ context.setAutoFlushFrames(true);
+ if (inputManager != null) {
+ inputManager.reset();
+ }
+ }
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void loseFocus(){
+ if (lostFocusBehavior != LostFocusBehavior.Disabled){
+ if (lostFocusBehavior == LostFocusBehavior.PauseOnLostFocus) {
+ paused = true;
+ }
+ context.setAutoFlushFrames(false);
+ }
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void requestClose(boolean esc){
+ context.destroy(false);
+ }
+
+ /**
+ * Enqueues a task/callable object to execute in the jME3
+ * rendering thread.
+ *
+ * Callables are executed right at the beginning of the main loop.
+ * They are executed even if the application is currently paused
+ * or out of focus.
+ *
+ * @param callable The callable to run in the main jME3 thread
+ */
+ public Future enqueue(Callable callable) {
+ AppTask task = new AppTask(callable);
+ taskQueue.add(task);
+ return task;
+ }
+
+ /**
+ * Enqueues a runnable object to execute in the jME3
+ * rendering thread.
+ *
+ * Runnables are executed right at the beginning of the main loop.
+ * They are executed even if the application is currently paused
+ * or out of focus.
+ *
+ * @param runnable The runnable to run in the main jME3 thread
+ */
+ public void enqueue(Runnable runnable){
+ enqueue(new RunnableWrapper(runnable));
+ }
+
+ /**
+ * Runs tasks enqueued via {@link #enqueue(Callable)}
+ */
+ protected void runQueuedTasks() {
+ AppTask> task;
+ while( (task = taskQueue.poll()) != null ) {
+ if (!task.isCancelled()) {
+ task.invoke();
+ }
+ }
+ }
+
+ /**
+ * Do not call manually.
+ * Callback from ContextListener.
+ */
+ public void update(){
+ // Make sure the audio renderer is available to callables
+ AudioContext.setAudioRenderer(audioRenderer);
+
+ if (prof!=null) prof.appStep(AppStep.QueuedTasks);
+ runQueuedTasks();
+
+ if (speed == 0 || paused)
+ return;
+
+ timer.update();
+
+ if (inputEnabled){
+ if (prof!=null) prof.appStep(AppStep.ProcessInput);
+ inputManager.update(timer.getTimePerFrame());
+ }
+
+ if (audioRenderer != null){
+ if (prof!=null) prof.appStep(AppStep.ProcessAudio);
+ audioRenderer.update(timer.getTimePerFrame());
+ }
+
+ // user code here..
+ }
+
+ protected void destroyInput(){
+ if (mouseInput != null)
+ mouseInput.destroy();
+
+ if (keyInput != null)
+ keyInput.destroy();
+
+ if (joyInput != null)
+ joyInput.destroy();
+
+ if (touchInput != null)
+ touchInput.destroy();
+
+ inputManager = null;
+ }
+
+ /**
+ * Do not call manually.
+ * Callback from ContextListener.
+ */
+ public void destroy(){
+ stateManager.cleanup();
+
+ destroyInput();
+ if (audioRenderer != null)
+ audioRenderer.cleanup();
+
+ timer.reset();
+ }
+
+ /**
+ * @return The GUI viewport. Which is used for the on screen
+ * statistics and FPS.
+ */
+ public ViewPort getGuiViewPort() {
+ return guiViewPort;
+ }
+
+ public ViewPort getViewPort() {
+ return viewPort;
+ }
+
+ private class RunnableWrapper implements Callable{
+ private final Runnable runnable;
+
+ public RunnableWrapper(Runnable runnable){
+ this.runnable = runnable;
+ }
+
+ @Override
+ public Object call(){
+ runnable.run();
+ return null;
+ }
+
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java b/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java
index 9433754dc..310191007 100644
--- a/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java
+++ b/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java
@@ -59,17 +59,17 @@ import com.jme3.system.JmeSystem;
*
C | - Display the camera position and rotation in the console. |
* M | - Display memory usage in the console. |
*
- *
+ *
* A {@link com.jme3.app.FlyCamAppState} is by default attached as well and can
* be removed by calling stateManager.detach( stateManager.getState(FlyCamAppState.class) );
*/
-public abstract class SimpleApplication extends Application {
+public abstract class SimpleApplication extends LegacyApplication {
public static final String INPUT_MAPPING_EXIT = "SIMPLEAPP_Exit";
public static final String INPUT_MAPPING_CAMERA_POS = DebugKeysAppState.INPUT_MAPPING_CAMERA_POS;
public static final String INPUT_MAPPING_MEMORY = DebugKeysAppState.INPUT_MAPPING_MEMORY;
public static final String INPUT_MAPPING_HIDE_STATS = "SIMPLEAPP_HideStats";
-
+
protected Node rootNode = new Node("Root Node");
protected Node guiNode = new Node("Gui Node");
protected BitmapText fpsText;
@@ -77,7 +77,7 @@ public abstract class SimpleApplication extends Application {
protected FlyByCamera flyCam;
protected boolean showSettings = true;
private AppActionListener actionListener = new AppActionListener();
-
+
private class AppActionListener implements ActionListener {
public void onAction(String name, boolean value, float tpf) {
@@ -101,7 +101,7 @@ public abstract class SimpleApplication extends Application {
public SimpleApplication( AppState... initialStates ) {
super();
-
+
if (initialStates != null) {
for (AppState a : initialStates) {
if (a != null) {
@@ -193,7 +193,7 @@ public abstract class SimpleApplication extends Application {
guiViewPort.attachScene(guiNode);
if (inputManager != null) {
-
+
// We have to special-case the FlyCamAppState because too
// many SimpleApplication subclasses expect it to exist in
// simpleInit(). But at least it only gets initialized if
@@ -201,7 +201,7 @@ public abstract class SimpleApplication extends Application {
if (stateManager.getState(FlyCamAppState.class) != null) {
flyCam = new FlyByCamera(cam);
flyCam.setMoveSpeed(1f); // odd to set this here but it did it before
- stateManager.getState(FlyCamAppState.class).setCamera( flyCam );
+ stateManager.getState(FlyCamAppState.class).setCamera( flyCam );
}
if (context.getType() == Type.Display) {
@@ -210,10 +210,10 @@ public abstract class SimpleApplication extends Application {
if (stateManager.getState(StatsAppState.class) != null) {
inputManager.addMapping(INPUT_MAPPING_HIDE_STATS, new KeyTrigger(KeyInput.KEY_F5));
- inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS);
+ inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS);
}
-
- inputManager.addListener(actionListener, INPUT_MAPPING_EXIT);
+
+ inputManager.addListener(actionListener, INPUT_MAPPING_EXIT);
}
if (stateManager.getState(StatsAppState.class) != null) {
@@ -230,37 +230,37 @@ public abstract class SimpleApplication extends Application {
@Override
public void update() {
if (prof!=null) prof.appStep(AppStep.BeginFrame);
-
+
super.update(); // makes sure to execute AppTasks
if (speed == 0 || paused) {
return;
}
float tpf = timer.getTimePerFrame() * speed;
-
+
// update states
if (prof!=null) prof.appStep(AppStep.StateManagerUpdate);
stateManager.update(tpf);
// simple update and root node
simpleUpdate(tpf);
-
+
if (prof!=null) prof.appStep(AppStep.SpatialUpdate);
rootNode.updateLogicalState(tpf);
guiNode.updateLogicalState(tpf);
-
+
rootNode.updateGeometricState();
guiNode.updateGeometricState();
-
+
// render states
if (prof!=null) prof.appStep(AppStep.StateManagerRender);
stateManager.render(renderManager);
-
+
if (prof!=null) prof.appStep(AppStep.RenderFrame);
renderManager.render(tpf, context.isRenderable());
simpleRender(renderManager);
stateManager.postRender();
-
+
if (prof!=null) prof.appStep(AppStep.EndFrame);
}
diff --git a/jme3-core/src/main/java/com/jme3/app/StatsAppState.java b/jme3-core/src/main/java/com/jme3/app/StatsAppState.java
index 346733257..d5ffde097 100644
--- a/jme3-core/src/main/java/com/jme3/app/StatsAppState.java
+++ b/jme3-core/src/main/java/com/jme3/app/StatsAppState.java
@@ -46,7 +46,7 @@ import com.jme3.scene.shape.Quad;
/**
* Displays stats in SimpleApplication's GUI node or
- * using the node and font parameters provided.
+ * using the node and font parameters provided.
*
* @author Paul Speed
*/
@@ -58,7 +58,7 @@ public class StatsAppState extends AbstractAppState {
private boolean showFps = true;
private boolean showStats = true;
private boolean darkenBehind = true;
-
+
protected Node guiNode;
protected float secondCounter = 0.0f;
protected int frameCounter = 0;
@@ -68,7 +68,7 @@ public class StatsAppState extends AbstractAppState {
protected Geometry darkenStats;
public StatsAppState() {
- }
+ }
public StatsAppState( Node guiNode, BitmapFont guiFont ) {
this.guiNode = guiNode;
@@ -89,7 +89,7 @@ public class StatsAppState extends AbstractAppState {
public BitmapText getFpsText() {
return fpsText;
}
-
+
public StatsView getStatsView() {
return statsView;
}
@@ -110,7 +110,7 @@ public class StatsAppState extends AbstractAppState {
if (darkenFps != null) {
darkenFps.setCullHint(showFps && darkenBehind ? CullHint.Never : CullHint.Always);
}
-
+
}
}
@@ -138,7 +138,7 @@ public class StatsAppState extends AbstractAppState {
public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);
this.app = app;
-
+
if (app instanceof SimpleApplication) {
SimpleApplication simpleApp = (SimpleApplication)app;
if (guiNode == null) {
@@ -147,21 +147,21 @@ public class StatsAppState extends AbstractAppState {
if (guiFont == null ) {
guiFont = simpleApp.guiFont;
}
- }
-
+ }
+
if (guiNode == null) {
throw new RuntimeException( "No guiNode specific and cannot be automatically determined." );
- }
-
+ }
+
if (guiFont == null) {
guiFont = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
}
-
- loadFpsText();
- loadStatsView();
+
+ loadFpsText();
+ loadStatsView();
loadDarken();
}
-
+
/**
* Attaches FPS statistics to guiNode and displays it on the screen.
*
@@ -170,12 +170,12 @@ public class StatsAppState extends AbstractAppState {
if (fpsText == null) {
fpsText = new BitmapText(guiFont, false);
}
-
+
fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0);
fpsText.setText("Frames per second");
fpsText.setCullHint(showFps ? CullHint.Never : CullHint.Always);
guiNode.attachChild(fpsText);
-
+
}
/**
@@ -184,53 +184,53 @@ public class StatsAppState extends AbstractAppState {
*
*/
public void loadStatsView() {
- statsView = new StatsView("Statistics View",
- app.getAssetManager(),
+ statsView = new StatsView("Statistics View",
+ app.getAssetManager(),
app.getRenderer().getStatistics());
// move it up so it appears above fps text
statsView.setLocalTranslation(0, fpsText.getLineHeight(), 0);
statsView.setEnabled(showStats);
- statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always);
+ statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always);
guiNode.attachChild(statsView);
}
-
+
public void loadDarken() {
- Material mat = new Material(app.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", new ColorRGBA(0,0,0,0.5f));
mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
-
+
darkenFps = new Geometry("StatsDarken", new Quad(200, fpsText.getLineHeight()));
darkenFps.setMaterial(mat);
darkenFps.setLocalTranslation(0, 0, -1);
darkenFps.setCullHint(showFps && darkenBehind ? CullHint.Never : CullHint.Always);
guiNode.attachChild(darkenFps);
-
+
darkenStats = new Geometry("StatsDarken", new Quad(200, statsView.getHeight()));
darkenStats.setMaterial(mat);
darkenStats.setLocalTranslation(0, fpsText.getHeight(), -1);
darkenStats.setCullHint(showStats && darkenBehind ? CullHint.Never : CullHint.Always);
guiNode.attachChild(darkenStats);
}
-
+
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
-
+
if (enabled) {
fpsText.setCullHint(showFps ? CullHint.Never : CullHint.Always);
darkenFps.setCullHint(showFps && darkenBehind ? CullHint.Never : CullHint.Always);
statsView.setEnabled(showStats);
- statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always);
+ statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always);
darkenStats.setCullHint(showStats && darkenBehind ? CullHint.Never : CullHint.Always);
} else {
fpsText.setCullHint(CullHint.Always);
darkenFps.setCullHint(CullHint.Always);
statsView.setEnabled(false);
- statsView.setCullHint(CullHint.Always);
+ statsView.setCullHint(CullHint.Always);
darkenStats.setCullHint(CullHint.Always);
}
}
-
+
@Override
public void update(float tpf) {
if (showFps) {
@@ -241,14 +241,14 @@ public class StatsAppState extends AbstractAppState {
fpsText.setText("Frames per second: " + fps);
secondCounter = 0.0f;
frameCounter = 0;
- }
+ }
}
}
@Override
public void cleanup() {
super.cleanup();
-
+
guiNode.detachChild(statsView);
guiNode.detachChild(fpsText);
guiNode.detachChild(darkenFps);
diff --git a/jme3-core/src/main/java/com/jme3/app/StatsView.java b/jme3-core/src/main/java/com/jme3/app/StatsView.java
index a0446e85e..8b88833c2 100644
--- a/jme3-core/src/main/java/com/jme3/app/StatsView.java
+++ b/jme3-core/src/main/java/com/jme3/app/StatsView.java
@@ -41,6 +41,8 @@ import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
/**
* The StatsView
provides a heads-up display (HUD) of various
@@ -58,7 +60,7 @@ import com.jme3.scene.control.Control;
* rootNode.attachChild(statsView);
*
*/
-public class StatsView extends Node implements Control {
+public class StatsView extends Node implements Control, JmeCloneable {
private BitmapText statText;
private Statistics statistics;
@@ -67,7 +69,7 @@ public class StatsView extends Node implements Control {
private int[] statData;
private boolean enabled = true;
-
+
private final StringBuilder stringBuilder = new StringBuilder();
public StatsView(String name, AssetManager manager, Statistics stats){
@@ -93,32 +95,43 @@ public class StatsView extends Node implements Control {
public float getHeight() {
return statText.getLineHeight() * statLabels.length;
}
-
+
public void update(float tpf) {
-
- if (!isEnabled())
+
+ if (!isEnabled())
return;
-
+
statistics.getData(statData);
stringBuilder.setLength(0);
-
- // Need to walk through it backwards, as the first label
+
+ // Need to walk through it backwards, as the first label
// should appear at the bottom, not the top.
for (int i = statLabels.length - 1; i >= 0; i--) {
stringBuilder.append(statLabels[i]).append(" = ").append(statData[i]).append('\n');
}
statText.setText(stringBuilder);
-
+
// Moved to ResetStatsState to make sure it is
// done even if there is no StatsView or the StatsView
// is disable.
//statistics.clearFrame();
}
+ @Override
public Control cloneForSpatial(Spatial spatial) {
return (Control) spatial;
}
+ @Override
+ public StatsView jmeClone() {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+
public void setSpatial(Spatial spatial) {
}
diff --git a/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java b/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java
index f386af5db..c6ba5b124 100644
--- a/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java
+++ b/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java
@@ -249,21 +249,23 @@ public class ScreenshotAppState extends AbstractAppState implements ActionListen
}
logger.log(Level.FINE, "Saving ScreenShot to: {0}", file.getAbsolutePath());
- OutputStream outStream = null;
try {
- outStream = new FileOutputStream(file);
- JmeSystem.writeImageFile(outStream, "png", outBuf, width, height);
+ writeImageFile(file);
} catch (IOException ex) {
logger.log(Level.SEVERE, "Error while saving screenshot", ex);
- } finally {
- if (outStream != null){
- try {
- outStream.close();
- } catch (IOException ex) {
- logger.log(Level.SEVERE, "Error while saving screenshot", ex);
- }
- }
- }
+ }
}
}
+
+ /**
+ * Called by postFrame() once the screen has been captured to outBuf.
+ */
+ protected void writeImageFile( File file ) throws IOException {
+ OutputStream outStream = new FileOutputStream(file);
+ try {
+ JmeSystem.writeImageFile(outStream, "png", outBuf, width, height);
+ } finally {
+ outStream.close();
+ }
+ }
}
diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java
index 2c5cc5b98..55a57768d 100644
--- a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java
+++ b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java
@@ -41,26 +41,27 @@ import com.jme3.export.OutputCapsule;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.util.PlaceholderAssets;
+import com.jme3.util.clone.Cloner;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
- * An AudioNode
is a scene Node which can play audio assets.
- *
- * An AudioNode is either positional or ambient, with positional being the
- * default. Once a positional node is attached to the scene, its location and
- * velocity relative to the {@link Listener} affect how it sounds when played.
- * Positional nodes can only play monoaural (single-channel) assets, not stereo
- * ones.
- *
- * An ambient AudioNode plays in "headspace", meaning that the node's location
- * and velocity do not affect how it sounds when played. Ambient audio nodes can
- * play stereo assets.
- *
- * The "positional" property of an AudioNode can be set via
+ * An AudioNode
is a scene Node which can play audio assets.
+ *
+ * An AudioNode is either positional or ambient, with positional being the
+ * default. Once a positional node is attached to the scene, its location and
+ * velocity relative to the {@link Listener} affect how it sounds when played.
+ * Positional nodes can only play monoaural (single-channel) assets, not stereo
+ * ones.
+ *
+ * An ambient AudioNode plays in "headspace", meaning that the node's location
+ * and velocity do not affect how it sounds when played. Ambient audio nodes can
+ * play stereo assets.
+ *
+ * The "positional" property of an AudioNode can be set via
* {@link AudioNode#setPositional(boolean) }.
- *
+ *
* @author normenhansen
* @author Kirill Vainer
*/
@@ -99,15 +100,15 @@ public class AudioNode extends Node implements AudioSource {
* {@link AudioNode#play() } is called.
*/
Playing,
-
+
/**
* The audio node is currently paused.
*/
Paused,
-
+
/**
* The audio node is currently stopped.
- * This will be set if {@link AudioNode#stop() } is called
+ * This will be set if {@link AudioNode#stop() } is called
* or the audio has reached the end of the file.
*/
Stopped,
@@ -121,14 +122,14 @@ public class AudioNode extends Node implements AudioSource {
/**
* Creates a new AudioNode
with the given data and key.
- *
+ *
* @param audioData The audio data contains the audio track to play.
* @param audioKey The audio key that was used to load the AudioData
*/
public AudioNode(AudioData audioData, AudioKey audioKey) {
setAudioData(audioData, audioKey);
}
-
+
/**
* Creates a new AudioNode
with the given audio file.
* @param assetManager The asset manager to use to load the audio file
@@ -142,16 +143,16 @@ public class AudioNode extends Node implements AudioSource {
/**
* Creates a new AudioNode
with the given audio file.
- *
+ *
* @param assetManager The asset manager to use to load the audio file
* @param name The filename of the audio file
- * @param stream If true, the audio will be streamed gradually from disk,
+ * @param stream If true, the audio will be streamed gradually from disk,
* otherwise, it will be buffered.
* @param streamCache If stream is also true, then this specifies if
* the stream cache is used. When enabled, the audio stream will
- * be read entirely but not decoded, allowing features such as
+ * be read entirely but not decoded, allowing features such as
* seeking, looping and determining duration.
- *
+ *
* @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType)} instead
*/
public AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache) {
@@ -161,12 +162,12 @@ public class AudioNode extends Node implements AudioSource {
/**
* Creates a new AudioNode
with the given audio file.
- *
+ *
* @param assetManager The asset manager to use to load the audio file
* @param name The filename of the audio file
- * @param stream If true, the audio will be streamed gradually from disk,
+ * @param stream If true, the audio will be streamed gradually from disk,
* otherwise, it will be buffered.
- *
+ *
* @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType)} instead
*/
public AudioNode(AssetManager assetManager, String name, boolean stream) {
@@ -175,20 +176,20 @@ public class AudioNode extends Node implements AudioSource {
/**
* Creates a new AudioNode
with the given audio file.
- *
+ *
* @param audioRenderer The audio renderer to use for playing. Cannot be null.
* @param assetManager The asset manager to use to load the audio file
* @param name The filename of the audio file
- *
+ *
* @deprecated AudioRenderer parameter is ignored.
*/
public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name) {
this(assetManager, name, DataType.Buffer);
}
-
+
/**
* Creates a new AudioNode
with the given audio file.
- *
+ *
* @param assetManager The asset manager to use to load the audio file
* @param name The filename of the audio file
* @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType) } instead
@@ -196,14 +197,14 @@ public class AudioNode extends Node implements AudioSource {
public AudioNode(AssetManager assetManager, String name) {
this(assetManager, name, DataType.Buffer);
}
-
+
protected AudioRenderer getRenderer() {
AudioRenderer result = AudioContext.getAudioRenderer();
if( result == null )
throw new IllegalStateException( "No audio renderer available, make sure call is being performed on render thread." );
- return result;
+ return result;
}
-
+
/**
* Start playing the audio.
*/
@@ -217,7 +218,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* Start playing an instance of this audio. This method can be used
* to play the same AudioNode
multiple times. Note
- * that changes to the parameters of this AudioNode will not effect the
+ * that changes to the parameters of this AudioNode will not effect the
* instances already playing.
*/
public void playInstance(){
@@ -226,21 +227,21 @@ public class AudioNode extends Node implements AudioSource {
}
getRenderer().playSourceInstance(this);
}
-
+
/**
* Stop playing the audio that was started with {@link AudioNode#play() }.
*/
public void stop(){
getRenderer().stopSource(this);
}
-
+
/**
* Pause the audio that was started with {@link AudioNode#play() }.
*/
public void pause(){
getRenderer().pauseSource(this);
}
-
+
/**
* Do not use.
*/
@@ -261,7 +262,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return The {#link Filter dry filter} that is set.
- * @see AudioNode#setDryFilter(com.jme3.audio.Filter)
+ * @see AudioNode#setDryFilter(com.jme3.audio.Filter)
*/
public Filter getDryFilter() {
return dryFilter;
@@ -269,14 +270,14 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the dry filter to use for this audio node.
- *
- * When {@link AudioNode#setReverbEnabled(boolean) reverb} is used,
- * the dry filter will only influence the "dry" portion of the audio,
+ *
+ * When {@link AudioNode#setReverbEnabled(boolean) reverb} is used,
+ * the dry filter will only influence the "dry" portion of the audio,
* e.g. not the reverberated parts of the AudioNode playing.
- *
+ *
* See the relevent documentation for the {@link Filter} to determine
* the effect.
- *
+ *
* @param dryFilter The filter to set, or null to disable dry filter.
*/
public void setDryFilter(Filter dryFilter) {
@@ -289,7 +290,7 @@ public class AudioNode extends Node implements AudioSource {
* Set the audio data to use for the audio. Note that this method
* can only be called once, if for example the audio node was initialized
* without an {@link AudioData}.
- *
+ *
* @param audioData The audio data contains the audio track to play.
* @param audioKey The audio key that was used to load the AudioData
*/
@@ -303,7 +304,7 @@ public class AudioNode extends Node implements AudioSource {
}
/**
- * @return The {@link AudioData} set previously with
+ * @return The {@link AudioData} set previously with
* {@link AudioNode#setAudioData(com.jme3.audio.AudioData, com.jme3.audio.AudioKey) }
* or any of the constructors that initialize the audio data.
*/
@@ -312,7 +313,7 @@ public class AudioNode extends Node implements AudioSource {
}
/**
- * @return The {@link Status} of the audio node.
+ * @return The {@link Status} of the audio node.
* The status will be changed when either the {@link AudioNode#play() }
* or {@link AudioNode#stop() } methods are called.
*/
@@ -339,7 +340,7 @@ public class AudioNode extends Node implements AudioSource {
else
return data.getDataType();
}
-
+
/**
* @return True if the audio will keep looping after it is done playing,
* otherwise, false.
@@ -351,7 +352,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the looping mode for the audio node. The default is false.
- *
+ *
* @param loop True if the audio should keep looping after it is done playing.
*/
public void setLooping(boolean loop) {
@@ -362,8 +363,8 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return The pitch of the audio, also the speed of playback.
- *
- * @see AudioNode#setPitch(float)
+ *
+ * @see AudioNode#setPitch(float)
*/
public float getPitch() {
return pitch;
@@ -372,7 +373,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the pitch of the audio, also the speed of playback.
* The value must be between 0.5 and 2.0.
- *
+ *
* @param pitch The pitch to set.
* @throws IllegalArgumentException If pitch is not between 0.5 and 2.0.
*/
@@ -388,7 +389,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return The volume of this audio node.
- *
+ *
* @see AudioNode#setVolume(float)
*/
public float getVolume() {
@@ -397,9 +398,9 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the volume of this audio node.
- *
+ *
* The volume is specified as gain. 1.0 is the default.
- *
+ *
* @param volume The volume to set.
* @throws IllegalArgumentException If volume is negative
*/
@@ -422,7 +423,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the time offset in the sound sample when to start playing.
- *
+ *
* @param timeOffset The time offset
* @throws IllegalArgumentException If timeOffset is negative
*/
@@ -439,7 +440,7 @@ public class AudioNode extends Node implements AudioSource {
play();
}
}
-
+
@Override
public float getPlaybackTime() {
if (channel >= 0)
@@ -451,10 +452,10 @@ public class AudioNode extends Node implements AudioSource {
public Vector3f getPosition() {
return getWorldTranslation();
}
-
+
/**
* @return The velocity of the audio node.
- *
+ *
* @see AudioNode#setVelocity(com.jme3.math.Vector3f)
*/
public Vector3f getVelocity() {
@@ -464,7 +465,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the velocity of the audio node. The velocity is expected
* to be in meters. Does nothing if the audio node is not positional.
- *
+ *
* @param velocity The velocity to set.
* @see AudioNode#setPositional(boolean)
*/
@@ -476,7 +477,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return True if reverb is enabled, otherwise false.
- *
+ *
* @see AudioNode#setReverbEnabled(boolean)
*/
public boolean isReverbEnabled() {
@@ -487,10 +488,10 @@ public class AudioNode extends Node implements AudioSource {
* Set to true to enable reverberation effects for this audio node.
* Does nothing if the audio node is not positional.
*
- * When enabled, the audio environment set with
+ * When enabled, the audio environment set with
* {@link AudioRenderer#setEnvironment(com.jme3.audio.Environment) }
* will apply a reverb effect to the audio playing from this audio node.
- *
+ *
* @param reverbEnabled True to enable reverb.
*/
public void setReverbEnabled(boolean reverbEnabled) {
@@ -502,8 +503,8 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return Filter for the reverberations of this audio node.
- *
- * @see AudioNode#setReverbFilter(com.jme3.audio.Filter)
+ *
+ * @see AudioNode#setReverbFilter(com.jme3.audio.Filter)
*/
public Filter getReverbFilter() {
return reverbFilter;
@@ -515,7 +516,7 @@ public class AudioNode extends Node implements AudioSource {
* The reverb filter will influence the reverberations
* of the audio node playing. This only has an effect if
* reverb is enabled.
- *
+ *
* @param reverbFilter The reverb filter to set.
* @see AudioNode#setDryFilter(com.jme3.audio.Filter)
*/
@@ -527,7 +528,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return Max distance for this audio node.
- *
+ *
* @see AudioNode#setMaxDistance(float)
*/
public float getMaxDistance() {
@@ -545,7 +546,7 @@ public class AudioNode extends Node implements AudioSource {
* get any quieter than at that distance. If you want a sound to fall-off
* very quickly then set ref distance very short and leave this distance
* very long.
- *
+ *
* @param maxDistance The maximum playing distance.
* @throws IllegalArgumentException If maxDistance is negative
*/
@@ -561,8 +562,8 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return The reference playing distance for the audio node.
- *
- * @see AudioNode#setRefDistance(float)
+ *
+ * @see AudioNode#setRefDistance(float)
*/
public float getRefDistance() {
return refDistance;
@@ -574,7 +575,7 @@ public class AudioNode extends Node implements AudioSource {
*
* The reference playing distance is the distance at which the
* audio node will be exactly half of its volume.
- *
+ *
* @param refDistance The reference playing distance.
* @throws IllegalArgumentException If refDistance is negative
*/
@@ -590,8 +591,8 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return True if the audio node is directional
- *
- * @see AudioNode#setDirectional(boolean)
+ *
+ * @see AudioNode#setDirectional(boolean)
*/
public boolean isDirectional() {
return directional;
@@ -601,10 +602,10 @@ public class AudioNode extends Node implements AudioSource {
* Set the audio node to be directional.
* Does nothing if the audio node is not positional.
*
- * After setting directional, you should call
+ * After setting directional, you should call
* {@link AudioNode#setDirection(com.jme3.math.Vector3f) }
* to set the audio node's direction.
- *
+ *
* @param directional If the audio node is directional
*/
public void setDirectional(boolean directional) {
@@ -615,7 +616,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return The direction of this audio node.
- *
+ *
* @see AudioNode#setDirection(com.jme3.math.Vector3f)
*/
public Vector3f getDirection() {
@@ -625,9 +626,9 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the direction of this audio node.
* Does nothing if the audio node is not directional.
- *
- * @param direction
- * @see AudioNode#setDirectional(boolean)
+ *
+ * @param direction
+ * @see AudioNode#setDirectional(boolean)
*/
public void setDirection(Vector3f direction) {
this.direction = direction;
@@ -637,8 +638,8 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return The directional audio node, cone inner angle.
- *
- * @see AudioNode#setInnerAngle(float)
+ *
+ * @see AudioNode#setInnerAngle(float)
*/
public float getInnerAngle() {
return innerAngle;
@@ -647,7 +648,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the directional audio node cone inner angle.
* Does nothing if the audio node is not directional.
- *
+ *
* @param innerAngle The cone inner angle.
*/
public void setInnerAngle(float innerAngle) {
@@ -658,8 +659,8 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return The directional audio node, cone outer angle.
- *
- * @see AudioNode#setOuterAngle(float)
+ *
+ * @see AudioNode#setOuterAngle(float)
*/
public float getOuterAngle() {
return outerAngle;
@@ -668,7 +669,7 @@ public class AudioNode extends Node implements AudioSource {
/**
* Set the directional audio node cone outer angle.
* Does nothing if the audio node is not directional.
- *
+ *
* @param outerAngle The cone outer angle.
*/
public void setOuterAngle(float outerAngle) {
@@ -679,8 +680,8 @@ public class AudioNode extends Node implements AudioSource {
/**
* @return True if the audio node is positional.
- *
- * @see AudioNode#setPositional(boolean)
+ *
+ * @see AudioNode#setPositional(boolean)
*/
public boolean isPositional() {
return positional;
@@ -690,7 +691,7 @@ public class AudioNode extends Node implements AudioSource {
* Set the audio node as positional.
* The position, velocity, and distance parameters effect positional
* audio nodes. Set to false if the audio node should play in "headspace".
- *
+ *
* @param positional True if the audio node should be positional, otherwise
* false if it should be headspace.
*/
@@ -707,7 +708,7 @@ public class AudioNode extends Node implements AudioSource {
if ((refreshFlags & RF_TRANSFORM) != 0){
updatePos = true;
}
-
+
super.updateGeometricState();
if (updatePos && channel >= 0)
@@ -717,13 +718,37 @@ public class AudioNode extends Node implements AudioSource {
@Override
public AudioNode clone(){
AudioNode clone = (AudioNode) super.clone();
-
+
clone.direction = direction.clone();
clone.velocity = velocity.clone();
-
+
return clone;
}
-
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ super.cloneFields(cloner, original);
+
+ this.direction = cloner.clone(direction);
+ this.velocity = cloner.clone(velocity);
+
+ // Change in behavior: the filters were not cloned before meaning
+ // that two cloned audio nodes would share the same filter instance.
+ // While settings will only be applied when the filter is actually
+ // set, I think it's probably surprising to callers if the values of
+ // a filter change from one AudioNode when a different AudioNode's
+ // filter attributes are updated.
+ // Plus if they disable and re-enable the thing using the filter then
+ // the settings get reapplied and it might be surprising to have them
+ // suddenly be strange.
+ // ...so I'll clone them. -pspeed
+ this.dryFilter = cloner.clone(dryFilter);
+ this.reverbFilter = cloner.clone(reverbFilter);
+ }
+
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
@@ -745,7 +770,7 @@ public class AudioNode extends Node implements AudioSource {
oc.write(direction, "direction", null);
oc.write(innerAngle, "inner_angle", 360);
oc.write(outerAngle, "outer_angle", 360);
-
+
oc.write(positional, "positional", false);
}
@@ -753,7 +778,7 @@ public class AudioNode extends Node implements AudioSource {
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
-
+
// NOTE: In previous versions of jME3, audioKey was actually
// written with the name "key". This has been changed
// to "audio_key" in case Spatial's key will be written as "key".
@@ -762,7 +787,7 @@ public class AudioNode extends Node implements AudioSource {
}else{
audioKey = (AudioKey) ic.readSavable("audio_key", null);
}
-
+
loop = ic.readBoolean("looping", false);
volume = ic.readFloat("volume", 1);
pitch = ic.readFloat("pitch", 1);
@@ -779,9 +804,9 @@ public class AudioNode extends Node implements AudioSource {
direction = (Vector3f) ic.readSavable("direction", null);
innerAngle = ic.readFloat("inner_angle", 360);
outerAngle = ic.readFloat("outer_angle", 360);
-
+
positional = ic.readBoolean("positional", false);
-
+
if (audioKey != null) {
try {
data = im.getAssetManager().loadAsset(audioKey);
diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java
index 2349461b5..60acb4c56 100644
--- a/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java
+++ b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2016 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -47,6 +47,8 @@ import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@@ -56,15 +58,15 @@ import java.io.IOException;
*
* @author Nehon
*/
-public class MotionEvent extends AbstractCinematicEvent implements Control {
+public class MotionEvent extends AbstractCinematicEvent implements Control, JmeCloneable {
protected Spatial spatial;
protected int currentWayPoint;
protected float currentValue;
protected Vector3f direction = new Vector3f();
- protected Vector3f lookAt;
+ protected Vector3f lookAt = null;
protected Vector3f upVector = Vector3f.UNIT_Y;
- protected Quaternion rotation;
+ protected Quaternion rotation = null;
protected Direction directionType = Direction.None;
protected MotionPath path;
private boolean isControl = true;
@@ -118,7 +120,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
*/
public MotionEvent(Spatial spatial, MotionPath path) {
super();
- this.spatial = spatial;
spatial.addControl(this);
this.path = path;
}
@@ -130,7 +131,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
*/
public MotionEvent(Spatial spatial, MotionPath path, float initialDuration) {
super(initialDuration);
- this.spatial = spatial;
spatial.addControl(this);
this.path = path;
}
@@ -142,7 +142,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
*/
public MotionEvent(Spatial spatial, MotionPath path, LoopMode loopMode) {
super();
- this.spatial = spatial;
spatial.addControl(this);
this.path = path;
this.loopMode = loopMode;
@@ -155,7 +154,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
*/
public MotionEvent(Spatial spatial, MotionPath path, float initialDuration, LoopMode loopMode) {
super(initialDuration);
- this.spatial = spatial;
spatial.addControl(this);
this.path = path;
this.loopMode = loopMode;
@@ -211,9 +209,9 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
- oc.write(lookAt, "lookAt", Vector3f.ZERO);
+ oc.write(lookAt, "lookAt", null);
oc.write(upVector, "upVector", Vector3f.UNIT_Y);
- oc.write(rotation, "rotation", Quaternion.IDENTITY);
+ oc.write(rotation, "rotation", null);
oc.write(directionType, "directionType", Direction.None);
oc.write(path, "path", null);
}
@@ -222,9 +220,9 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule in = im.getCapsule(this);
- lookAt = (Vector3f) in.readSavable("lookAt", Vector3f.ZERO);
+ lookAt = (Vector3f) in.readSavable("lookAt", null);
upVector = (Vector3f) in.readSavable("upVector", Vector3f.UNIT_Y);
- rotation = (Quaternion) in.readSavable("rotation", Quaternion.IDENTITY);
+ rotation = (Quaternion) in.readSavable("rotation", null);
directionType = in.readEnum("directionType", Direction.class, Direction.None);
path = (MotionPath) in.readSavable("path", null);
}
@@ -274,15 +272,17 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
* @param spatial
* @return
*/
+ @Override
public Control cloneForSpatial(Spatial spatial) {
- MotionEvent control = new MotionEvent(spatial, path);
+ MotionEvent control = new MotionEvent();
+ control.setPath(path);
control.playState = playState;
control.currentWayPoint = currentWayPoint;
control.currentValue = currentValue;
control.direction = direction.clone();
- control.lookAt = lookAt.clone();
+ control.lookAt = lookAt;
control.upVector = upVector.clone();
- control.rotation = rotation.clone();
+ control.rotation = rotation;
control.initialDuration = initialDuration;
control.speed = speed;
control.loopMode = loopMode;
@@ -291,6 +291,31 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
return control;
}
+ @Override
+ public Object jmeClone() {
+ MotionEvent control = new MotionEvent();
+ control.path = path;
+ control.playState = playState;
+ control.currentWayPoint = currentWayPoint;
+ control.currentValue = currentValue;
+ control.direction = direction.clone();
+ control.lookAt = lookAt;
+ control.upVector = upVector.clone();
+ control.rotation = rotation;
+ control.initialDuration = initialDuration;
+ control.speed = speed;
+ control.loopMode = loopMode;
+ control.directionType = directionType;
+ control.spatial = spatial;
+
+ return control;
+ }
+
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.spatial = cloner.clone(spatial);
+ }
+
@Override
public void onPlay() {
traveledDistance = 0;
diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
index c4eff45dc..477a2e99a 100644
--- a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
+++ b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
@@ -54,6 +54,8 @@ import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@@ -63,12 +65,12 @@ import java.io.IOException;
* Particle emitters can be used to simulate various kinds of phenomena,
* such as fire, smoke, explosions and much more.
*
- * Particle emitters have many properties which are used to control the
- * simulation. The interpretation of these properties depends on the
+ * Particle emitters have many properties which are used to control the
+ * simulation. The interpretation of these properties depends on the
* {@link ParticleInfluencer} that has been assigned to the emitter via
* {@link ParticleEmitter#setParticleInfluencer(com.jme3.effect.influencers.ParticleInfluencer) }.
* By default the implementation {@link DefaultParticleInfluencer} is used.
- *
+ *
* @author Kirill Vainer
*/
public class ParticleEmitter extends Geometry {
@@ -98,7 +100,7 @@ public class ParticleEmitter extends Geometry {
private Vector3f faceNormal = new Vector3f(Vector3f.NAN);
private int imagesX = 1;
private int imagesY = 1;
-
+
private ColorRGBA startColor = new ColorRGBA(0.4f, 0.4f, 0.4f, 0.5f);
private ColorRGBA endColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 0.0f);
private float startSize = 0.2f;
@@ -108,7 +110,7 @@ public class ParticleEmitter extends Geometry {
private transient Vector3f temp = new Vector3f();
private transient Vector3f lastPos;
- public static class ParticleEmitterControl implements Control {
+ public static class ParticleEmitterControl implements Control, JmeCloneable {
ParticleEmitter parentEmitter;
@@ -119,11 +121,26 @@ public class ParticleEmitter extends Geometry {
this.parentEmitter = parentEmitter;
}
+ @Override
public Control cloneForSpatial(Spatial spatial) {
return this; // WARNING: Sets wrong control on spatial. Will be
// fixed automatically by ParticleEmitter.clone() method.
}
+ @Override
+ public Object jmeClone() {
+ try {
+ return super.clone();
+ } catch( CloneNotSupportedException e ) {
+ throw new RuntimeException("Error cloning", e);
+ }
+ }
+
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.parentEmitter = cloner.clone(parentEmitter);
+ }
+
public void setSpatial(Spatial spatial) {
}
@@ -157,6 +174,13 @@ public class ParticleEmitter extends Geometry {
@Override
public ParticleEmitter clone(boolean cloneMaterial) {
+ return (ParticleEmitter)super.clone(cloneMaterial);
+ }
+
+ /**
+ * The old clone() method that did not use the new Cloner utility.
+ */
+ public ParticleEmitter oldClone(boolean cloneMaterial) {
ParticleEmitter clone = (ParticleEmitter) super.clone(cloneMaterial);
clone.shape = shape.deepClone();
@@ -194,6 +218,44 @@ public class ParticleEmitter extends Geometry {
return clone;
}
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ super.cloneFields(cloner, original);
+
+ this.shape = cloner.clone(shape);
+ this.control = cloner.clone(control);
+ this.faceNormal = cloner.clone(faceNormal);
+ this.startColor = cloner.clone(startColor);
+ this.endColor = cloner.clone(endColor);
+ this.particleInfluencer = cloner.clone(particleInfluencer);
+
+ // change in behavior: gravity was not cloned before -pspeed
+ this.gravity = cloner.clone(gravity);
+
+ // So, simply setting the mesh type will cause all kinds of things
+ // to happen:
+ // 1) the new mesh gets created.
+ // 2) it is set to the Geometry
+ // 3) the particles array is recreated because setNumParticles()
+ //
+ // ...so this should be equivalent but simpler than half of the old clone()
+ // method. Note: we do not ever want to share particleMesh so we do not
+ // clone it at all.
+ setMeshType(meshType);
+
+ // change in behavior: temp and lastPos were not cloned before...
+ // perhaps because it was believed that 'transient' fields were exluded
+ // from cloning? (they aren't)
+ // If it was ok for these to be shared because of how they are used
+ // then they could just as well be made static... else I think it's clearer
+ // to clone them.
+ this.temp = cloner.clone(temp);
+ this.lastPos = cloner.clone(lastPos);
+ }
+
public ParticleEmitter(String name, Type type, int numParticles) {
super(name);
setBatchHint(BatchHint.Never);
@@ -208,7 +270,7 @@ public class ParticleEmitter extends Geometry {
meshType = type;
- // Must create clone of shape/influencer so that a reference to a static is
+ // Must create clone of shape/influencer so that a reference to a static is
// not maintained
shape = shape.deepClone();
particleInfluencer = particleInfluencer.clone();
@@ -250,10 +312,10 @@ public class ParticleEmitter extends Geometry {
/**
* Set the {@link ParticleInfluencer} to influence this particle emitter.
- *
- * @param particleInfluencer the {@link ParticleInfluencer} to influence
+ *
+ * @param particleInfluencer the {@link ParticleInfluencer} to influence
* this particle emitter.
- *
+ *
* @see ParticleInfluencer
*/
public void setParticleInfluencer(ParticleInfluencer particleInfluencer) {
@@ -261,12 +323,12 @@ public class ParticleEmitter extends Geometry {
}
/**
- * Returns the {@link ParticleInfluencer} that influences this
+ * Returns the {@link ParticleInfluencer} that influences this
* particle emitter.
- *
- * @return the {@link ParticleInfluencer} that influences this
+ *
+ * @return the {@link ParticleInfluencer} that influences this
* particle emitter.
- *
+ *
* @see ParticleInfluencer
*/
public ParticleInfluencer getParticleInfluencer() {
@@ -275,12 +337,12 @@ public class ParticleEmitter extends Geometry {
/**
* Returns the mesh type used by the particle emitter.
- *
- *
+ *
+ *
* @return the mesh type used by the particle emitter.
- *
+ *
* @see #setMeshType(com.jme3.effect.ParticleMesh.Type)
- * @see ParticleEmitter#ParticleEmitter(java.lang.String, com.jme3.effect.ParticleMesh.Type, int)
+ * @see ParticleEmitter#ParticleEmitter(java.lang.String, com.jme3.effect.ParticleMesh.Type, int)
*/
public ParticleMesh.Type getMeshType() {
return meshType;
@@ -308,26 +370,26 @@ public class ParticleEmitter extends Geometry {
}
/**
- * Returns true if particles should spawn in world space.
- *
- * @return true if particles should spawn in world space.
- *
- * @see ParticleEmitter#setInWorldSpace(boolean)
+ * Returns true if particles should spawn in world space.
+ *
+ * @return true if particles should spawn in world space.
+ *
+ * @see ParticleEmitter#setInWorldSpace(boolean)
*/
public boolean isInWorldSpace() {
return worldSpace;
}
/**
- * Set to true if particles should spawn in world space.
- *
+ * Set to true if particles should spawn in world space.
+ *
*
If set to true and the particle emitter is moved in the scene,
* then particles that have already spawned won't be effected by this
* motion. If set to false, the particles will emit in local space
* and when the emitter is moved, so are all the particles that
* were emitted previously.
- *
- * @param worldSpace true if particles should spawn in world space.
+ *
+ * @param worldSpace true if particles should spawn in world space.
*/
public void setInWorldSpace(boolean worldSpace) {
this.setIgnoreTransform(worldSpace);
@@ -336,7 +398,7 @@ public class ParticleEmitter extends Geometry {
/**
* Returns the number of visible particles (spawned but not dead).
- *
+ *
* @return the number of visible particles
*/
public int getNumVisibleParticles() {
@@ -348,7 +410,7 @@ public class ParticleEmitter extends Geometry {
* Set the maximum amount of particles that
* can exist at the same time with this emitter.
* Calling this method many times is not recommended.
- *
+ *
* @param numParticles the maximum amount of particles that
* can exist at the same time with this emitter.
*/
@@ -370,13 +432,13 @@ public class ParticleEmitter extends Geometry {
/**
* Returns a list of all particles (shouldn't be used in most cases).
- *
+ *
*
* This includes both existing and non-existing particles.
* The size of the array is set to the numParticles
value
* specified in the constructor or {@link ParticleEmitter#setNumParticles(int) }
- * method.
- *
+ * method.
+ *
* @return a list of all particles.
*/
public Particle[] getParticles() {
@@ -384,11 +446,11 @@ public class ParticleEmitter extends Geometry {
}
/**
- * Get the normal which particles are facing.
- *
- * @return the normal which particles are facing.
- *
- * @see ParticleEmitter#setFaceNormal(com.jme3.math.Vector3f)
+ * Get the normal which particles are facing.
+ *
+ * @return the normal which particles are facing.
+ *
+ * @see ParticleEmitter#setFaceNormal(com.jme3.math.Vector3f)
*/
public Vector3f getFaceNormal() {
if (Vector3f.isValidVector(faceNormal)) {
@@ -399,8 +461,8 @@ public class ParticleEmitter extends Geometry {
}
/**
- * Sets the normal which particles are facing.
- *
+ * Sets the normal which particles are facing.
+ *
*
By default, particles
* will face the camera, but for some effects (e.g shockwave) it may
* be necessary to face a specific direction instead. To restore
@@ -420,10 +482,10 @@ public class ParticleEmitter extends Geometry {
/**
* Returns the rotation speed in radians/sec for particles.
- *
+ *
* @return the rotation speed in radians/sec for particles.
- *
- * @see ParticleEmitter#setRotateSpeed(float)
+ *
+ * @see ParticleEmitter#setRotateSpeed(float)
*/
public float getRotateSpeed() {
return rotateSpeed;
@@ -432,7 +494,7 @@ public class ParticleEmitter extends Geometry {
/**
* Set the rotation speed in radians/sec for particles
* spawned after the invocation of this method.
- *
+ *
* @param rotateSpeed the rotation speed in radians/sec for particles
* spawned after the invocation of this method.
*/
@@ -442,12 +504,12 @@ public class ParticleEmitter extends Geometry {
/**
* Returns true if every particle spawned
- * should have a random facing angle.
- *
+ * should have a random facing angle.
+ *
* @return true if every particle spawned
- * should have a random facing angle.
- *
- * @see ParticleEmitter#setRandomAngle(boolean)
+ * should have a random facing angle.
+ *
+ * @see ParticleEmitter#setRandomAngle(boolean)
*/
public boolean isRandomAngle() {
return randomAngle;
@@ -455,8 +517,8 @@ public class ParticleEmitter extends Geometry {
/**
* Set to true if every particle spawned
- * should have a random facing angle.
- *
+ * should have a random facing angle.
+ *
* @param randomAngle if every particle spawned
* should have a random facing angle.
*/
@@ -467,11 +529,11 @@ public class ParticleEmitter extends Geometry {
/**
* Returns true if every particle spawned should get a random
* image.
- *
+ *
* @return True if every particle spawned should get a random
* image.
- *
- * @see ParticleEmitter#setSelectRandomImage(boolean)
+ *
+ * @see ParticleEmitter#setSelectRandomImage(boolean)
*/
public boolean isSelectRandomImage() {
return selectRandomImage;
@@ -481,7 +543,7 @@ public class ParticleEmitter extends Geometry {
* Set to true if every particle spawned
* should get a random image from a pool of images constructed from
* the texture, with X by Y possible images.
- *
+ *
*
By default, X and Y are equal
* to 1, thus allowing only 1 possible image to be selected, but if the
* particle is configured with multiple images by using {@link ParticleEmitter#setImagesX(int) }
@@ -489,7 +551,7 @@ public class ParticleEmitter extends Geometry {
* can be selected. Setting to false will cause each particle to have an animation
* of images displayed, starting at image 1, and going until image X*Y when
* the particle reaches its end of life.
- *
+ *
* @param selectRandomImage True if every particle spawned should get a random
* image.
*/
@@ -499,10 +561,10 @@ public class ParticleEmitter extends Geometry {
/**
* Check if particles spawned should face their velocity.
- *
+ *
* @return True if particles spawned should face their velocity.
- *
- * @see ParticleEmitter#setFacingVelocity(boolean)
+ *
+ * @see ParticleEmitter#setFacingVelocity(boolean)
*/
public boolean isFacingVelocity() {
return facingVelocity;
@@ -511,11 +573,11 @@ public class ParticleEmitter extends Geometry {
/**
* Set to true if particles spawned should face
* their velocity (or direction to which they are moving towards).
- *
+ *
*
This is typically used for e.g spark effects.
- *
+ *
* @param followVelocity True if particles spawned should face their velocity.
- *
+ *
*/
public void setFacingVelocity(boolean followVelocity) {
this.facingVelocity = followVelocity;
@@ -523,10 +585,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the end color of the particles spawned.
- *
+ *
* @return the end color of the particles spawned.
- *
- * @see ParticleEmitter#setEndColor(com.jme3.math.ColorRGBA)
+ *
+ * @see ParticleEmitter#setEndColor(com.jme3.math.ColorRGBA)
*/
public ColorRGBA getEndColor() {
return endColor;
@@ -534,12 +596,12 @@ public class ParticleEmitter extends Geometry {
/**
* Set the end color of the particles spawned.
- *
+ *
*
The
* particle color at any time is determined by blending the start color
* and end color based on the particle's current time of life relative
* to its end of life.
- *
+ *
* @param endColor the end color of the particles spawned.
*/
public void setEndColor(ColorRGBA endColor) {
@@ -548,10 +610,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the end size of the particles spawned.
- *
+ *
* @return the end size of the particles spawned.
- *
- * @see ParticleEmitter#setEndSize(float)
+ *
+ * @see ParticleEmitter#setEndSize(float)
*/
public float getEndSize() {
return endSize;
@@ -559,12 +621,12 @@ public class ParticleEmitter extends Geometry {
/**
* Set the end size of the particles spawned.
- *
+ *
*
The
* particle size at any time is determined by blending the start size
* and end size based on the particle's current time of life relative
* to its end of life.
- *
+ *
* @param endSize the end size of the particles spawned.
*/
public void setEndSize(float endSize) {
@@ -573,10 +635,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the gravity vector.
- *
+ *
* @return the gravity vector.
- *
- * @see ParticleEmitter#setGravity(com.jme3.math.Vector3f)
+ *
+ * @see ParticleEmitter#setGravity(com.jme3.math.Vector3f)
*/
public Vector3f getGravity() {
return gravity;
@@ -584,7 +646,7 @@ public class ParticleEmitter extends Geometry {
/**
* This method sets the gravity vector.
- *
+ *
* @param gravity the gravity vector
*/
public void setGravity(Vector3f gravity) {
@@ -593,7 +655,7 @@ public class ParticleEmitter extends Geometry {
/**
* Sets the gravity vector.
- *
+ *
* @param x the x component of the gravity vector
* @param y the y component of the gravity vector
* @param z the z component of the gravity vector
@@ -606,10 +668,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the high value of life.
- *
+ *
* @return the high value of life.
- *
- * @see ParticleEmitter#setHighLife(float)
+ *
+ * @see ParticleEmitter#setHighLife(float)
*/
public float getHighLife() {
return highLife;
@@ -617,10 +679,10 @@ public class ParticleEmitter extends Geometry {
/**
* Set the high value of life.
- *
+ *
*
The particle's lifetime/expiration
* is determined by randomly selecting a time between low life and high life.
- *
+ *
* @param highLife the high value of life.
*/
public void setHighLife(float highLife) {
@@ -629,10 +691,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the number of images along the X axis (width).
- *
+ *
* @return the number of images along the X axis (width).
- *
- * @see ParticleEmitter#setImagesX(int)
+ *
+ * @see ParticleEmitter#setImagesX(int)
*/
public int getImagesX() {
return imagesX;
@@ -640,11 +702,11 @@ public class ParticleEmitter extends Geometry {
/**
* Set the number of images along the X axis (width).
- *
+ *
*
To determine
* how multiple particle images are selected and used, see the
* {@link ParticleEmitter#setSelectRandomImage(boolean) } method.
- *
+ *
* @param imagesX the number of images along the X axis (width).
*/
public void setImagesX(int imagesX) {
@@ -654,10 +716,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the number of images along the Y axis (height).
- *
+ *
* @return the number of images along the Y axis (height).
- *
- * @see ParticleEmitter#setImagesY(int)
+ *
+ * @see ParticleEmitter#setImagesY(int)
*/
public int getImagesY() {
return imagesY;
@@ -665,10 +727,10 @@ public class ParticleEmitter extends Geometry {
/**
* Set the number of images along the Y axis (height).
- *
+ *
*
To determine how multiple particle images are selected and used, see the
* {@link ParticleEmitter#setSelectRandomImage(boolean) } method.
- *
+ *
* @param imagesY the number of images along the Y axis (height).
*/
public void setImagesY(int imagesY) {
@@ -678,10 +740,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the low value of life.
- *
+ *
* @return the low value of life.
- *
- * @see ParticleEmitter#setLowLife(float)
+ *
+ * @see ParticleEmitter#setLowLife(float)
*/
public float getLowLife() {
return lowLife;
@@ -689,10 +751,10 @@ public class ParticleEmitter extends Geometry {
/**
* Set the low value of life.
- *
+ *
*
The particle's lifetime/expiration
* is determined by randomly selecting a time between low life and high life.
- *
+ *
* @param lowLife the low value of life.
*/
public void setLowLife(float lowLife) {
@@ -702,11 +764,11 @@ public class ParticleEmitter extends Geometry {
/**
* Get the number of particles to spawn per
* second.
- *
+ *
* @return the number of particles to spawn per
* second.
- *
- * @see ParticleEmitter#setParticlesPerSec(float)
+ *
+ * @see ParticleEmitter#setParticlesPerSec(float)
*/
public float getParticlesPerSec() {
return particlesPerSec;
@@ -715,7 +777,7 @@ public class ParticleEmitter extends Geometry {
/**
* Set the number of particles to spawn per
* second.
- *
+ *
* @param particlesPerSec the number of particles to spawn per
* second.
*/
@@ -723,13 +785,13 @@ public class ParticleEmitter extends Geometry {
this.particlesPerSec = particlesPerSec;
timeDifference = 0;
}
-
+
/**
* Get the start color of the particles spawned.
- *
+ *
* @return the start color of the particles spawned.
- *
- * @see ParticleEmitter#setStartColor(com.jme3.math.ColorRGBA)
+ *
+ * @see ParticleEmitter#setStartColor(com.jme3.math.ColorRGBA)
*/
public ColorRGBA getStartColor() {
return startColor;
@@ -737,11 +799,11 @@ public class ParticleEmitter extends Geometry {
/**
* Set the start color of the particles spawned.
- *
+ *
*
The particle color at any time is determined by blending the start color
* and end color based on the particle's current time of life relative
* to its end of life.
- *
+ *
* @param startColor the start color of the particles spawned
*/
public void setStartColor(ColorRGBA startColor) {
@@ -750,10 +812,10 @@ public class ParticleEmitter extends Geometry {
/**
* Get the start color of the particles spawned.
- *
+ *
* @return the start color of the particles spawned.
- *
- * @see ParticleEmitter#setStartSize(float)
+ *
+ * @see ParticleEmitter#setStartSize(float)
*/
public float getStartSize() {
return startSize;
@@ -761,11 +823,11 @@ public class ParticleEmitter extends Geometry {
/**
* Set the start size of the particles spawned.
- *
+ *
*
The particle size at any time is determined by blending the start size
* and end size based on the particle's current time of life relative
* to its end of life.
- *
+ *
* @param startSize the start size of the particles spawned.
*/
public void setStartSize(float startSize) {
@@ -788,10 +850,10 @@ public class ParticleEmitter extends Geometry {
* gravity.
*
* @deprecated
- * This method is deprecated.
+ * This method is deprecated.
* Use ParticleEmitter.getParticleInfluencer().setInitialVelocity(initialVelocity); instead.
*
- * @see ParticleEmitter#setVelocityVariation(float)
+ * @see ParticleEmitter#setVelocityVariation(float)
* @see ParticleEmitter#setGravity(float)
*/
@Deprecated
@@ -801,7 +863,7 @@ public class ParticleEmitter extends Geometry {
/**
* @deprecated
- * This method is deprecated.
+ * This method is deprecated.
* Use ParticleEmitter.getParticleInfluencer().getVelocityVariation(); instead.
* @return the initial velocity variation factor
*/
@@ -816,9 +878,9 @@ public class ParticleEmitter extends Geometry {
* from 0 to 1, where 0 means particles are to spawn with exactly
* the velocity given in {@link ParticleEmitter#setStartVel(com.jme3.math.Vector3f) },
* and 1 means particles are to spawn with a completely random velocity.
- *
+ *
* @deprecated
- * This method is deprecated.
+ * This method is deprecated.
* Use ParticleEmitter.getParticleInfluencer().setVelocityVariation(variation); instead.
*/
@Deprecated
@@ -906,7 +968,7 @@ public class ParticleEmitter extends Geometry {
vars.release();
}
-
+
/**
* Instantly kills all active particles, after this method is called, all
* particles will be dead and no longer visible.
@@ -918,12 +980,12 @@ public class ParticleEmitter extends Geometry {
}
}
}
-
+
/**
* Kills the particle at the given index.
- *
+ *
* @param index The index of the particle to kill
- * @see #getParticles()
+ * @see #getParticles()
*/
public void killParticle(int index){
freeParticle(index);
@@ -978,7 +1040,7 @@ public class ParticleEmitter extends Geometry {
p.imageIndex = (int) (b * imagesX * imagesY);
}
}
-
+
private void updateParticleState(float tpf) {
// Force world transform to update
this.getWorldTransform();
@@ -1011,7 +1073,7 @@ public class ParticleEmitter extends Geometry {
firstUnUsed++;
}
}
-
+
// Spawns particles within the tpf timeslot with proper age
float interval = 1f / particlesPerSec;
float originalTpf = tpf;
@@ -1048,10 +1110,10 @@ public class ParticleEmitter extends Geometry {
/**
* Set to enable or disable the particle emitter
- *
+ *
*
When a particle is
* disabled, it will be "frozen in time" and not update.
- *
+ *
* @param enabled True to enable the particle emitter
*/
public void setEnabled(boolean enabled) {
@@ -1060,10 +1122,10 @@ public class ParticleEmitter extends Geometry {
/**
* Check if a particle emitter is enabled for update.
- *
+ *
* @return True if a particle emitter is enabled for update.
- *
- * @see ParticleEmitter#setEnabled(boolean)
+ *
+ * @see ParticleEmitter#setEnabled(boolean)
*/
public boolean isEnabled() {
return enabled;
@@ -1071,7 +1133,7 @@ public class ParticleEmitter extends Geometry {
/**
* Callback from Control.update(), do not use.
- * @param tpf
+ * @param tpf
*/
public void updateFromControl(float tpf) {
if (enabled) {
@@ -1081,9 +1143,9 @@ public class ParticleEmitter extends Geometry {
/**
* Callback from Control.render(), do not use.
- *
+ *
* @param rm
- * @param vp
+ * @param vp
*/
private void renderFromControl(RenderManager rm, ViewPort vp) {
Camera cam = vp.getCamera();
@@ -1220,7 +1282,7 @@ public class ParticleEmitter extends Geometry {
gravity.y = ic.readFloat("gravity", 0);
}
} else {
- // since the parentEmitter is not loaded, it must be
+ // since the parentEmitter is not loaded, it must be
// loaded separately
control = getControl(ParticleEmitterControl.class);
control.parentEmitter = this;
diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/DefaultParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/DefaultParticleInfluencer.java
index b52d8df80..9cd06f0e3 100644
--- a/jme3-core/src/main/java/com/jme3/effect/influencers/DefaultParticleInfluencer.java
+++ b/jme3-core/src/main/java/com/jme3/effect/influencers/DefaultParticleInfluencer.java
@@ -39,6 +39,8 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@@ -49,7 +51,7 @@ import java.io.IOException;
*/
public class DefaultParticleInfluencer implements ParticleInfluencer {
- //Version #1 : changed startVelocity to initialvelocity for consistency with accessors
+ //Version #1 : changed startVelocity to initialvelocity for consistency with accessors
//and also changed it in serialization
public static final int SAVABLE_VERSION = 1;
/** Temporary variable used to help with calculations. */
@@ -94,7 +96,7 @@ public class DefaultParticleInfluencer implements ParticleInfluencer {
initialVelocity = (Vector3f) ic.readSavable("startVelocity", Vector3f.ZERO.clone());
}else{
initialVelocity = (Vector3f) ic.readSavable("initialVelocity", Vector3f.ZERO.clone());
- }
+ }
velocityVariation = ic.readFloat("variation", 0.2f);
}
@@ -109,6 +111,35 @@ public class DefaultParticleInfluencer implements ParticleInfluencer {
}
}
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public Object jmeClone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.initialVelocity = cloner.clone(initialVelocity);
+
+ // Change in behavior: I'm cloning 'for real' the 'temp' field because
+ // otherwise it will be shared across all clones. Note: if this is
+ // ok because of how its used then it might as well be static and let
+ // everything share it.
+ // Note 2: transient fields _are_ cloned just like anything else so
+ // thinking it wouldn't get cloned is also not right.
+ // -pspeed
+ this.temp = cloner.clone(temp);
+ }
+
@Override
public void setInitialVelocity(Vector3f initialVelocity) {
this.initialVelocity.set(initialVelocity);
diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/EmptyParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/EmptyParticleInfluencer.java
index ccbc7e5e6..88e938430 100644
--- a/jme3-core/src/main/java/com/jme3/effect/influencers/EmptyParticleInfluencer.java
+++ b/jme3-core/src/main/java/com/jme3/effect/influencers/EmptyParticleInfluencer.java
@@ -36,6 +36,8 @@ import com.jme3.effect.shapes.EmitterShape;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.math.Vector3f;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@@ -83,4 +85,23 @@ public class EmptyParticleInfluencer implements ParticleInfluencer {
throw new AssertionError();
}
}
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public Object jmeClone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ }
}
diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/NewtonianParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/NewtonianParticleInfluencer.java
index b2f81f9a8..b0bc1be25 100644
--- a/jme3-core/src/main/java/com/jme3/effect/influencers/NewtonianParticleInfluencer.java
+++ b/jme3-core/src/main/java/com/jme3/effect/influencers/NewtonianParticleInfluencer.java
@@ -39,6 +39,8 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/ParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/ParticleInfluencer.java
index 5e3532bb0..4f322df74 100644
--- a/jme3-core/src/main/java/com/jme3/effect/influencers/ParticleInfluencer.java
+++ b/jme3-core/src/main/java/com/jme3/effect/influencers/ParticleInfluencer.java
@@ -36,12 +36,13 @@ import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.shapes.EmitterShape;
import com.jme3.export.Savable;
import com.jme3.math.Vector3f;
+import com.jme3.util.clone.JmeCloneable;
/**
* An interface that defines the methods to affect initial velocity of the particles.
* @author Marcin Roguski (Kaelthas)
*/
-public interface ParticleInfluencer extends Savable, Cloneable {
+public interface ParticleInfluencer extends Savable, Cloneable, JmeCloneable {
/**
* This method influences the particle.
diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/RadialParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/RadialParticleInfluencer.java
index 87c5ce507..fba223dc1 100644
--- a/jme3-core/src/main/java/com/jme3/effect/influencers/RadialParticleInfluencer.java
+++ b/jme3-core/src/main/java/com/jme3/effect/influencers/RadialParticleInfluencer.java
@@ -38,6 +38,7 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
+import com.jme3.util.clone.Cloner;
import java.io.IOException;
/**
@@ -81,7 +82,7 @@ public class RadialParticleInfluencer extends DefaultParticleInfluencer {
/**
* the origin used for computing the radial velocity direction
- * @param origin
+ * @param origin
*/
public void setOrigin(Vector3f origin) {
this.origin = origin;
@@ -97,7 +98,7 @@ public class RadialParticleInfluencer extends DefaultParticleInfluencer {
/**
* the radial velocity
- * @param radialVelocity
+ * @param radialVelocity
*/
public void setRadialVelocity(float radialVelocity) {
this.radialVelocity = radialVelocity;
@@ -105,7 +106,7 @@ public class RadialParticleInfluencer extends DefaultParticleInfluencer {
/**
* nullify y component of particle velocity to make the effect expand only on x and z axis
- * @return
+ * @return
*/
public boolean isHorizontal() {
return horizontal;
@@ -113,12 +114,24 @@ public class RadialParticleInfluencer extends DefaultParticleInfluencer {
/**
* nullify y component of particle velocity to make the effect expand only on x and z axis
- * @param horizontal
+ * @param horizontal
*/
public void setHorizontal(boolean horizontal) {
this.horizontal = horizontal;
}
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ super.cloneFields(cloner, original);
+
+ // Change in behavior: the old origin was not cloned -pspeed
+ this.origin = cloner.clone(origin);
+ }
+
+
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterBoxShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterBoxShape.java
index 63323db7b..6b29843c9 100644
--- a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterBoxShape.java
+++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterBoxShape.java
@@ -37,6 +37,8 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
public class EmitterBoxShape implements EmitterShape {
@@ -86,6 +88,27 @@ public class EmitterBoxShape implements EmitterShape {
}
}
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public Object jmeClone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.min = cloner.clone(min);
+ this.len = cloner.clone(len);
+ }
+
public Vector3f getMin() {
return min;
}
diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshVertexShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshVertexShape.java
index 0a5ba128a..b996e63cb 100644
--- a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshVertexShape.java
+++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshVertexShape.java
@@ -40,6 +40,8 @@ import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -168,6 +170,27 @@ public class EmitterMeshVertexShape implements EmitterShape {
}
}
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public Object jmeClone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.vertices = cloner.clone(vertices);
+ this.normals = cloner.clone(normals);
+ }
+
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
@@ -180,7 +203,7 @@ public class EmitterMeshVertexShape implements EmitterShape {
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
this.vertices = ic.readSavableArrayList("vertices", null);
-
+
List> tmpNormals = ic.readSavableArrayList("normals", null);
if (tmpNormals != null){
this.normals = tmpNormals;
diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterPointShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterPointShape.java
index 9f7e71107..e33691101 100644
--- a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterPointShape.java
+++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterPointShape.java
@@ -35,6 +35,8 @@ import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.Vector3f;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
public class EmitterPointShape implements EmitterShape {
@@ -59,6 +61,26 @@ public class EmitterPointShape implements EmitterShape {
}
}
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public Object jmeClone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.point = cloner.clone(point);
+ }
+
@Override
public void getRandomPoint(Vector3f store) {
store.set(point);
diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterShape.java
index bdecd5b5f..f247412d0 100644
--- a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterShape.java
+++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterShape.java
@@ -33,12 +33,13 @@ package com.jme3.effect.shapes;
import com.jme3.export.Savable;
import com.jme3.math.Vector3f;
+import com.jme3.util.clone.JmeCloneable;
/**
* This interface declares methods used by all shapes that represent particle emitters.
* @author Kirill
*/
-public interface EmitterShape extends Savable, Cloneable {
+public interface EmitterShape extends Savable, Cloneable, JmeCloneable {
/**
* This method fills in the initial position of the particle.
diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterSphereShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterSphereShape.java
index 770ba6c9d..99d76205d 100644
--- a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterSphereShape.java
+++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterSphereShape.java
@@ -37,6 +37,8 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
public class EmitterSphereShape implements EmitterShape {
@@ -71,6 +73,26 @@ public class EmitterSphereShape implements EmitterShape {
}
}
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public Object jmeClone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.center = cloner.clone(center);
+ }
+
@Override
public void getRandomPoint(Vector3f store) {
do {
diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapText.java b/jme3-core/src/main/java/com/jme3/font/BitmapText.java
index 913bfe13a..4dfd87aaa 100644
--- a/jme3-core/src/main/java/com/jme3/font/BitmapText.java
+++ b/jme3-core/src/main/java/com/jme3/font/BitmapText.java
@@ -38,6 +38,7 @@ import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Node;
+import com.jme3.util.clone.Cloner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -84,6 +85,27 @@ public class BitmapText extends Node {
return clone;
}
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ super.cloneFields(cloner, original);
+
+ for( int i = 0; i < textPages.length; i++ ) {
+ textPages[i] = cloner.clone(textPages[i]);
+ }
+ this.block = cloner.clone(block);
+
+ // Change in behavior: The 'letters' field was not cloned or recreated
+ // before. I'm not sure how this worked and suspect BitmapText was just
+ // not cloneable if you planned to change the text later. -pspeed
+ this.letters = new Letters(font, block, letters.getQuad().isRightToLeft());
+
+ // Just noticed BitmapText is not even writable/readable really...
+ // so I guess cloning doesn't come up that often.
+ }
+
public BitmapFont getFont() {
return font;
}
@@ -115,10 +137,10 @@ public class BitmapText extends Node {
*
* @param text String to change text to
*/
- public void setText(String text) {
+ public void setText(String text) {
text = text == null ? "" : text;
- if (text == block.getText() || block.getText().equals(text)) {
+ if (text == block.getText() || block.getText().equals(text)) {
return;
}
@@ -126,24 +148,24 @@ public class BitmapText extends Node {
The problem with the below block is that StringBlock carries
pretty much all of the text-related state of the BitmapText such
as size, text box, alignment, etc.
-
+
I'm not sure why this change was needed and the commit message was
- not entirely helpful because it purports to fix a problem that I've
+ not entirely helpful because it purports to fix a problem that I've
never encountered.
-
+
If block.setText("") doesn't do the right thing then that's where
the fix should go because StringBlock carries too much information to
be blown away every time. -pspeed
-
+
Change was made:
http://code.google.com/p/jmonkeyengine/source/detail?spec=svn9389&r=9389
Diff:
http://code.google.com/p/jmonkeyengine/source/diff?path=/trunk/engine/src/core/com/jme3/font/BitmapText.java&format=side&r=9389&old_path=/trunk/engine/src/core/com/jme3/font/BitmapText.java&old=8843
-
+
// If the text is empty, reset
if (text.isEmpty()) {
detachAllChildren();
-
+
for (int page = 0; page < textPages.length; page++) {
textPages[page] = new BitmapTextPage(font, true, page);
attachChild(textPages[page]);
@@ -153,7 +175,7 @@ public class BitmapText extends Node {
letters = new Letters(font, block, letters.getQuad().isRightToLeft());
}
*/
-
+
// Update the text content
block.setText(text);
letters.setText(text);
@@ -185,7 +207,7 @@ public class BitmapText extends Node {
letters.invalidate(); // TODO: Don't have to align.
needRefresh = true;
}
-
+
/**
* Sets an overall alpha that will be applied to all
* letters. If the alpha passed is -1 then alpha reverts
@@ -196,7 +218,7 @@ public class BitmapText extends Node {
public void setAlpha(float alpha) {
letters.setBaseAlpha(alpha);
needRefresh = true;
- }
+ }
public float getAlpha() {
return letters.getBaseAlpha();
@@ -414,17 +436,17 @@ public class BitmapText extends Node {
if( mp == null ) {
return null;
}
- return (ColorRGBA)mp.getValue();
+ return (ColorRGBA)mp.getValue();
}
public void render(RenderManager rm, ColorRGBA color) {
for (BitmapTextPage page : textPages) {
Material mat = page.getMaterial();
mat.setTexture("ColorMap", page.getTexture());
- //ColorRGBA original = getColor(mat, "Color");
+ //ColorRGBA original = getColor(mat, "Color");
//mat.setColor("Color", color);
mat.render(page, rm);
-
+
//if( original == null ) {
// mat.clearParam("Color");
//} else {
diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java b/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java
index 7036418e8..1bfa59698 100644
--- a/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java
+++ b/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java
@@ -123,6 +123,13 @@ class BitmapTextPage extends Geometry {
return clone;
}
+ // Here is where one might add JmeCloneable related stuff except
+ // the old clone() method doesn't actually bother to clone anything.
+ // The arrays and the pageQuads are shared across all BitmapTextPage
+ // clones and it doesn't seem to bother anything. That means the
+ // fields could probably just as well be static... but this code is
+ // all very fragile. I'm not tipping that particular boat today. -pspeed
+
void assemble(Letters quads) {
pageQuads.clear();
quads.rewind();
diff --git a/jme3-core/src/main/java/com/jme3/font/Letters.java b/jme3-core/src/main/java/com/jme3/font/Letters.java
index e8b8c8270..604f68785 100644
--- a/jme3-core/src/main/java/com/jme3/font/Letters.java
+++ b/jme3-core/src/main/java/com/jme3/font/Letters.java
@@ -53,7 +53,7 @@ class Letters {
private ColorRGBA baseColor = null;
private float baseAlpha = -1;
private String plainText;
-
+
Letters(BitmapFont font, StringBlock bound, boolean rightToLeft) {
final String text = bound.getText();
this.block = bound;
@@ -78,10 +78,10 @@ class Letters {
// Give the letter a default color if
// one has been provided.
l.setColor( baseColor );
- }
+ }
}
}
-
+
LinkedList ranges = colorTags.getTags();
if (!ranges.isEmpty()) {
for (int i = 0; i < ranges.size()-1; i++) {
@@ -92,7 +92,7 @@ class Letters {
Range end = ranges.getLast();
setColor(end.start, plainText.length(), end.color);
}
-
+
invalidate();
}
@@ -103,17 +103,17 @@ class Letters {
LetterQuad getTail() {
return tail;
}
-
+
void update() {
LetterQuad l = head;
int lineCount = 1;
BitmapCharacter ellipsis = font.getCharSet().getCharacter(block.getEllipsisChar());
float ellipsisWidth = ellipsis!=null? ellipsis.getWidth()*getScale(): 0;
-
+
while (!l.isTail()) {
if (l.isInvalid()) {
l.update(block);
-
+
if (l.isInvalid(block)) {
switch (block.getLineWrapMode()) {
case Character:
@@ -144,7 +144,7 @@ class Letters {
}
}
break;
- case NoWrap:
+ case NoWrap:
LetterQuad cursor = l.getPrevious();
while (cursor.isInvalid(block, ellipsisWidth) && !cursor.isLineStart()) {
cursor = cursor.getPrevious();
@@ -158,10 +158,10 @@ class Letters {
cursor = cursor.getNext();
}
break;
- case Clip:
+ case Clip:
// Clip the character that falls out of bounds
l.clip(block);
-
+
// Clear the rest up to the next line feed.
for( LetterQuad q = l.getNext(); !q.isTail() && !q.isLineFeed(); q = q.getNext() ) {
q.setBitmapChar(null);
@@ -178,12 +178,12 @@ class Letters {
}
l = l.getNext();
}
-
+
align();
block.setLineCount(lineCount);
rewind();
}
-
+
private void align() {
final Align alignment = block.getAlignment();
final VAlign valignment = block.getVerticalAlignment();
@@ -233,7 +233,7 @@ class Letters {
l.invalidate();
l.update(block); // TODO: update from l
}
-
+
float getCharacterX0() {
return current.getX0();
}
@@ -241,54 +241,54 @@ class Letters {
float getCharacterY0() {
return current.getY0();
}
-
+
float getCharacterX1() {
return current.getX1();
}
-
+
float getCharacterY1() {
return current.getY1();
}
-
+
float getCharacterAlignX() {
return current.getAlignX();
}
-
+
float getCharacterAlignY() {
return current.getAlignY();
}
-
+
float getCharacterWidth() {
return current.getWidth();
}
-
+
float getCharacterHeight() {
return current.getHeight();
}
-
+
public boolean nextCharacter() {
if (current.isTail())
return false;
current = current.getNext();
return true;
}
-
+
public int getCharacterSetPage() {
return current.getBitmapChar().getPage();
}
-
+
public LetterQuad getQuad() {
return current;
}
-
+
public void rewind() {
current = head;
}
-
+
public void invalidate() {
invalidate(head);
}
-
+
public void invalidate(LetterQuad cursor) {
totalWidth = -1;
totalHeight = -1;
@@ -298,7 +298,7 @@ class Letters {
cursor = cursor.getNext();
}
}
-
+
float getScale() {
return block.getSize() / font.getCharSet().getRenderedSize();
}
@@ -306,7 +306,7 @@ class Letters {
public boolean isPrintable() {
return current.getBitmapChar() != null;
}
-
+
float getTotalWidth() {
validateSize();
return totalWidth;
@@ -316,7 +316,7 @@ class Letters {
validateSize();
return totalHeight;
}
-
+
void validateSize() {
if (totalWidth < 0) {
LetterQuad l = head;
@@ -371,11 +371,11 @@ class Letters {
cursor = cursor.getNext();
}
}
-
+
float getBaseAlpha() {
return baseAlpha;
}
-
+
void setBaseAlpha( float alpha ) { this.baseAlpha = alpha;
colorTags.setBaseAlpha(alpha);
@@ -409,7 +409,7 @@ class Letters {
setColor(end.start, plainText.length(), end.color);
}
}
-
+
invalidate();
}
diff --git a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java
index d636858c7..de2c6e8ae 100644
--- a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java
+++ b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java
@@ -43,13 +43,15 @@ import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
* A camera that follows a spatial and can turn around it by dragging the mouse
* @author nehon
*/
-public class ChaseCamera implements ActionListener, AnalogListener, Control {
+public class ChaseCamera implements ActionListener, AnalogListener, Control, JmeCloneable {
protected Spatial target = null;
protected float minVerticalRotation = 0.00f;
@@ -567,6 +569,7 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control {
* @param spatial
* @return
*/
+ @Override
public Control cloneForSpatial(Spatial spatial) {
ChaseCamera cc = new ChaseCamera(cam, spatial, inputManager);
cc.setMaxDistance(getMaxDistance());
@@ -574,6 +577,23 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control {
return cc;
}
+ @Override
+ public Object jmeClone() {
+ ChaseCamera cc = new ChaseCamera(cam, inputManager);
+ cc.target = target;
+ cc.setMaxDistance(getMaxDistance());
+ cc.setMinDistance(getMinDistance());
+ return cc;
+ }
+
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.target = cloner.clone(target);
+ computePosition();
+ prevPos = new Vector3f(target.getWorldTranslation());
+ cam.setLocation(pos);
+ }
+
/**
* Sets the spacial for the camera control, should only be used internally
* @param spatial
diff --git a/jme3-core/src/main/java/com/jme3/light/LightList.java b/jme3-core/src/main/java/com/jme3/light/LightList.java
index fc08df2c2..dfeb65405 100644
--- a/jme3-core/src/main/java/com/jme3/light/LightList.java
+++ b/jme3-core/src/main/java/com/jme3/light/LightList.java
@@ -33,6 +33,8 @@ package com.jme3.light;
import com.jme3.export.*;
import com.jme3.scene.Spatial;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import com.jme3.util.SortUtil;
import java.io.IOException;
import java.util.*;
@@ -40,10 +42,10 @@ import java.util.*;
/**
* LightList
is used internally by {@link Spatial}s to manage
* lights that are attached to them.
- *
+ *
* @author Kirill Vainer
*/
-public final class LightList implements Iterable, Savable, Cloneable {
+public final class LightList implements Iterable, Savable, Cloneable, JmeCloneable {
private Light[] list, tlist;
private float[] distToOwner;
@@ -74,7 +76,7 @@ public final class LightList implements Iterable, Savable, Cloneable {
/**
* Creates a LightList
for the given {@link Spatial}.
- *
+ *
* @param owner The spatial owner
*/
public LightList(Spatial owner) {
@@ -87,7 +89,7 @@ public final class LightList implements Iterable, Savable, Cloneable {
/**
* Set the owner of the LightList. Only used for cloning.
- * @param owner
+ * @param owner
*/
public void setOwner(Spatial owner){
this.owner = owner;
@@ -118,7 +120,7 @@ public final class LightList implements Iterable, Savable, Cloneable {
/**
* Remove the light at the given index.
- *
+ *
* @param index
*/
public void remove(int index){
@@ -139,7 +141,7 @@ public final class LightList implements Iterable, Savable, Cloneable {
/**
* Removes the given light from the LightList.
- *
+ *
* @param l the light to remove
*/
public void remove(Light l){
@@ -187,12 +189,12 @@ public final class LightList implements Iterable, Savable, Cloneable {
/**
* Sorts the elements in the list according to their Comparator.
- * There are two reasons why lights should be resorted.
- * First, if the lights have moved, that means their distance to
- * the spatial changed.
- * Second, if the spatial itself moved, it means the distance from it to
+ * There are two reasons why lights should be resorted.
+ * First, if the lights have moved, that means their distance to
+ * the spatial changed.
+ * Second, if the spatial itself moved, it means the distance from it to
* the individual lights might have changed.
- *
+ *
*
* @param transformChanged Whether the spatial's transform has changed
*/
@@ -252,7 +254,7 @@ public final class LightList implements Iterable, Savable, Cloneable {
list[p] = parent.list[i];
distToOwner[p] = Float.NEGATIVE_INFINITY;
}
-
+
listSize = local.listSize + parent.listSize;
}else{
listSize = local.listSize;
@@ -261,7 +263,7 @@ public final class LightList implements Iterable, Savable, Cloneable {
/**
* Returns an iterator that can be used to iterate over this LightList.
- *
+ *
* @return an iterator that can be used to iterate over this LightList.
*/
public Iterator iterator() {
@@ -276,10 +278,10 @@ public final class LightList implements Iterable, Savable, Cloneable {
public Light next() {
if (!hasNext())
throw new NoSuchElementException();
-
+
return list[index++];
}
-
+
public void remove() {
LightList.this.remove(--index);
}
@@ -290,7 +292,7 @@ public final class LightList implements Iterable, Savable, Cloneable {
public LightList clone(){
try{
LightList clone = (LightList) super.clone();
-
+
clone.owner = null;
clone.list = list.clone();
clone.distToOwner = distToOwner.clone();
@@ -302,6 +304,24 @@ public final class LightList implements Iterable, Savable, Cloneable {
}
}
+ @Override
+ public LightList jmeClone() {
+ try{
+ LightList clone = (LightList)super.clone();
+ clone.tlist = null; // list used for sorting only
+ return clone;
+ }catch (CloneNotSupportedException ex){
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.owner = cloner.clone(owner);
+ this.list = cloner.clone(list);
+ this.distToOwner = cloner.clone(distToOwner);
+ }
+
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
// oc.write(owner, "owner", null);
@@ -319,7 +339,7 @@ public final class LightList implements Iterable, Savable, Cloneable {
List lights = ic.readSavableArrayList("lights", null);
listSize = lights.size();
-
+
// NOTE: make sure the array has a length of at least 1
int arraySize = Math.max(DEFAULT_SIZE, listSize);
list = new Light[arraySize];
@@ -328,7 +348,7 @@ public final class LightList implements Iterable, Savable, Cloneable {
for (int i = 0; i < listSize; i++){
list[i] = lights.get(i);
}
-
+
Arrays.fill(distToOwner, Float.NEGATIVE_INFINITY);
}
diff --git a/jme3-core/src/main/java/com/jme3/material/MatParam.java b/jme3-core/src/main/java/com/jme3/material/MatParam.java
index c7f3dfc31..8d965e363 100644
--- a/jme3-core/src/main/java/com/jme3/material/MatParam.java
+++ b/jme3-core/src/main/java/com/jme3/material/MatParam.java
@@ -244,16 +244,45 @@ When arrays can be inserted in J3M files
if (texKey.isFlipY()) {
ret += "Flip ";
}
- if (texVal.getWrap(Texture.WrapAxis.S) == WrapMode.Repeat) {
- ret += "Repeat ";
+
+ //Wrap mode
+ ret += getWrapMode(texVal, Texture.WrapAxis.S);
+ ret += getWrapMode(texVal, Texture.WrapAxis.T);
+ ret += getWrapMode(texVal, Texture.WrapAxis.R);
+
+ //Min and Mag filter
+ Texture.MinFilter def = Texture.MinFilter.BilinearNoMipMaps;
+ if(texVal.getImage().hasMipmaps() || texKey.isGenerateMips()){
+ def = Texture.MinFilter.Trilinear;
+ }
+ if(texVal.getMinFilter() != def){
+ ret += "Min" + texVal.getMinFilter().name()+ " ";
+ }
+
+ if(texVal.getMagFilter() != Texture.MagFilter.Bilinear){
+ ret += "Mag" + texVal.getMagFilter().name()+ " ";
}
- return ret + texKey.getName();
+ return ret + "\"" + texKey.getName() + "\"";
default:
return null; // parameter type not supported in J3M
}
}
+ private String getWrapMode(Texture texVal, Texture.WrapAxis axis) {
+ WrapMode mode = WrapMode.EdgeClamp;
+ try{
+ mode = texVal.getWrap(axis);
+ }catch (IllegalArgumentException e){
+ //this axis doesn't exist on the texture
+ return "";
+ }
+ if(mode != WrapMode.EdgeClamp){
+ return"Wrap"+ mode.name() + "_" + axis.name() + " ";
+ }
+ return "";
+ }
+
@Override
public MatParam clone() {
try {
diff --git a/jme3-core/src/main/java/com/jme3/material/RenderState.java b/jme3-core/src/main/java/com/jme3/material/RenderState.java
index 9359ce7f4..94cae3f50 100644
--- a/jme3-core/src/main/java/com/jme3/material/RenderState.java
+++ b/jme3-core/src/main/java/com/jme3/material/RenderState.java
@@ -311,6 +311,8 @@ public class RenderState implements Cloneable, Savable {
boolean applyPolyOffset = true;
boolean stencilTest = false;
boolean applyStencilTest = false;
+ float lineWidth = 1;
+ boolean applyLineWidth = false;
TestFunction depthFunc = TestFunction.LessOrEqual;
//by default depth func will be applied anyway if depth test is applied
boolean applyDepthFunc = false;
@@ -350,6 +352,9 @@ public class RenderState implements Cloneable, Savable {
oc.write(backStencilDepthPassOperation, "backStencilDepthPassOperation", StencilOperation.Keep);
oc.write(frontStencilFunction, "frontStencilFunction", TestFunction.Always);
oc.write(backStencilFunction, "backStencilFunction", TestFunction.Always);
+ oc.write(depthFunc, "depthFunc", TestFunction.LessOrEqual);
+ oc.write(alphaFunc, "alphaFunc", TestFunction.Greater);
+ oc.write(lineWidth, "lineWidth", 1);
// Only "additional render state" has them set to false by default
oc.write(applyPointSprite, "applyPointSprite", true);
@@ -364,8 +369,7 @@ public class RenderState implements Cloneable, Savable {
oc.write(applyPolyOffset, "applyPolyOffset", true);
oc.write(applyDepthFunc, "applyDepthFunc", true);
oc.write(applyAlphaFunc, "applyAlphaFunc", false);
- oc.write(depthFunc, "depthFunc", TestFunction.LessOrEqual);
- oc.write(alphaFunc, "alphaFunc", TestFunction.Greater);
+ oc.write(applyLineWidth, "applyLineWidth", true);
}
@@ -394,6 +398,8 @@ public class RenderState implements Cloneable, Savable {
backStencilFunction = ic.readEnum("backStencilFunction", TestFunction.class, TestFunction.Always);
depthFunc = ic.readEnum("depthFunc", TestFunction.class, TestFunction.LessOrEqual);
alphaFunc = ic.readEnum("alphaFunc", TestFunction.class, TestFunction.Greater);
+ lineWidth = ic.readFloat("lineWidth", 1);
+
applyPointSprite = ic.readBoolean("applyPointSprite", true);
applyWireFrame = ic.readBoolean("applyWireFrame", true);
@@ -407,6 +413,8 @@ public class RenderState implements Cloneable, Savable {
applyPolyOffset = ic.readBoolean("applyPolyOffset", true);
applyDepthFunc = ic.readBoolean("applyDepthFunc", true);
applyAlphaFunc = ic.readBoolean("applyAlphaFunc", false);
+ applyLineWidth = ic.readBoolean("applyLineWidth", true);
+
}
@@ -528,6 +536,10 @@ public class RenderState implements Cloneable, Savable {
}
}
+ if(lineWidth != rs.lineWidth){
+ return false;
+ }
+
return true;
}
@@ -803,8 +815,17 @@ public class RenderState implements Cloneable, Savable {
this.alphaFunc = alphaFunc;
cachedHashCode = -1;
}
-
-
+
+ /**
+ * Sets the mesh line width.
+ * This is to use in conjunction with {@link #setWireframe(boolean)} or with a mesh in {@link Mesh.Mode#Lines} mode.
+ * @param lineWidth the line width.
+ */
+ public void setLineWidth(float lineWidth) {
+ this.lineWidth = lineWidth;
+ this.applyLineWidth = true;
+ cachedHashCode = -1;
+ }
/**
* Check if stencil test is enabled.
@@ -1118,8 +1139,16 @@ public class RenderState implements Cloneable, Savable {
public TestFunction getAlphaFunc() {
return alphaFunc;
}
-
-
+
+ /**
+ * returns the wireframe line width
+ *
+ * @return the line width
+ */
+ public float getLineWidth() {
+ return lineWidth;
+ }
+
public boolean isApplyAlphaFallOff() {
return applyAlphaFallOff;
@@ -1168,8 +1197,10 @@ public class RenderState implements Cloneable, Savable {
public boolean isApplyAlphaFunc() {
return applyAlphaFunc;
}
-
-
+
+ public boolean isApplyLineWidth() {
+ return applyLineWidth;
+ }
/**
*
@@ -1200,6 +1231,7 @@ public class RenderState implements Cloneable, Savable {
hash = 79 * hash + (this.backStencilDepthPassOperation != null ? this.backStencilDepthPassOperation.hashCode() : 0);
hash = 79 * hash + (this.frontStencilFunction != null ? this.frontStencilFunction.hashCode() : 0);
hash = 79 * hash + (this.backStencilFunction != null ? this.backStencilFunction.hashCode() : 0);
+ hash = 79 * hash + Float.floatToIntBits(this.lineWidth);
cachedHashCode = hash;
}
return cachedHashCode;
@@ -1324,6 +1356,11 @@ public class RenderState implements Cloneable, Savable {
state.frontStencilFunction = frontStencilFunction;
state.backStencilFunction = backStencilFunction;
}
+ if (additionalState.applyLineWidth) {
+ state.lineWidth = additionalState.lineWidth;
+ } else {
+ state.lineWidth = lineWidth;
+ }
state.cachedHashCode = -1;
return state;
}
@@ -1351,6 +1388,7 @@ public class RenderState implements Cloneable, Savable {
backStencilFunction = state.backStencilFunction;
depthFunc = state.depthFunc;
alphaFunc = state.alphaFunc;
+ lineWidth = state.lineWidth;
applyPointSprite = true;
applyWireFrame = true;
@@ -1364,6 +1402,7 @@ public class RenderState implements Cloneable, Savable {
applyPolyOffset = true;
applyDepthFunc = true;
applyAlphaFunc = false;
+ applyLineWidth = true;
}
@Override
@@ -1392,7 +1431,8 @@ public class RenderState implements Cloneable, Savable {
+ "\noffsetEnabled=" + offsetEnabled
+ "\napplyPolyOffset=" + applyPolyOffset
+ "\noffsetFactor=" + offsetFactor
- + "\noffsetUnits=" + offsetUnits
+ + "\noffsetUnits=" + offsetUnits
+ + "\nlineWidth=" + lineWidth
+ "\n]";
}
}
diff --git a/jme3-core/src/main/java/com/jme3/math/Spline.java b/jme3-core/src/main/java/com/jme3/math/Spline.java
index 041baa0ba..2220ea8b7 100644
--- a/jme3-core/src/main/java/com/jme3/math/Spline.java
+++ b/jme3-core/src/main/java/com/jme3/math/Spline.java
@@ -90,7 +90,7 @@ public class Spline implements Savable {
type = splineType;
this.curveTension = curveTension;
this.cycle = cycle;
- this.computeTotalLentgh();
+ this.computeTotalLength();
}
/**
@@ -116,7 +116,7 @@ public class Spline implements Savable {
this.controlPoints.addAll(controlPoints);
this.curveTension = curveTension;
this.cycle = cycle;
- this.computeTotalLentgh();
+ this.computeTotalLength();
}
/**
@@ -144,7 +144,7 @@ public class Spline implements Savable {
this.weights[i] = controlPoint.w;
}
CurveAndSurfaceMath.prepareNurbsKnots(knots, basisFunctionDegree);
- this.computeTotalLentgh();
+ this.computeTotalLength();
}
private void initCatmullRomWayPoints(List list) {
@@ -186,7 +186,7 @@ public class Spline implements Savable {
controlPoints.add(controlPoints.get(0).clone());
}
if (controlPoints.size() > 1) {
- this.computeTotalLentgh();
+ this.computeTotalLength();
}
}
@@ -197,7 +197,7 @@ public class Spline implements Savable {
public void removeControlPoint(Vector3f controlPoint) {
controlPoints.remove(controlPoint);
if (controlPoints.size() > 1) {
- this.computeTotalLentgh();
+ this.computeTotalLength();
}
}
@@ -209,7 +209,7 @@ public class Spline implements Savable {
/**
* This method computes the total length of the curve.
*/
- private void computeTotalLentgh() {
+ private void computeTotalLength() {
totalLength = 0;
float l = 0;
if (segmentsLength == null) {
@@ -317,7 +317,7 @@ public class Spline implements Savable {
public void setCurveTension(float curveTension) {
this.curveTension = curveTension;
if(type==SplineType.CatmullRom && !getControlPoints().isEmpty()) {
- this.computeTotalLentgh();
+ this.computeTotalLength();
}
}
@@ -342,7 +342,7 @@ public class Spline implements Savable {
controlPoints.add(controlPoints.get(0));
}
this.cycle = cycle;
- this.computeTotalLentgh();
+ this.computeTotalLength();
} else {
this.cycle = cycle;
}
@@ -369,7 +369,7 @@ public class Spline implements Savable {
*/
public void setType(SplineType type) {
this.type = type;
- this.computeTotalLentgh();
+ this.computeTotalLength();
}
/**
@@ -435,9 +435,13 @@ public class Spline implements Savable {
OutputCapsule oc = ex.getCapsule(this);
oc.writeSavableArrayList((ArrayList) controlPoints, "controlPoints", null);
oc.write(type, "type", SplineType.CatmullRom);
- float list[] = new float[segmentsLength.size()];
- for (int i = 0; i < segmentsLength.size(); i++) {
- list[i] = segmentsLength.get(i);
+
+ float list[] = null;
+ if (segmentsLength != null) {
+ list = new float[segmentsLength.size()];
+ for (int i = 0; i < segmentsLength.size(); i++) {
+ list[i] = segmentsLength.get(i);
+ }
}
oc.write(list, "segmentsLength", null);
@@ -454,7 +458,7 @@ public class Spline implements Savable {
public void read(JmeImporter im) throws IOException {
InputCapsule in = im.getCapsule(this);
- controlPoints = (ArrayList) in.readSavableArrayList("wayPoints", null);
+ controlPoints = (ArrayList) in.readSavableArrayList("controlPoints", new ArrayList()); /* Empty List as default, prevents null pointers */
float list[] = in.readFloatArray("segmentsLength", null);
if (list != null) {
segmentsLength = new ArrayList();
diff --git a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java
index 2f52eb425..cf51ad0c9 100644
--- a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java
+++ b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java
@@ -401,8 +401,10 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
viewPort.getCamera().setViewPort(left, right, bottom, top);
viewPort.setOutputFrameBuffer(outputBuffer);
viewPort = null;
-
- renderFrameBuffer.dispose();
+
+ if(renderFrameBuffer != null){
+ renderFrameBuffer.dispose();
+ }
if(depthTexture!=null){
depthTexture.getImage().dispose();
}
diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java
index 8287a270e..5be184c94 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java
@@ -101,7 +101,7 @@ public class RenderContext {
public float pointSize = 1;
/**
- * @see Mesh#setLineWidth(float)
+ * @see RenderState#setLineWidth(float)
*/
public float lineWidth = 1;
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
index 6ce3bc294..76e3a873e 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
@@ -152,7 +152,7 @@ public final class GLRenderer implements Renderer {
int major = Integer.parseInt(m.group(1));
int minor = Integer.parseInt(m.group(2));
if (minor >= 10 && minor % 10 == 0) {
- // some versions can look like "1.30" instead of "1.3".
+ // some versions can look like "1.30" instead of "1.3".
// make sure to correct for this
minor /= 10;
}
@@ -524,7 +524,7 @@ public final class GLRenderer implements Renderer {
// Initialize default state..
gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
-
+
if (caps.contains(Caps.SeamlessCubemap)) {
// Enable this globally. Should be OK.
gl.glEnable(GLExt.GL_TEXTURE_CUBE_MAP_SEAMLESS);
@@ -644,7 +644,7 @@ public final class GLRenderer implements Renderer {
gl.glDepthFunc(convertTestFunction(state.getDepthFunc()));
context.depthFunc = state.getDepthFunc();
}
-
+
if (state.isDepthWrite() && !context.depthWriteEnabled) {
gl.glDepthMask(true);
context.depthWriteEnabled = true;
@@ -797,6 +797,10 @@ public final class GLRenderer implements Renderer {
gl.glDisable(GL.GL_STENCIL_TEST);
}
}
+ if (context.lineWidth != state.getLineWidth()) {
+ gl.glLineWidth(state.getLineWidth());
+ context.lineWidth = state.getLineWidth();
+ }
}
private int convertStencilOperation(StencilOperation stencilOp) {
@@ -1094,7 +1098,7 @@ public final class GLRenderer implements Renderer {
if (gles2) {
// request GLSL ES (1.00) when compiling under GLES2.
stringBuf.append("#version 100\n");
-
+
if (source.getType() == ShaderType.Fragment) {
// GLES2 requires precision qualifier.
stringBuf.append("precision mediump float;\n");
@@ -1481,7 +1485,7 @@ public final class GLRenderer implements Renderer {
rb.getId());
}
}
-
+
private void bindFrameBuffer(FrameBuffer fb) {
if (fb == null) {
if (context.boundFBO != 0) {
@@ -1530,6 +1534,7 @@ public final class GLRenderer implements Renderer {
updateFrameBufferAttachment(fb, depthBuf);
}
+
setReadDrawBuffers(fb);
checkFrameBufferError();
@@ -1570,11 +1575,11 @@ public final class GLRenderer implements Renderer {
if (gl2 == null) {
return;
}
-
+
final int NONE = -2;
final int INITIAL = -1;
final int MRT_OFF = 100;
-
+
if (fb == null) {
// Set Read/Draw buffers to initial value.
if (context.boundDrawBuf != INITIAL) {
@@ -1638,9 +1643,9 @@ public final class GLRenderer implements Renderer {
}
}
}
-
+
}
-
+
public void setFrameBuffer(FrameBuffer fb) {
if (fb == null && mainFbOverride != null) {
fb = mainFbOverride;
@@ -1884,7 +1889,7 @@ public final class GLRenderer implements Renderer {
if (image != null) {
haveMips = image.isGeneratedMipmapsRequired() || image.hasMipmaps();
}
-
+
LastTextureState curState = image.getLastTextureState();
if (curState.magFilter != tex.getMagFilter()) {
@@ -1947,7 +1952,7 @@ public final class GLRenderer implements Renderer {
}
curState.shadowCompareMode = texCompareMode;
}
-
+
// If at this point we didn't bind the texture, bind it now
bindTextureOnly(target, image, unit);
}
@@ -1955,7 +1960,7 @@ public final class GLRenderer implements Renderer {
/**
* Validates if a potentially NPOT texture is supported by the hardware.
*
- * Textures with power-of-2 dimensions are supported on all hardware, however
+ * Textures with power-of-2 dimensions are supported on all hardware, however
* non-power-of-2 textures may or may not be supported depending on which
* texturing features are used.
*
@@ -2010,7 +2015,7 @@ public final class GLRenderer implements Renderer {
/**
* Ensures that the texture is bound to the given unit
* and that the unit is currently active (for modification).
- *
+ *
* @param target The texture target, one of GL_TEXTURE_***
* @param img The image texture to bind
* @param unit At what unit to bind the texture.
@@ -2028,11 +2033,11 @@ public final class GLRenderer implements Renderer {
statistics.onTextureUse(img, false);
}
}
-
+
/**
* Ensures that the texture is bound to the given unit,
* but does not care if the unit is active (for rendering).
- *
+ *
* @param target The texture target, one of GL_TEXTURE_***
* @param img The image texture to bind
* @param unit At what unit to bind the texture.
@@ -2050,7 +2055,7 @@ public final class GLRenderer implements Renderer {
statistics.onTextureUse(img, false);
}
}
-
+
/**
* Uploads the given image to the GL driver.
*
@@ -2074,7 +2079,6 @@ public final class GLRenderer implements Renderer {
// bind texture
int target = convertTextureType(type, img.getMultiSamples(), -1);
-
bindTextureAndUnit(target, img, unit);
if (!img.hasMipmaps() && img.isGeneratedMipmapsRequired()) {
@@ -2089,7 +2093,7 @@ public final class GLRenderer implements Renderer {
// We'll generate mipmaps via glGenerateMipmapEXT (see below)
}
} else if (img.hasMipmaps()) {
- // Image already has mipmaps, set the max level based on the
+ // Image already has mipmaps, set the max level based on the
// number of mipmaps we have.
gl.glTexParameteri(target, GL.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1);
} else {
@@ -2825,7 +2829,8 @@ public final class GLRenderer implements Renderer {
throw new RendererException("Mesh instancing is not supported by the video hardware");
}
- if (context.lineWidth != mesh.getLineWidth()) {
+ //this is kept for backward compatibility.
+ if (mesh.getLineWidth() != -1 && context.lineWidth != mesh.getLineWidth()) {
gl.glLineWidth(mesh.getLineWidth());
context.lineWidth = mesh.getLineWidth();
}
diff --git a/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java b/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java
index bf376006e..d31997f5b 100644
--- a/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java
+++ b/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java
@@ -39,6 +39,7 @@ import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.binary.BinaryImporter;
+import com.jme3.util.clone.Cloner;
import com.jme3.util.SafeArrayList;
import java.io.IOException;
import java.util.*;
@@ -50,7 +51,7 @@ import java.util.logging.Logger;
* The AssetLinkNode does not store its children when exported to file.
* Instead, you can add a list of AssetKeys that will be loaded and attached
* when the AssetLinkNode is restored.
- *
+ *
* @author normenhansen
*/
public class AssetLinkNode extends Node {
@@ -70,6 +71,20 @@ public class AssetLinkNode extends Node {
assetLoaderKeys.add(key);
}
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ super.cloneFields(cloner, original);
+
+ // This is a change in behavior because the old version did not clone
+ // this list... changes to one clone would be reflected in all.
+ // I think that's probably undesirable. -pspeed
+ this.assetLoaderKeys = cloner.clone(assetLoaderKeys);
+ this.assetChildren = new HashMap();
+ }
+
/**
* Add a "linked" child. These are loaded from the assetManager when the
* AssetLinkNode is loaded from a binary file.
@@ -166,7 +181,7 @@ public class AssetLinkNode extends Node {
children.add(child);
assetChildren.put(modelKey, child);
} else {
- Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}",
+ Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}",
new Object[]{ modelKey, key });
}
}
diff --git a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java
index 26d944b88..1b0d5e051 100644
--- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java
+++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java
@@ -48,6 +48,8 @@ import com.jme3.math.Vector3f;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
/**
* BatchNode holds geometries that are a batched version of all the geometries that are in its sub scenegraph.
@@ -60,7 +62,7 @@ import com.jme3.util.TempVars;
* Sub geoms can be removed but it may be slower than the normal spatial removing
* Sub geoms can be added after the batch() method has been called but won't be batched and will just be rendered as normal geometries.
* To integrate them in the batch you have to call the batch() method again on the batchNode.
- *
+ *
* TODO normal or tangents or both looks a bit weird
* TODO more automagic (batch when needed in the updateLogicalState)
* @author Nehon
@@ -77,7 +79,7 @@ public class BatchNode extends GeometryGroupNode {
*/
protected Map batchesByGeom = new HashMap();
/**
- * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer
+ * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer
*/
private float[] tmpFloat;
private float[] tmpFloatN;
@@ -96,7 +98,7 @@ public class BatchNode extends GeometryGroupNode {
public BatchNode(String name) {
super(name);
}
-
+
@Override
public void onTransformChange(Geometry geom) {
updateSubBatch(geom);
@@ -123,7 +125,7 @@ public class BatchNode extends GeometryGroupNode {
protected Matrix4f getTransformMatrix(Geometry g){
return g.cachedWorldMat;
}
-
+
protected void updateSubBatch(Geometry bg) {
Batch batch = batchesByGeom.get(bg);
if (batch != null) {
@@ -134,13 +136,13 @@ public class BatchNode extends GeometryGroupNode {
FloatBuffer posBuf = (FloatBuffer) pvb.getData();
VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal);
FloatBuffer normBuf = (FloatBuffer) nvb.getData();
-
+
VertexBuffer opvb = origMesh.getBuffer(VertexBuffer.Type.Position);
FloatBuffer oposBuf = (FloatBuffer) opvb.getData();
VertexBuffer onvb = origMesh.getBuffer(VertexBuffer.Type.Normal);
FloatBuffer onormBuf = (FloatBuffer) onvb.getData();
Matrix4f transformMat = getTransformMatrix(bg);
-
+
if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) {
VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent);
@@ -184,12 +186,12 @@ public class BatchNode extends GeometryGroupNode {
}
batches.clear();
batchesByGeom.clear();
- }
+ }
//only reset maxVertCount if there is something new to batch
if (matMap.size() > 0) {
maxVertCount = 0;
}
-
+
for (Map.Entry> entry : matMap.entrySet()) {
Mesh m = new Mesh();
Material material = entry.getKey();
@@ -255,7 +257,7 @@ public class BatchNode extends GeometryGroupNode {
/**
* recursively visit the subgraph and unbatch geometries
- * @param s
+ * @param s
*/
private void unbatchSubGraph(Spatial s) {
if (s instanceof Node) {
@@ -269,8 +271,8 @@ public class BatchNode extends GeometryGroupNode {
}
}
}
-
-
+
+
private void gatherGeometries(Map> map, Spatial n, boolean rebatch) {
if (n instanceof Geometry) {
@@ -283,7 +285,7 @@ public class BatchNode extends GeometryGroupNode {
}
List list = map.get(g.getMaterial());
if (list == null) {
- //trying to compare materials with the isEqual method
+ //trying to compare materials with the isEqual method
for (Map.Entry> mat : map.entrySet()) {
if (g.getMaterial().contentEquals(mat.getKey())) {
list = mat.getValue();
@@ -331,7 +333,7 @@ public class BatchNode extends GeometryGroupNode {
/**
* Sets the material to the all the batches of this BatchNode
* use setMaterial(Material material,int batchIndex) to set a material to a specific batch
- *
+ *
* @param material the material to use for this geometry
*/
@Override
@@ -341,12 +343,12 @@ public class BatchNode extends GeometryGroupNode {
/**
* Returns the material that is used for the first batch of this BatchNode
- *
+ *
* use getMaterial(Material material,int batchIndex) to get a material from a specific batch
- *
+ *
* @return the material that is used for the first batch of this BatchNode
- *
- * @see #setMaterial(com.jme3.material.Material)
+ *
+ * @see #setMaterial(com.jme3.material.Material)
*/
public Material getMaterial() {
if (!batches.isEmpty()) {
@@ -359,7 +361,7 @@ public class BatchNode extends GeometryGroupNode {
/**
* Merges all geometries in the collection into
* the output mesh. Does not take into account materials.
- *
+ *
* @param geometries
* @param outMesh
*/
@@ -383,7 +385,7 @@ public class BatchNode extends GeometryGroupNode {
maxVertCount = geom.getVertexCount();
}
Mesh.Mode listMode;
- float listLineWidth = 1f;
+ //float listLineWidth = 1f;
int components;
switch (geom.getMesh().getMode()) {
case Points:
@@ -394,7 +396,7 @@ public class BatchNode extends GeometryGroupNode {
case LineStrip:
case Lines:
listMode = Mesh.Mode.Lines;
- listLineWidth = geom.getMesh().getLineWidth();
+ //listLineWidth = geom.getMesh().getLineWidth();
components = 2;
break;
case TriangleFan:
@@ -418,7 +420,7 @@ public class BatchNode extends GeometryGroupNode {
formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat();
normForBuf[vb.getBufferType().ordinal()] = vb.isNormalized();
}
-
+
maxWeights = Math.max(maxWeights, geom.getMesh().getMaxNumWeights());
if (mode != null && mode != listMode) {
@@ -426,19 +428,20 @@ public class BatchNode extends GeometryGroupNode {
+ " primitive types: " + mode + " != " + listMode);
}
mode = listMode;
- if (mode == Mesh.Mode.Lines) {
- if (lineWidth != 1f && listLineWidth != lineWidth) {
- throw new UnsupportedOperationException("When using Mesh Line mode, cannot combine meshes with different line width "
- + lineWidth + " != " + listLineWidth);
- }
- lineWidth = listLineWidth;
- }
+ //Not needed anymore as lineWidth is now in RenderState and will be taken into account when merging according to the material
+// if (mode == Mesh.Mode.Lines) {
+// if (lineWidth != 1f && listLineWidth != lineWidth) {
+// throw new UnsupportedOperationException("When using Mesh Line mode, cannot combine meshes with different line width "
+// + lineWidth + " != " + listLineWidth);
+// }
+// lineWidth = listLineWidth;
+// }
compsForBuf[VertexBuffer.Type.Index.ordinal()] = components;
}
outMesh.setMaxNumWeights(maxWeights);
outMesh.setMode(mode);
- outMesh.setLineWidth(lineWidth);
+ //outMesh.setLineWidth(lineWidth);
if (totalVerts >= 65536) {
// make sure we create an UnsignedInt buffer so we can fit all of the meshes
formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt;
@@ -585,7 +588,7 @@ public class BatchNode extends GeometryGroupNode {
int offset = start * 3;
int tanOffset = start * 4;
-
+
bindBufPos.rewind();
bindBufNorm.rewind();
bindBufTangents.rewind();
@@ -661,10 +664,10 @@ public class BatchNode extends GeometryGroupNode {
vars.release();
}
- protected class Batch {
+ protected class Batch implements JmeCloneable {
/**
* update the batchesByGeom map for this batch with the given List of geometries
- * @param list
+ * @param list
*/
void updateGeomList(List list) {
for (Geometry geom : list) {
@@ -674,6 +677,25 @@ public class BatchNode extends GeometryGroupNode {
}
}
Geometry geometry;
+
+ public final Geometry getGeometry() {
+ return geometry;
+ }
+
+ @Override
+ public Batch jmeClone() {
+ try {
+ return (Batch)super.clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.geometry = cloner.clone(geometry);
+ }
+
}
protected void setNeedsFullRebatch(boolean needsFullRebatch) {
@@ -699,7 +721,27 @@ public class BatchNode extends GeometryGroupNode {
}
return clone;
}
-
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ super.cloneFields(cloner, original);
+
+ this.batches = cloner.clone(batches);
+ this.tmpFloat = cloner.clone(tmpFloat);
+ this.tmpFloatN = cloner.clone(tmpFloatN);
+ this.tmpFloatT = cloner.clone(tmpFloatT);
+
+
+ HashMap newBatchesByGeom = new HashMap();
+ for( Map.Entry e : batchesByGeom.entrySet() ) {
+ newBatchesByGeom.put(cloner.clone(e.getKey()), cloner.clone(e.getValue()));
+ }
+ this.batchesByGeom = newBatchesByGeom;
+ }
+
@Override
public int collideWith(Collidable other, CollisionResults results) {
int total = 0;
diff --git a/jme3-core/src/main/java/com/jme3/scene/CameraNode.java b/jme3-core/src/main/java/com/jme3/scene/CameraNode.java
index 36cde482c..44fed8208 100644
--- a/jme3-core/src/main/java/com/jme3/scene/CameraNode.java
+++ b/jme3-core/src/main/java/com/jme3/scene/CameraNode.java
@@ -36,6 +36,7 @@ import com.jme3.export.JmeImporter;
import com.jme3.renderer.Camera;
import com.jme3.scene.control.CameraControl;
import com.jme3.scene.control.CameraControl.ControlDirection;
+import com.jme3.util.clone.Cloner;
import java.io.IOException;
/**
@@ -93,7 +94,20 @@ public class CameraNode extends Node {
// this.lookAt(position, upVector);
// camControl.getCamera().lookAt(position, upVector);
// }
-
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ super.cloneFields(cloner, original);
+
+ // A change in behavior... I think previously CameraNode was probably
+ // not really cloneable... or at least its camControl would be pointing
+ // to the wrong control. -pspeed
+ this.camControl = cloner.clone(camControl);
+ }
+
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
diff --git a/jme3-core/src/main/java/com/jme3/scene/Geometry.java b/jme3-core/src/main/java/com/jme3/scene/Geometry.java
index 4c394101a..f2f33501a 100644
--- a/jme3-core/src/main/java/com/jme3/scene/Geometry.java
+++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java
@@ -43,6 +43,8 @@ import com.jme3.material.Material;
import com.jme3.math.Matrix4f;
import com.jme3.renderer.Camera;
import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.IdentityCloneFunction;
import com.jme3.util.TempVars;
import java.io.IOException;
import java.util.Queue;
@@ -54,12 +56,12 @@ import java.util.logging.Logger;
* contains the geometric data for rendering objects. It manages all rendering
* information such as a {@link Material} object to define how the surface
* should be shaded and the {@link Mesh} data to contain the actual geometry.
- *
+ *
* @author Kirill Vainer
*/
public class Geometry extends Spatial {
- // Version #1: removed shared meshes.
+ // Version #1: removed shared meshes.
// models loaded with shared mesh will be automatically fixed.
public static final int SAVABLE_VERSION = 1;
private static final Logger logger = Logger.getLogger(Geometry.class.getName());
@@ -71,19 +73,19 @@ public class Geometry extends Spatial {
*/
protected boolean ignoreTransform = false;
protected transient Matrix4f cachedWorldMat = new Matrix4f();
-
+
/**
* Specifies which {@link GeometryGroupNode} this Geometry
* is managed by.
*/
protected GeometryGroupNode groupNode;
-
+
/**
- * The start index of this Geometry's
inside
+ * The start index of this Geometry's
inside
* the {@link GeometryGroupNode}.
*/
protected int startIndex = -1;
-
+
/**
* Serialization only. Do not use.
*/
@@ -95,37 +97,37 @@ public class Geometry extends Spatial {
* Create a geometry node without any mesh data.
* Both the mesh and the material are null, the geometry
* cannot be rendered until those are set.
- *
+ *
* @param name The name of this geometry
*/
public Geometry(String name) {
super(name);
-
+
// For backwards compatibility, only clear the "requires
// update" flag if we are not a subclass of Geometry.
// This prevents subclass from silently failing to receive
// updates when they upgrade.
- setRequiresUpdates(Geometry.class != getClass());
+ setRequiresUpdates(Geometry.class != getClass());
}
/**
* Create a geometry node with mesh data.
* The material of the geometry is null, it cannot
* be rendered until it is set.
- *
+ *
* @param name The name of this geometry
* @param mesh The mesh data for this geometry
*/
public Geometry(String name, Mesh mesh) {
this(name);
-
+
if (mesh == null) {
throw new IllegalArgumentException("mesh cannot be null");
}
this.mesh = mesh;
}
-
+
@Override
public boolean checkCulling(Camera cam) {
if (isGrouped()) {
@@ -137,8 +139,8 @@ public class Geometry extends Spatial {
/**
* @return If ignoreTransform mode is set.
- *
- * @see Geometry#setIgnoreTransform(boolean)
+ *
+ * @see Geometry#setIgnoreTransform(boolean)
*/
public boolean isIgnoreTransform() {
return ignoreTransform;
@@ -156,7 +158,7 @@ public class Geometry extends Spatial {
* Level 0 indicates that the default index buffer should be used,
* levels [1, LodLevels + 1] represent the levels set on the mesh
* with {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
- *
+ *
* @param lod The lod level to set
*/
@Override
@@ -170,7 +172,7 @@ public class Geometry extends Spatial {
}
lodLevel = lod;
-
+
if (isGrouped()) {
groupNode.onMeshChange(this);
}
@@ -178,7 +180,7 @@ public class Geometry extends Spatial {
/**
* Returns the LOD level set with {@link #setLodLevel(int) }.
- *
+ *
* @return the LOD level set
*/
public int getLodLevel() {
@@ -187,10 +189,10 @@ public class Geometry extends Spatial {
/**
* Returns this geometry's mesh vertex count.
- *
+ *
* @return this geometry's mesh vertex count.
- *
- * @see Mesh#getVertexCount()
+ *
+ * @see Mesh#getVertexCount()
*/
public int getVertexCount() {
return mesh.getVertexCount();
@@ -198,10 +200,10 @@ public class Geometry extends Spatial {
/**
* Returns this geometry's mesh triangle count.
- *
+ *
* @return this geometry's mesh triangle count.
- *
- * @see Mesh#getTriangleCount()
+ *
+ * @see Mesh#getTriangleCount()
*/
public int getTriangleCount() {
return mesh.getTriangleCount();
@@ -209,9 +211,9 @@ public class Geometry extends Spatial {
/**
* Sets the mesh to use for this geometry when rendering.
- *
+ *
* @param mesh the mesh to use for this geometry
- *
+ *
* @throws IllegalArgumentException If mesh is null
*/
public void setMesh(Mesh mesh) {
@@ -221,7 +223,7 @@ public class Geometry extends Spatial {
this.mesh = mesh;
setBoundRefresh();
-
+
if (isGrouped()) {
groupNode.onMeshChange(this);
}
@@ -229,10 +231,10 @@ public class Geometry extends Spatial {
/**
* Returns the mesh to use for this geometry
- *
+ *
* @return the mesh to use for this geometry
- *
- * @see #setMesh(com.jme3.scene.Mesh)
+ *
+ * @see #setMesh(com.jme3.scene.Mesh)
*/
public Mesh getMesh() {
return mesh;
@@ -240,13 +242,13 @@ public class Geometry extends Spatial {
/**
* Sets the material to use for this geometry.
- *
+ *
* @param material the material to use for this geometry
*/
@Override
public void setMaterial(Material material) {
this.material = material;
-
+
if (isGrouped()) {
groupNode.onMaterialChange(this);
}
@@ -254,10 +256,10 @@ public class Geometry extends Spatial {
/**
* Returns the material that is used for this geometry.
- *
+ *
* @return the material that is used for this geometry
- *
- * @see #setMaterial(com.jme3.material.Material)
+ *
+ * @see #setMaterial(com.jme3.material.Material)
*/
public Material getMaterial() {
return material;
@@ -310,18 +312,18 @@ public class Geometry extends Spatial {
computeWorldMatrix();
if (isGrouped()) {
- groupNode.onTransformChange(this);
+ groupNode.onTransformChange(this);
}
-
+
// geometry requires lights to be sorted
worldLights.sort(true);
}
/**
* Associate this Geometry
with a {@link GeometryGroupNode}.
- *
+ *
* Should only be called by the parent {@link GeometryGroupNode}.
- *
+ *
* @param node Which {@link GeometryGroupNode} to associate with.
* @param startIndex The starting index of this geometry in the group.
*/
@@ -329,26 +331,26 @@ public class Geometry extends Spatial {
if (isGrouped()) {
unassociateFromGroupNode();
}
-
+
this.groupNode = node;
this.startIndex = startIndex;
}
/**
- * Removes the {@link GeometryGroupNode} association from this
+ * Removes the {@link GeometryGroupNode} association from this
* Geometry
.
- *
+ *
* Should only be called by the parent {@link GeometryGroupNode}.
*/
public void unassociateFromGroupNode() {
if (groupNode != null) {
- // Once the geometry is removed
+ // Once the geometry is removed
// from the parent, the group node needs to be updated.
groupNode.onGeometryUnassociated(this);
groupNode = null;
-
+
// change the default to -1 to make error detection easier
- startIndex = -1;
+ startIndex = -1;
}
}
@@ -360,7 +362,7 @@ public class Geometry extends Spatial {
@Override
protected void setParent(Node parent) {
super.setParent(parent);
-
+
// If the geometry is managed by group node we need to unassociate.
if (parent == null && isGrouped()) {
unassociateFromGroupNode();
@@ -406,7 +408,7 @@ public class Geometry extends Spatial {
* {@link Geometry#getWorldTransform() world transform} of this geometry.
* In order to receive updated values, you must call {@link Geometry#computeWorldMatrix() }
* before using this method.
- *
+ *
* @return Matrix to transform from local space to world space
*/
public Matrix4f getWorldMatrix() {
@@ -418,7 +420,7 @@ public class Geometry extends Spatial {
* This alters the bound used on the mesh as well via
* {@link Mesh#setBound(com.jme3.bounding.BoundingVolume) } and
* forces the world bounding volume to be recomputed.
- *
+ *
* @param modelBound The model bound to set
*/
@Override
@@ -465,15 +467,15 @@ public class Geometry extends Spatial {
}
/**
- * Determine whether this Geometry
is managed by a
+ * Determine whether this Geometry
is managed by a
* {@link GeometryGroupNode} or not.
- *
+ *
* @return True if managed by a {@link GeometryGroupNode}.
*/
public boolean isGrouped() {
return groupNode != null;
}
-
+
/**
* @deprecated Use {@link #isGrouped()} instead.
*/
@@ -491,15 +493,22 @@ public class Geometry extends Spatial {
*/
@Override
public Geometry clone(boolean cloneMaterial) {
+ return (Geometry)super.clone(cloneMaterial);
+ }
+
+ /**
+ * The old clone() method that did not use the new Cloner utility.
+ */
+ public Geometry oldClone(boolean cloneMaterial) {
Geometry geomClone = (Geometry) super.clone(cloneMaterial);
-
+
// This geometry is managed,
// but the cloned one is not attached to anything, hence not managed.
if (geomClone.isGrouped()) {
geomClone.groupNode = null;
geomClone.startIndex = -1;
}
-
+
geomClone.cachedWorldMat = cachedWorldMat.clone();
if (material != null) {
if (cloneMaterial) {
@@ -534,11 +543,58 @@ public class Geometry extends Spatial {
*/
@Override
public Spatial deepClone() {
+ return super.deepClone();
+ }
+
+ public Spatial oldDeepClone() {
Geometry geomClone = clone(true);
geomClone.mesh = mesh.deepClone();
return geomClone;
}
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ super.cloneFields(cloner, original);
+
+ // If this is a grouped node and if our group node is
+ // also cloned then we'll grab it's reference.
+ if( groupNode != null ) {
+ if( cloner.isCloned(groupNode) ) {
+ // Then resolve the reference
+ this.groupNode = cloner.clone(groupNode);
+ } else {
+ // We are on our own now
+ this.groupNode = null;
+ this.startIndex = -1;
+ }
+
+ // The above is based on the fact that if we were
+ // cloning the hierarchy that contained the parent
+ // group then it would have been shallow cloned before
+ // this child. Can't really be otherwise.
+ }
+
+ this.cachedWorldMat = cloner.clone(cachedWorldMat);
+
+ // See if we are doing a shallow clone or a deep mesh clone
+ boolean shallowClone = (cloner.getCloneFunction(Mesh.class) instanceof IdentityCloneFunction);
+
+ // See if we clone the mesh using the special animation
+ // semi-deep cloning
+ if( shallowClone && mesh != null && mesh.getBuffer(Type.BindPosePosition) != null ) {
+ // Then we need to clone the mesh a little deeper
+ this.mesh = mesh.cloneForAnim();
+ } else {
+ // Do whatever the cloner wants to do about it
+ this.mesh = cloner.clone(mesh);
+ }
+
+ this.material = cloner.clone(material);
+ }
+
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
diff --git a/jme3-core/src/main/java/com/jme3/scene/LightNode.java b/jme3-core/src/main/java/com/jme3/scene/LightNode.java
index fea63bf57..a64250c50 100644
--- a/jme3-core/src/main/java/com/jme3/scene/LightNode.java
+++ b/jme3-core/src/main/java/com/jme3/scene/LightNode.java
@@ -36,11 +36,12 @@ import com.jme3.export.JmeImporter;
import com.jme3.light.Light;
import com.jme3.scene.control.LightControl;
import com.jme3.scene.control.LightControl.ControlDirection;
+import com.jme3.util.clone.Cloner;
import java.io.IOException;
/**
* LightNode
is used to link together a {@link Light} object
- * with a {@link Node} object.
+ * with a {@link Node} object.
*
* @author Tim8Dev
*/
@@ -66,7 +67,7 @@ public class LightNode extends Node {
/**
* Enable or disable the LightNode
functionality.
- *
+ *
* @param enabled If false, the functionality of LightNode will
* be disabled.
*/
@@ -93,7 +94,20 @@ public class LightNode extends Node {
public Light getLight() {
return lightControl.getLight();
}
-
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ super.cloneFields(cloner, original);
+
+ // A change in behavior... I think previously LightNode was probably
+ // not really cloneable... or at least its lightControl would be pointing
+ // to the wrong control. -pspeed
+ this.lightControl = cloner.clone(lightControl);
+ }
+
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
diff --git a/jme3-core/src/main/java/com/jme3/scene/Mesh.java b/jme3-core/src/main/java/com/jme3/scene/Mesh.java
index 628af069a..da859a823 100644
--- a/jme3-core/src/main/java/com/jme3/scene/Mesh.java
+++ b/jme3-core/src/main/java/com/jme3/scene/Mesh.java
@@ -37,12 +37,12 @@ import com.jme3.collision.Collidable;
import com.jme3.collision.CollisionResults;
import com.jme3.collision.bih.BIHTree;
import com.jme3.export.*;
+import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.Matrix4f;
import com.jme3.math.Triangle;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
-import com.jme3.renderer.Renderer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
@@ -50,8 +50,9 @@ import com.jme3.scene.mesh.*;
import com.jme3.util.BufferUtils;
import com.jme3.util.IntMap;
import com.jme3.util.IntMap.Entry;
-import com.jme3.util.NativeObject;
import com.jme3.util.SafeArrayList;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.nio.*;
import java.util.ArrayList;
@@ -62,18 +63,18 @@ import java.util.ArrayList;
* All visible elements in a scene are represented by meshes.
* Meshes may contain three types of geometric primitives:
*
- * - Points - Every vertex represents a single point in space,
+ *
- Points - Every vertex represents a single point in space,
* the size of each point is specified via {@link Mesh#setPointSize(float) }.
* Points can also be used for {@link RenderState#setPointSprite(boolean) point
* sprite} mode.
* - Lines - 2 vertices represent a line segment, with the width specified
- * via {@link Mesh#setLineWidth(float) }.
+ * via {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)}.
* - Triangles - 3 vertices represent a solid triangle primitive.
*
- *
+ *
* @author Kirill Vainer
*/
-public class Mesh extends NativeObject implements Savable {
+public class Mesh extends NativeObject implements Savable, Cloneable, JmeCloneable {
/**
* The mode of the Mesh specifies both the type of primitive represented
@@ -81,49 +82,49 @@ public class Mesh extends NativeObject implements Savable {
*/
public enum Mode {
/**
- * A primitive is a single point in space. The size of the points
+ * A primitive is a single point in space. The size of the points
* can be specified with {@link Mesh#setPointSize(float) }.
*/
Points(true),
-
+
/**
* A primitive is a line segment. Every two vertices specify
- * a single line. {@link Mesh#setLineWidth(float) } can be used
+ * a single line. {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} can be used
* to set the width of the lines.
*/
Lines(true),
-
+
/**
* A primitive is a line segment. The first two vertices specify
- * a single line, while subsequent vertices are combined with the
- * previous vertex to make a line. {@link Mesh#setLineWidth(float) } can
+ * a single line, while subsequent vertices are combined with the
+ * previous vertex to make a line. {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} can
* be used to set the width of the lines.
*/
LineStrip(false),
-
+
/**
* Identical to {@link #LineStrip} except that at the end
* the last vertex is connected with the first to form a line.
- * {@link Mesh#setLineWidth(float) } can be used
+ * {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} can be used
* to set the width of the lines.
*/
LineLoop(false),
-
+
/**
* A primitive is a triangle. Each 3 vertices specify a single
* triangle.
*/
Triangles(true),
-
+
/**
- * Similar to {@link #Triangles}, the first 3 vertices
+ * Similar to {@link #Triangles}, the first 3 vertices
* specify a triangle, while subsequent vertices are combined with
- * the previous two to form a triangle.
+ * the previous two to form a triangle.
*/
TriangleStrip(false),
-
+
/**
- * Similar to {@link #Triangles}, the first 3 vertices
+ * Similar to {@link #Triangles}, the first 3 vertices
* specify a triangle, each 2 subsequent vertices are combined
* with the very first vertex to make a triangle.
*/
@@ -136,20 +137,19 @@ public class Mesh extends NativeObject implements Savable {
* for each patch (default is 3 for triangle tesselation)
*/
Patch(true);
-
private boolean listMode = false;
-
+
private Mode(boolean listMode){
this.listMode = listMode;
}
-
+
/**
* Returns true if the specified mode is a list mode (meaning
- * ,it specifies the indices as a linear list and not some special
+ * ,it specifies the indices as a linear list and not some special
* format).
* Will return true for the types {@link #Points}, {@link #Lines} and
* {@link #Triangles}.
- *
+ *
* @return true if the mode is a list type mode
*/
public boolean isListMode(){
@@ -169,7 +169,7 @@ public class Mesh extends NativeObject implements Savable {
private IntMap buffers = new IntMap();
private VertexBuffer[] lodLevels;
private float pointSize = 1;
- private float lineWidth = 1;
+ private float lineWidth = -1;
private transient int vertexArrayID = -1;
@@ -195,7 +195,7 @@ public class Mesh extends NativeObject implements Savable {
* Create a shallow clone of this Mesh. The {@link VertexBuffer vertex
* buffers} are shared between this and the clone mesh, the rest
* of the data is cloned.
- *
+ *
* @return A shallow clone of the mesh
*/
@Override
@@ -209,10 +209,10 @@ public class Mesh extends NativeObject implements Savable {
}
/**
- * Creates a deep clone of this mesh.
+ * Creates a deep clone of this mesh.
* The {@link VertexBuffer vertex buffers} and the data inside them
* is cloned.
- *
+ *
* @return a deep clone of this mesh.
*/
public Mesh deepClone(){
@@ -248,14 +248,14 @@ public class Mesh extends NativeObject implements Savable {
* of the {@link VertexBuffer vertex buffer} data, however the
* {@link Type#Position}, {@link Type#Normal}, and {@link Type#Tangent} buffers
* are deeply cloned.
- *
+ *
* @return A clone of the mesh for animation use.
*/
public Mesh cloneForAnim(){
Mesh clone = clone();
if (getBuffer(Type.BindPosePosition) != null){
VertexBuffer oldPos = getBuffer(Type.Position);
-
+
// NOTE: creates deep clone
VertexBuffer newPos = oldPos.clone();
clone.clearBuffer(Type.Position);
@@ -266,7 +266,7 @@ public class Mesh extends NativeObject implements Savable {
VertexBuffer newNorm = oldNorm.clone();
clone.clearBuffer(Type.Normal);
clone.setBuffer(newNorm);
-
+
if (getBuffer(Type.BindPoseTangent) != null){
VertexBuffer oldTang = getBuffer(Type.Tangent);
VertexBuffer newTang = oldTang.clone();
@@ -278,14 +278,42 @@ public class Mesh extends NativeObject implements Savable {
return clone;
}
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public Mesh jmeClone() {
+ try {
+ Mesh clone = (Mesh)super.clone();
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+
+ // Probably could clone this now but it will get regenerated anyway.
+ this.collisionTree = null;
+
+ this.meshBound = cloner.clone(meshBound);
+ this.buffersList = cloner.clone(buffersList);
+ this.buffers = cloner.clone(buffers);
+ this.lodLevels = cloner.clone(lodLevels);
+ }
+
/**
* Generates the {@link Type#BindPosePosition}, {@link Type#BindPoseNormal},
- * and {@link Type#BindPoseTangent}
+ * and {@link Type#BindPoseTangent}
* buffers for this mesh by duplicating them based on the position and normal
* buffers already set on the mesh.
* This method does nothing if the mesh has no bone weight or index
* buffers.
- *
+ *
* @param forSoftwareAnim Should be true if the bind pose is to be generated.
*/
public void generateBindPose(boolean forSoftwareAnim){
@@ -318,7 +346,7 @@ public class Mesh extends NativeObject implements Savable {
setBuffer(bindNorm);
norm.setUsage(Usage.Stream);
}
-
+
VertexBuffer tangents = getBuffer(Type.Tangent);
if (tangents != null) {
VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent);
@@ -334,8 +362,8 @@ public class Mesh extends NativeObject implements Savable {
/**
* Prepares the mesh for software skinning by converting the bone index
- * and weight buffers to heap buffers.
- *
+ * and weight buffers to heap buffers.
+ *
* @param forSoftwareAnim Should be true to enable the conversion.
*/
public void prepareForAnim(boolean forSoftwareAnim){
@@ -379,7 +407,7 @@ public class Mesh extends NativeObject implements Savable {
}
} else {
//if HWBoneIndex and HWBoneWeight are empty, we setup them as direct
- //buffers with software anim buffers data
+ //buffers with software anim buffers data
VertexBuffer indicesHW = getBuffer(Type.HWBoneIndex);
if (indicesHW.getData() == null) {
VertexBuffer indices = getBuffer(Type.BoneIndex);
@@ -389,7 +417,7 @@ public class Mesh extends NativeObject implements Savable {
directIndex.put(originalIndex);
indicesHW.setupData(Usage.Static, indices.getNumComponents(), indices.getFormat(), directIndex);
}
-
+
VertexBuffer weightsHW = getBuffer(Type.HWBoneWeight);
if (weightsHW.getData() == null) {
VertexBuffer weights = getBuffer(Type.BoneWeight);
@@ -399,26 +427,26 @@ public class Mesh extends NativeObject implements Savable {
directWeight.put(originalWeight);
weightsHW.setupData(Usage.Static, weights.getNumComponents(), weights.getFormat(), directWeight);
}
-
+
// position, normal, and tanget buffers to be in "Static" mode
VertexBuffer positions = getBuffer(Type.Position);
VertexBuffer normals = getBuffer(Type.Normal);
VertexBuffer tangents = getBuffer(Type.Tangent);
-
+
VertexBuffer positionsBP = getBuffer(Type.BindPosePosition);
VertexBuffer normalsBP = getBuffer(Type.BindPoseNormal);
VertexBuffer tangentsBP = getBuffer(Type.BindPoseTangent);
-
+
positions.setUsage(Usage.Static);
positionsBP.copyElements(0, positions, 0, positionsBP.getNumElements());
positions.setUpdateNeeded();
-
+
if (normals != null) {
normals.setUsage(Usage.Static);
normalsBP.copyElements(0, normals, 0, normalsBP.getNumElements());
normals.setUpdateNeeded();
}
-
+
if (tangents != null) {
tangents.setUsage(Usage.Static);
tangentsBP.copyElements(0, tangents, 0, tangentsBP.getNumElements());
@@ -429,7 +457,7 @@ public class Mesh extends NativeObject implements Savable {
/**
* Set the LOD (level of detail) index buffers on this mesh.
- *
+ *
* @param lodLevels The LOD levels to set
*/
public void setLodLevels(VertexBuffer[] lodLevels){
@@ -446,15 +474,15 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the lod level at the given index.
- *
+ *
* @param lod The lod level index, this does not include
* the main index buffer.
* @return The LOD index buffer at the index
- *
- * @throws IndexOutOfBoundsException If the index is outside of the
+ *
+ * @throws IndexOutOfBoundsException If the index is outside of the
* range [0, {@link #getNumLodLevels()}].
- *
- * @see #setLodLevels(com.jme3.scene.VertexBuffer[])
+ *
+ * @see #setLodLevels(com.jme3.scene.VertexBuffer[])
*/
public VertexBuffer getLodLevel(int lod){
return lodLevels[lod];
@@ -462,10 +490,10 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the mesh mode
- *
+ *
* @return the mesh mode
- *
- * @see #setMode(com.jme3.scene.Mesh.Mode)
+ *
+ * @see #setMode(com.jme3.scene.Mesh.Mode)
*/
public Mode getMode() {
return mode;
@@ -473,9 +501,9 @@ public class Mesh extends NativeObject implements Savable {
/**
* Change the Mesh's mode. By default the mode is {@link Mode#Triangles}.
- *
+ *
* @param mode The new mode to set
- *
+ *
* @see Mode
*/
public void setMode(Mode mode) {
@@ -485,10 +513,10 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the maximum number of weights per vertex on this mesh.
- *
+ *
* @return maximum number of weights per vertex
- *
- * @see #setMaxNumWeights(int)
+ *
+ * @see #setMaxNumWeights(int)
*/
public int getMaxNumWeights() {
return maxNumWeights;
@@ -498,8 +526,8 @@ public class Mesh extends NativeObject implements Savable {
* Set the maximum number of weights per vertex on this mesh.
* Only relevant if this mesh has bone index/weight buffers.
* This value should be between 0 and 4.
- *
- * @param maxNumWeights
+ *
+ * @param maxNumWeights
*/
public void setMaxNumWeights(int maxNumWeights) {
this.maxNumWeights = maxNumWeights;
@@ -507,23 +535,23 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the size of points for point meshes
- *
+ *
* @return the size of points
- *
- * @see #setPointSize(float)
+ *
+ * @see #setPointSize(float)
*/
public float getPointSize() {
return pointSize;
}
/**
- * Set the size of points for meshes of mode {@link Mode#Points}.
+ * Set the size of points for meshes of mode {@link Mode#Points}.
* The point size is specified as on-screen pixels, the default
* value is 1.0. The point size
* does nothing if {@link RenderState#setPointSprite(boolean) point sprite}
- * render state is enabled, in that case, the vertex shader must specify the
+ * render state is enabled, in that case, the vertex shader must specify the
* point size by writing to gl_PointSize
.
- *
+ *
* @param pointSize The size of points
*/
public void setPointSize(float pointSize) {
@@ -532,26 +560,30 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the line width for line meshes.
- *
+ *
* @return the line width
+ * @deprecated use {@link Material#getAdditionalRenderState()} and {@link RenderState#getLineWidth()}
*/
+ @Deprecated
public float getLineWidth() {
return lineWidth;
}
/**
* Specify the line width for meshes of the line modes, such
- * as {@link Mode#Lines}. The line width is specified as on-screen pixels,
+ * as {@link Mode#Lines}. The line width is specified as on-screen pixels,
* the default value is 1.0.
- *
+ *
* @param lineWidth The line width
+ * @deprecated use {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)}
*/
+ @Deprecated
public void setLineWidth(float lineWidth) {
this.lineWidth = lineWidth;
}
/**
- * Indicates to the GPU that this mesh will not be modified (a hint).
+ * Indicates to the GPU that this mesh will not be modified (a hint).
* Sets the usage mode to {@link Usage#Static}
* for all {@link VertexBuffer vertex buffers} on this Mesh.
*/
@@ -592,7 +624,7 @@ public class Mesh extends NativeObject implements Savable {
public void setInterleaved(){
ArrayList vbs = new ArrayList();
vbs.addAll(buffersList);
-
+
// ArrayList vbs = new ArrayList(buffers.values());
// index buffer not included when interleaving
vbs.remove(getBuffer(Type.Index));
@@ -611,7 +643,7 @@ public class Mesh extends NativeObject implements Savable {
VertexBuffer allData = new VertexBuffer(Type.InterleavedData);
ByteBuffer dataBuf = BufferUtils.createByteBuffer(stride * getVertexCount());
allData.setupData(Usage.Static, 1, Format.UnsignedByte, dataBuf);
-
+
// adding buffer directly so that no update counts is forced
buffers.put(Type.InterleavedData.ordinal(), allData);
buffersList.add(allData);
@@ -662,7 +694,7 @@ public class Mesh extends NativeObject implements Savable {
for (VertexBuffer vb : vbs){
vb.setOffset(offset);
vb.setStride(stride);
-
+
vb.updateData(null);
//vb.setupData(vb.usage, vb.components, vb.format, null);
offset += vb.componentsLength;
@@ -696,20 +728,20 @@ public class Mesh extends NativeObject implements Savable {
int max = 0;
for( VertexBuffer vb : buffersList ) {
if( vb.getBaseInstanceCount() > max ) {
- max = vb.getBaseInstanceCount();
- }
- }
+ max = vb.getBaseInstanceCount();
+ }
+ }
return max;
}
/**
- * Update the {@link #getVertexCount() vertex} and
+ * Update the {@link #getVertexCount() vertex} and
* {@link #getTriangleCount() triangle} counts for this mesh
* based on the current data. This method should be called
* after the {@link Buffer#capacity() capacities} of the mesh's
* {@link VertexBuffer vertex buffers} has been altered.
- *
- * @throws IllegalStateException If this mesh is in
+ *
+ * @throws IllegalStateException If this mesh is in
* {@link #setInterleaved() interleaved} format.
*/
public void updateCounts(){
@@ -739,7 +771,7 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the triangle count for the given LOD level.
- *
+ *
* @param lod The lod level to look up
* @return The triangle count for that LOD level
*/
@@ -762,10 +794,10 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns how many triangles or elements are on this Mesh.
* This value is only updated when {@link #updateCounts() } is called.
- * If the mesh mode is not a triangle mode, then this returns the
+ * If the mesh mode is not a triangle mode, then this returns the
* number of elements/primitives, e.g. how many lines or how many points,
* instead of how many triangles.
- *
+ *
* @return how many triangles/elements are on this Mesh.
*/
public int getTriangleCount(){
@@ -774,30 +806,30 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the number of vertices on this mesh.
- * The value is computed based on the position buffer, which
+ * The value is computed based on the position buffer, which
* must be set on all meshes.
- *
+ *
* @return Number of vertices on the mesh
*/
public int getVertexCount(){
return vertCount;
}
-
+
/**
* Returns the number of instances this mesh contains. The instance
* count is based on any VertexBuffers with instancing set.
*/
public int getInstanceCount() {
return instanceCount;
- }
+ }
/**
- * Gets the triangle vertex positions at the given triangle index
+ * Gets the triangle vertex positions at the given triangle index
* and stores them into the v1, v2, v3 arguments.
- *
- * @param index The index of the triangle.
+ *
+ * @param index The index of the triangle.
* Should be between 0 and {@link #getTriangleCount()}.
- *
+ *
* @param v1 Vector to contain first vertex position
* @param v2 Vector to contain second vertex position
* @param v3 Vector to contain third vertex position
@@ -822,15 +854,15 @@ public class Mesh extends NativeObject implements Savable {
+ " has incompatible format");
}
}
-
+
/**
- * Gets the triangle vertex positions at the given triangle index
+ * Gets the triangle vertex positions at the given triangle index
* and stores them into the {@link Triangle} argument.
* Also sets the triangle index to the index
argument.
- *
- * @param index The index of the triangle.
+ *
+ * @param index The index of the triangle.
* Should be between 0 and {@link #getTriangleCount()}.
- *
+ *
* @param tri The triangle to store the positions in
*/
public void getTriangle(int index, Triangle tri){
@@ -840,12 +872,12 @@ public class Mesh extends NativeObject implements Savable {
}
/**
- * Gets the triangle vertex indices at the given triangle index
+ * Gets the triangle vertex indices at the given triangle index
* and stores them into the given int array.
- *
- * @param index The index of the triangle.
+ *
+ * @param index The index of the triangle.
* Should be between 0 and {@link #getTriangleCount()}.
- *
+ *
* @param indices Indices of the triangle's vertices
*/
public void getTriangle(int index, int[] indices){
@@ -860,9 +892,9 @@ public class Mesh extends NativeObject implements Savable {
/**
* Generates a collision tree for the mesh.
- * Called automatically by {@link #collideWith(com.jme3.collision.Collidable,
- * com.jme3.math.Matrix4f,
- * com.jme3.bounding.BoundingVolume,
+ * Called automatically by {@link #collideWith(com.jme3.collision.Collidable,
+ * com.jme3.math.Matrix4f,
+ * com.jme3.bounding.BoundingVolume,
* com.jme3.collision.CollisionResults) }.
*/
public void createCollisionData(){
@@ -885,7 +917,7 @@ public class Mesh extends NativeObject implements Savable {
* User code should only use collideWith() on scene
* graph elements such as {@link Spatial}s.
*/
- public int collideWith(Collidable other,
+ public int collideWith(Collidable other,
Matrix4f worldMatrix,
BoundingVolume worldBound,
CollisionResults results){
@@ -893,18 +925,18 @@ public class Mesh extends NativeObject implements Savable {
if (getVertexCount() == 0) {
return 0;
}
-
+
if (collisionTree == null){
createCollisionData();
}
-
+
return collisionTree.collideWith(other, worldMatrix, worldBound, results);
}
/**
* Sets the {@link VertexBuffer} on the mesh.
* This will update the vertex/triangle counts if needed.
- *
+ *
* @param vb The buffer to set
* @throws IllegalArgumentException If the buffer type is already set
*/
@@ -916,12 +948,12 @@ public class Mesh extends NativeObject implements Savable {
buffersList.add(vb);
updateCounts();
}
-
+
/**
* Unsets the {@link VertexBuffer} set on this mesh
- * with the given type. Does nothing if the vertex buffer type is not set
+ * with the given type. Does nothing if the vertex buffer type is not set
* initially.
- *
+ *
* @param type The buffer type to remove
*/
public void clearBuffer(VertexBuffer.Type type){
@@ -931,17 +963,17 @@ public class Mesh extends NativeObject implements Savable {
updateCounts();
}
}
-
+
/**
* Creates a {@link VertexBuffer} for the mesh or modifies
* the existing one per the parameters given.
- *
+ *
* @param type The type of the buffer
* @param components Number of components
* @param format Data format
* @param buf The buffer data
- *
- * @throws UnsupportedOperationException If the buffer already set is
+ *
+ * @throws UnsupportedOperationException If the buffer already set is
* incompatible with the parameters given.
*/
public void setBuffer(Type type, int components, Format format, Buffer buf){
@@ -959,16 +991,16 @@ public class Mesh extends NativeObject implements Savable {
updateCounts();
}
}
-
+
/**
- * Set a floating point {@link VertexBuffer} on the mesh.
- *
- * @param type The type of {@link VertexBuffer},
+ * Set a floating point {@link VertexBuffer} on the mesh.
+ *
+ * @param type The type of {@link VertexBuffer},
* e.g. {@link Type#Position}, {@link Type#Normal}, etc.
- *
+ *
* @param components Number of components on the vertex buffer, should
* be between 1 and 4.
- *
+ *
* @param buf The floating point data to contain
*/
public void setBuffer(Type type, int components, FloatBuffer buf) {
@@ -1006,9 +1038,9 @@ public class Mesh extends NativeObject implements Savable {
/**
* Get the {@link VertexBuffer} stored on this mesh with the given
* type.
- *
+ *
* @param type The type of VertexBuffer
- * @return the VertexBuffer data, or null if not set
+ * @return the VertexBuffer data, or null if not set
*/
public VertexBuffer getBuffer(Type type){
return buffers.get(type.ordinal());
@@ -1017,7 +1049,7 @@ public class Mesh extends NativeObject implements Savable {
/**
* Get the {@link VertexBuffer} data stored on this mesh in float
* format.
- *
+ *
* @param type The type of VertexBuffer
* @return the VertexBuffer data, or null if not set
*/
@@ -1028,11 +1060,11 @@ public class Mesh extends NativeObject implements Savable {
return (FloatBuffer) vb.getData();
}
-
+
/**
* Get the {@link VertexBuffer} data stored on this mesh in short
* format.
- *
+ *
* @param type The type of VertexBuffer
* @return the VertexBuffer data, or null if not set
*/
@@ -1047,40 +1079,40 @@ public class Mesh extends NativeObject implements Savable {
/**
* Acquires an index buffer that will read the vertices on the mesh
* as a list.
- *
+ *
* @return A virtual or wrapped index buffer to read the data as a list
*/
public IndexBuffer getIndicesAsList(){
IndexBuffer ib = getIndexBuffer();
- if (ib != null) {
- if (mode.isListMode()) {
+ if (ib != null){
+ if (mode.isListMode()){
// already in list mode
return ib;
- } else {
+ }else{
// not in list mode but it does have an index buffer
// wrap it so the data is converted to list format
return new WrappedIndexBuffer(this);
}
- } else {
+ }else{
// return a virtual index buffer that will supply
// "fake" indices in list format
return new VirtualIndexBuffer(vertCount, mode);
}
}
-
+
/**
- * Get the index buffer for this mesh.
+ * Get the index buffer for this mesh.
* Will return null
if no index buffer is set.
- *
+ *
* @return The index buffer of this mesh.
- *
+ *
* @see Type#Index
*/
public IndexBuffer getIndexBuffer() {
VertexBuffer vb = getBuffer(Type.Index);
if (vb == null)
return null;
-
+
return IndexBuffer.wrapIndexBuffer(vb.getData());
}
@@ -1090,7 +1122,7 @@ public class Mesh extends NativeObject implements Savable {
* to index into the attributes of the other mesh.
* Note that this will also change this mesh's index buffer so that
* the references to the vertex data match the new indices.
- *
+ *
* @param other The mesh to extract the vertex data from
*/
public void extractVertexData(Mesh other) {
@@ -1110,7 +1142,7 @@ public class Mesh extends NativeObject implements Savable {
int oldIndex = indexBuf.get(i);
if (!oldIndicesToNewIndices.containsKey(oldIndex)) {
- // this vertex has not been added, so allocate a
+ // this vertex has not been added, so allocate a
// new index for it and add it to the map
oldIndicesToNewIndices.put(oldIndex, newIndex);
newIndicesToOldIndices.add(oldIndex);
@@ -1127,8 +1159,8 @@ public class Mesh extends NativeObject implements Savable {
throw new AssertionError();
}
- // Create the new index buffer.
- // Do not overwrite the old one because we might be able to
+ // Create the new index buffer.
+ // Do not overwrite the old one because we might be able to
// convert from int index buffer to short index buffer
IndexBuffer newIndexBuf;
if (newNumVerts >= 65536) {
@@ -1144,10 +1176,10 @@ public class Mesh extends NativeObject implements Savable {
newIndexBuf.put(i, newIndex);
}
-
+
VertexBuffer newIdxBuf = new VertexBuffer(Type.Index);
- newIdxBuf.setupData(oldIdxBuf.getUsage(),
- oldIdxBuf.getNumComponents(),
+ newIdxBuf.setupData(oldIdxBuf.getUsage(),
+ oldIdxBuf.getNumComponents(),
newIndexBuf instanceof IndexIntBuffer ? Format.UnsignedInt : Format.UnsignedShort,
newIndexBuf.getBuffer());
clearBuffer(Type.Index);
@@ -1163,7 +1195,7 @@ public class Mesh extends NativeObject implements Savable {
VertexBuffer newVb = new VertexBuffer(oldVb.getBufferType());
newVb.setNormalized(oldVb.isNormalized());
- //check for data before copying, some buffers are just empty shells
+ //check for data before copying, some buffers are just empty shells
//for caching purpose (HW skinning buffers), and will be filled when
//needed
if(oldVb.getData()!=null){
@@ -1181,32 +1213,32 @@ public class Mesh extends NativeObject implements Savable {
oldVb.copyElement(oldIndex, newVb, i);
}
}
-
+
// Set the buffer on the mesh
clearBuffer(newVb.getBufferType());
setBuffer(newVb);
}
-
+
// Copy max weights per vertex as well
setMaxNumWeights(other.getMaxNumWeights());
-
+
// The data has been copied over, update informations
updateCounts();
updateBound();
}
-
+
/**
* Scales the texture coordinate buffer on this mesh by the given
- * scale factor.
+ * scale factor.
*
- * Note that values above 1 will cause the
- * texture to tile, while values below 1 will cause the texture
+ * Note that values above 1 will cause the
+ * texture to tile, while values below 1 will cause the texture
* to stretch.
*
- *
+ *
* @param scaleFactor The scale factor to scale by. Every texture
* coordinate is multiplied by this vector to get the result.
- *
+ *
* @throws IllegalStateException If there's no texture coordinate
* buffer on the mesh
* @throws UnsupportedOperationException If the texture coordinate
@@ -1238,7 +1270,7 @@ public class Mesh extends NativeObject implements Savable {
}
/**
- * Updates the bounding volume of this mesh.
+ * Updates the bounding volume of this mesh.
* The method does nothing if the mesh has no {@link Type#Position} buffer.
* It is expected that the position buffer is a float buffer with 3 components.
*/
@@ -1252,7 +1284,7 @@ public class Mesh extends NativeObject implements Savable {
/**
* Returns the {@link BoundingVolume} of this Mesh.
* By default the bounding volume is a {@link BoundingBox}.
- *
+ *
* @return the bounding volume of this mesh
*/
public BoundingVolume getBound() {
@@ -1262,7 +1294,7 @@ public class Mesh extends NativeObject implements Savable {
/**
* Sets the {@link BoundingVolume} for this Mesh.
* The bounding volume is recomputed by calling {@link #updateBound() }.
- *
+ *
* @param modelBound The model bound to set
*/
public void setBound(BoundingVolume modelBound) {
@@ -1273,38 +1305,38 @@ public class Mesh extends NativeObject implements Savable {
* Returns a map of all {@link VertexBuffer vertex buffers} on this Mesh.
* The integer key for the map is the {@link Enum#ordinal() ordinal}
* of the vertex buffer's {@link Type}.
- * Note that the returned map is a reference to the map used internally,
+ * Note that the returned map is a reference to the map used internally,
* modifying it will cause undefined results.
- *
+ *
* @return map of vertex buffers on this mesh.
*/
public IntMap getBuffers(){
return buffers;
}
-
+
/**
* Returns a list of all {@link VertexBuffer vertex buffers} on this Mesh.
* Using a list instead an IntMap via the {@link #getBuffers() } method is
* better for iteration as there's no need to create an iterator instance.
* Note that the returned list is a reference to the list used internally,
* modifying it will cause undefined results.
- *
+ *
* @return list of vertex buffers on this mesh.
*/
public SafeArrayList getBufferList(){
return buffersList;
}
-
+
/**
* Determines if the mesh uses bone animation.
- *
+ *
* A mesh uses bone animation if it has bone index / weight buffers
* such as {@link Type#BoneIndex} or {@link Type#HWBoneIndex}.
- *
+ *
* @return true if the mesh uses bone animation, false otherwise
*/
public boolean isAnimated() {
- return getBuffer(Type.BoneIndex) != null ||
+ return getBuffer(Type.BoneIndex) != null ||
getBuffer(Type.HWBoneIndex) != null;
}
@@ -1399,7 +1431,7 @@ public class Mesh extends NativeObject implements Savable {
}
//creating hw animation buffers empty so that they are put in the cache
- if (isAnimated()) {
+ if(isAnimated()){
VertexBuffer hwBoneIndex = new VertexBuffer(Type.HWBoneIndex);
hwBoneIndex.setUsage(Usage.CpuOnly);
setBuffer(hwBoneIndex);
diff --git a/jme3-core/src/main/java/com/jme3/scene/Node.java b/jme3-core/src/main/java/com/jme3/scene/Node.java
index e37984e62..01298441b 100644
--- a/jme3-core/src/main/java/com/jme3/scene/Node.java
+++ b/jme3-core/src/main/java/com/jme3/scene/Node.java
@@ -40,6 +40,7 @@ import com.jme3.export.Savable;
import com.jme3.material.Material;
import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -53,7 +54,7 @@ import java.util.logging.Logger;
* node maintains a collection of children and handles merging said children
* into a single bound to allow for very fast culling of multiple nodes. Node
* allows for any number of children to be attached.
- *
+ *
* @author Mark Powell
* @author Gregg Patton
* @author Joshua Slack
@@ -62,26 +63,26 @@ public class Node extends Spatial {
private static final Logger logger = Logger.getLogger(Node.class.getName());
- /**
+ /**
* This node's children.
*/
protected SafeArrayList children = new SafeArrayList(Spatial.class);
/**
* If this node is a root, this list will contain the current
- * set of children (and children of children) that require
+ * set of children (and children of children) that require
* updateLogicalState() to be called as indicated by their
* requiresUpdate() method.
*/
private SafeArrayList updateList = null;
-
+
/**
* False if the update list requires rebuilding. This is Node.class
* specific and therefore not included as part of the Spatial update flags.
* A flag is used instead of nulling the updateList to avoid reallocating
* a whole list every time the scene graph changes.
- */
- private boolean updateListValid = false;
+ */
+ private boolean updateListValid = false;
/**
* Serialization only. Do not use.
@@ -93,29 +94,29 @@ public class Node extends Spatial {
/**
* Constructor instantiates a new Node
with a default empty
* list for containing children.
- *
+ *
* @param name the name of the scene element. This is required for
* identification and comparison purposes.
*/
public Node(String name) {
super(name);
-
+
// For backwards compatibility, only clear the "requires
// update" flag if we are not a subclass of Node.
// This prevents subclass from silently failing to receive
// updates when they upgrade.
- setRequiresUpdates(Node.class != getClass());
+ setRequiresUpdates(Node.class != getClass());
}
/**
- *
+ *
* getQuantity
returns the number of children this node
* maintains.
- *
+ *
* @return the number of children this node maintains.
*/
public int getQuantity() {
- return children.size();
+ return children.size();
}
@Override
@@ -155,7 +156,7 @@ public class Node extends Spatial {
@Override
protected void updateWorldBound(){
super.updateWorldBound();
-
+
// for a node, the world bound is a combination of all it's children
// bounds
BoundingVolume resultBound = null;
@@ -179,7 +180,7 @@ public class Node extends Spatial {
protected void setParent(Node parent) {
if( this.parent == null && parent != null ) {
// We were a root before and now we aren't... make sure if
- // we had an updateList then we clear it completely to
+ // we had an updateList then we clear it completely to
// avoid holding the dead array.
updateList = null;
updateListValid = false;
@@ -216,15 +217,15 @@ public class Node extends Spatial {
return updateList;
}
if( updateList == null ) {
- updateList = new SafeArrayList(Spatial.class);
+ updateList = new SafeArrayList(Spatial.class);
} else {
updateList.clear();
}
// Build the list
addUpdateChildren(updateList);
- updateListValid = true;
- return updateList;
+ updateListValid = true;
+ return updateList;
}
@Override
@@ -250,7 +251,7 @@ public class Node extends Spatial {
// This branch has no geometric state that requires updates.
return;
}
-
+
if ((refreshFlags & RF_LIGHTLIST) != 0){
updateWorldLightList();
}
@@ -266,7 +267,7 @@ public class Node extends Spatial {
}
refreshFlags &= ~RF_CHILD_LIGHTLIST;
-
+
if (!children.isEmpty()) {
// the important part- make sure child geometric state is refreshed
// first before updating own world bound. This saves
@@ -276,7 +277,7 @@ public class Node extends Spatial {
for (Spatial child : children.getArray()) {
child.updateGeometricState();
}
- }
+ }
if ((refreshFlags & RF_BOUND) != 0){
updateWorldBound();
@@ -288,7 +289,7 @@ public class Node extends Spatial {
/**
* getTriangleCount
returns the number of triangles contained
* in all sub-branches of this node that contain geometry.
- *
+ *
* @return the triangle count of this branch.
*/
@Override
@@ -302,11 +303,11 @@ public class Node extends Spatial {
return count;
}
-
+
/**
* getVertexCount
returns the number of vertices contained
* in all sub-branches of this node that contain geometry.
- *
+ *
* @return the vertex count of this branch.
*/
@Override
@@ -327,7 +328,7 @@ public class Node extends Spatial {
* returned.
*
* If the child already had a parent it is detached from that former parent.
- *
+ *
* @param child
* the child to attach to this node.
* @return the number of children maintained by this node.
@@ -336,15 +337,15 @@ public class Node extends Spatial {
public int attachChild(Spatial child) {
return attachChildAt(child, children.size());
}
-
+
/**
- *
+ *
* attachChildAt
attaches a child to this node at an index. This node
* becomes the child's parent. The current number of children maintained is
* returned.
*
* If the child already had a parent it is detached from that former parent.
- *
+ *
* @param child
* the child to attach to this node.
* @return the number of children maintained by this node.
@@ -360,28 +361,27 @@ public class Node extends Spatial {
}
child.setParent(this);
children.add(index, child);
-
+
// XXX: Not entirely correct? Forces bound update up the
// tree stemming from the attached child. Also forces
// transform update down the tree-
child.setTransformRefresh();
child.setLightListRefresh();
- child.setMatParamOverrideRefresh();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,"Child ({0}) attached to this node ({1})",
new Object[]{child.getName(), getName()});
}
-
+
invalidateUpdateList();
}
-
+
return children.size();
}
/**
* detachChild
removes a given child from the node's list.
* This child will no longer be maintained.
- *
+ *
* @param child
* the child to remove.
* @return the index the child was at. -1 if the child was not in the list.
@@ -396,16 +396,16 @@ public class Node extends Spatial {
detachChildAt(index);
}
return index;
- }
-
- return -1;
+ }
+
+ return -1;
}
/**
* detachChild
removes a given child from the node's list.
* This child will no longe be maintained. Only the first child with a
* matching name is removed.
- *
+ *
* @param childName
* the child to remove.
* @return the index the child was at. -1 if the child was not in the list.
@@ -425,10 +425,10 @@ public class Node extends Spatial {
}
/**
- *
+ *
* detachChildAt
removes a child at a given index. That child
* is returned for saving purposes.
- *
+ *
* @param index
* the index of the child to be removed.
* @return the child at the supplied index.
@@ -457,7 +457,7 @@ public class Node extends Spatial {
}
/**
- *
+ *
* detachAllChildren
removes all children attached to this
* node.
*/
@@ -476,7 +476,7 @@ public class Node extends Spatial {
* in this node's list of children.
* @param sp
* The spatial to look up
- * @return
+ * @return
* The index of the spatial in the node's children, or -1
* if the spatial is not attached to this node
*/
@@ -486,7 +486,7 @@ public class Node extends Spatial {
/**
* More efficient than e.g detaching and attaching as no updates are needed.
- *
+ *
* @param index1 The index of the first child to swap
* @param index2 The index of the second child to swap
*/
@@ -499,9 +499,9 @@ public class Node extends Spatial {
}
/**
- *
+ *
* getChild
returns a child at a given index.
- *
+ *
* @param i
* the index to retrieve the child from.
* @return the child at a specified index.
@@ -515,13 +515,13 @@ public class Node extends Spatial {
* given name (case sensitive.) This method does a depth first recursive
* search of all descendants of this node, it will return the first spatial
* found with a matching name.
- *
+ *
* @param name
* the name of the child to retrieve. If null, we'll return null.
* @return the child if found, or null.
*/
public Spatial getChild(String name) {
- if (name == null)
+ if (name == null)
return null;
for (Spatial child : children.getArray()) {
@@ -536,11 +536,11 @@ public class Node extends Spatial {
}
return null;
}
-
+
/**
* determines if the provided Spatial is contained in the children list of
* this node.
- *
+ *
* @param spat
* the child object to look for.
* @return true if the object is contained, false otherwise.
@@ -584,39 +584,39 @@ public class Node extends Spatial {
public int collideWith(Collidable other, CollisionResults results){
int total = 0;
-
+
// optimization: try collideWith BoundingVolume to avoid possibly redundant tests on children
- // number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all.
+ // number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all.
// The idea is when there are few children, it can be too expensive to test boundingVolume first.
/*
I'm removing this change until some issues can be addressed and I really
think it needs to be implemented a better way anyway.
-
+
First, it causes issues for anyone doing collideWith() with BoundingVolumes
and expecting it to trickle down to the children. For example, children
with BoundingSphere bounding volumes and collideWith(BoundingSphere). Doing
a collision check at the parent level then has to do a BoundingSphere to BoundingBox
collision which isn't resolved. (Having to come up with a collision point in that
case is tricky and the first sign that this is the wrong approach.)
-
+
Second, the rippling changes this caused to 'optimize' collideWith() for this
special use-case are another sign that this approach was a bit dodgy. The whole
idea of calculating a full collision just to see if the two shapes collide at all
is very wasteful.
-
+
A proper implementation should support a simpler boolean check that doesn't do
all of that calculation. For example, if 'other' is also a BoundingVolume (ie: 99.9%
of all non-Ray cases) then a direct BV to BV intersects() test can be done. So much
faster. And if 'other' _is_ a Ray then the BV.intersects(Ray) call can be done.
-
+
I don't have time to do it right now but I'll at least un-break a bunch of peoples'
code until it can be 'optimized' properly. Hopefully it's not too late to back out
- the other dodgy ripples this caused. -pspeed (hindsight-expert ;))
-
+ the other dodgy ripples this caused. -pspeed (hindsight-expert ;))
+
Note: the code itself is relatively simple to implement but I don't have time to
a) test it, and b) see if '> 4' is still a decent check for it. Could be it's fast
enough to do all the time for > 1.
-
+
if (children.size() > 4)
{
BoundingVolume bv = this.getWorldBound();
@@ -660,7 +660,7 @@ public class Node extends Spatial {
* @return Non-null, but possibly 0-element, list of matching Spatials (also Instances extending Spatials).
*
* @see java.util.regex.Pattern
- * @see Spatial#matches(java.lang.Class, java.lang.String)
+ * @see Spatial#matches(java.lang.Class, java.lang.String)
*/
@SuppressWarnings("unchecked")
public List descendantMatches(
@@ -680,7 +680,7 @@ public class Node extends Spatial {
/**
* Convenience wrapper.
*
- * @see #descendantMatches(java.lang.Class, java.lang.String)
+ * @see #descendantMatches(java.lang.Class, java.lang.String)
*/
public List descendantMatches(
Class spatialSubclass) {
@@ -690,7 +690,7 @@ public class Node extends Spatial {
/**
* Convenience wrapper.
*
- * @see #descendantMatches(java.lang.Class, java.lang.String)
+ * @see #descendantMatches(java.lang.Class, java.lang.String)
*/
public List descendantMatches(String nameRegex) {
return descendantMatches(null, nameRegex);
@@ -709,12 +709,22 @@ public class Node extends Spatial {
// Reset the fields of the clone that should be in a 'new' state.
nodeClone.updateList = null;
nodeClone.updateListValid = false; // safe because parent is nulled out in super.clone()
-
+
return nodeClone;
}
@Override
- public Spatial deepClone(){
+ public Spatial deepClone() {
+ Node nodeClone = (Node)super.deepClone();
+
+ // Reset the fields of the clone that should be in a 'new' state.
+ nodeClone.updateList = null;
+ nodeClone.updateListValid = false; // safe because parent is nulled out in super.clone()
+
+ return nodeClone;
+ }
+
+ public Spatial oldDeepClone(){
Node nodeClone = (Node) super.clone();
nodeClone.children = new SafeArrayList(Spatial.class);
for (Spatial child : children){
@@ -725,6 +735,21 @@ public class Node extends Spatial {
return nodeClone;
}
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ super.cloneFields(cloner, original);
+
+ this.children = cloner.clone(children);
+
+ // Only the outer cloning thing knows whether this should be nulled
+ // or not... after all, we might be cloning a root node in which case
+ // cloning this list is fine.
+ this.updateList = cloner.clone(updateList);
+ }
+
@Override
public void write(JmeExporter e) throws IOException {
super.write(e);
@@ -736,8 +761,8 @@ public class Node extends Spatial {
// XXX: Load children before loading itself!!
// This prevents empty children list if controls query
// it in Control.setSpatial().
-
- children = new SafeArrayList( Spatial.class,
+
+ children = new SafeArrayList( Spatial.class,
e.getCapsule(this).readSavableArrayList("children", null) );
// go through children and set parent to this node
@@ -746,7 +771,7 @@ public class Node extends Spatial {
child.parent = this;
}
}
-
+
super.read(e);
}
@@ -767,7 +792,7 @@ public class Node extends Spatial {
}
}
}
-
+
@Override
public void depthFirstTraversal(SceneGraphVisitor visitor) {
for (Spatial child : children.getArray()) {
@@ -775,7 +800,7 @@ public class Node extends Spatial {
}
visitor.visit(this);
}
-
+
@Override
protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue queue) {
queue.addAll(children);
diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java
index c2246a141..17646adf5 100644
--- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java
+++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java
@@ -48,6 +48,9 @@ import com.jme3.renderer.queue.RenderQueue;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.IdentityCloneFunction;
+import com.jme3.util.clone.JmeCloneable;
import com.jme3.util.SafeArrayList;
import com.jme3.util.TempVars;
import java.io.IOException;
@@ -64,17 +67,17 @@ import java.util.logging.Logger;
* @author Joshua Slack
* @version $Revision: 4075 $, $Data$
*/
-public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset {
+public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, JmeCloneable {
private static final Logger logger = Logger.getLogger(Spatial.class.getName());
/**
- * Specifies how frustum culling should be handled by
+ * Specifies how frustum culling should be handled by
* this spatial.
*/
public enum CullHint {
- /**
+ /**
* Do whatever our parent does. If no parent, default to {@link #Dynamic}.
*/
Inherit,
@@ -84,13 +87,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* Camera planes whether or not this Spatial should be culled.
*/
Dynamic,
- /**
+ /**
* Always cull this from the view, throwing away this object
* and any children from rendering commands.
*/
Always,
/**
- * Never cull this from view, always draw it.
+ * Never cull this from view, always draw it.
* Note that we will still get culled if our parent is culled.
*/
Never;
@@ -101,15 +104,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
*/
public enum BatchHint {
- /**
+ /**
* Do whatever our parent does. If no parent, default to {@link #Always}.
*/
Inherit,
- /**
+ /**
* This spatial will always be batched when attached to a BatchNode.
*/
Always,
- /**
+ /**
* This spatial will never be batched when attached to a BatchNode.
*/
Never;
@@ -119,13 +122,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
*/
protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
RF_BOUND = 0x02,
- RF_LIGHTLIST = 0x04, // changes in light lists
+ RF_LIGHTLIST = 0x04, // changes in light lists
RF_CHILD_LIGHTLIST = 0x08, // some child need geometry update
RF_MATPARAM_OVERRIDE = 0x10;
protected CullHint cullHint = CullHint.Inherit;
protected BatchHint batchHint = BatchHint.Inherit;
- /**
+ /**
* Spatial's bounding volume relative to the world.
*/
protected BoundingVolume worldBound;
@@ -138,7 +141,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
protected ArrayList localOverrides;
protected ArrayList worldOverrides;
- /**
+ /**
* This spatial's name.
*/
protected String name;
@@ -153,11 +156,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
protected HashMap userData = null;
/**
* Used for smart asset caching
- *
- * @see AssetKey#useSmartCache()
+ *
+ * @see AssetKey#useSmartCache()
*/
protected AssetKey key;
- /**
+ /**
* Spatial's parent, or null if it has none.
*/
protected transient Node parent;
@@ -180,7 +183,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
/**
* Serialization only. Do not use.
* Not really. This class is never instantiated directly but the
- * subclasses like to use the no-arg constructor for their own
+ * subclasses like to use the no-arg constructor for their own
* no-arg constructor... which is technically weaker than
* forward supplying defaults.
*/
@@ -198,7 +201,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
*/
protected Spatial(String name) {
this.name = name;
-
+
localTransform = new Transform();
worldTransform = new Transform();
@@ -228,13 +231,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
boolean requiresUpdates() {
return requiresUpdates | !controls.isEmpty();
}
-
+
/**
- * Subclasses can call this with true to denote that they require
+ * Subclasses can call this with true to denote that they require
* updateLogicalState() to be called even if they contain no controls.
* Setting this to false reverts to the default behavior of only
* updating if the spatial has controls. This is not meant to
- * indicate dynamic state in any way and must be called while
+ * indicate dynamic state in any way and must be called while
* unattached or an IllegalStateException is thrown. It is designed
* to be called during object construction and then never changed, ie:
* it's meant to be subclass specific state and not runtime state.
@@ -260,12 +263,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
// override it for more optimal behavior. Node and Geometry will override
// it to false if the class is Node.class or Geometry.class.
// This means that all subclasses will default to the old behavior
- // unless they opt in.
+ // unless they opt in.
if( parent != null ) {
- throw new IllegalStateException("setRequiresUpdates() cannot be called once attached.");
+ throw new IllegalStateException("setRequiresUpdates() cannot be called once attached.");
}
this.requiresUpdates = f;
- }
+ }
/**
* Indicate that the transform of this spatial has changed and that
@@ -278,17 +281,17 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
protected void setLightListRefresh() {
refreshFlags |= RF_LIGHTLIST;
-
+
// Make sure next updateGeometricState() visits this branch
// to update lights.
Spatial p = parent;
- while (p != null) {
+ while (p != null) {
if ((p.refreshFlags & RF_CHILD_LIGHTLIST) != 0) {
// The parent already has this flag,
// so must all ancestors.
return;
}
-
+
p.refreshFlags |= RF_CHILD_LIGHTLIST;
p = p.parent;
}
@@ -324,10 +327,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
p = p.parent;
}
}
-
+
/**
* (Internal use only) Forces a refresh of the given types of data.
- *
+ *
* @param transforms Refresh world transform based on parents'
* @param bounds Refresh bounding volume data based on child nodes
* @param lights Refresh light list based on parents'
@@ -410,9 +413,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
/**
* Returns the local {@link LightList}, which are the lights
* that were directly attached to this Spatial
through the
- * {@link #addLight(com.jme3.light.Light) } and
+ * {@link #addLight(com.jme3.light.Light) } and
* {@link #removeLight(com.jme3.light.Light) } methods.
- *
+ *
* @return The local light list
*/
public LightList getLocalLightList() {
@@ -423,7 +426,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* Returns the world {@link LightList}, containing the lights
* combined from all this Spatial's
parents up to and including
* this Spatial
's lights.
- *
+ *
* @return The combined world light list
*/
public LightList getWorldLightList() {
@@ -534,14 +537,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* lookAt
is a convenience method for auto-setting the local
* rotation based on a position in world space and an up vector. It computes the rotation
* to transform the z-axis to point onto 'position' and the y-axis to 'up'.
- * Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) }
+ * Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) }
* this method takes a world position to look at and not a relative direction.
*
* Note : 28/01/2013 this method has been fixed as it was not taking into account the parent rotation.
* This was resulting in improper rotation when the spatial had rotated parent nodes.
- * This method is intended to work in world space, so no matter what parent graph the
+ * This method is intended to work in world space, so no matter what parent graph the
* spatial has, it will look at the given position in world space.
- *
+ *
* @param position
* where to look at in terms of world coordinates
* @param upVector
@@ -554,10 +557,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
TempVars vars = TempVars.get();
Vector3f compVecA = vars.vect4;
-
+
compVecA.set(position).subtractLocal(worldTranslation);
- getLocalRotation().lookAt(compVecA, upVector);
-
+ getLocalRotation().lookAt(compVecA, upVector);
+
if ( getParent() != null ) {
Quaternion rot=vars.quat1;
rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation());
@@ -750,7 +753,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* @param vp The ViewPort to which the Spatial is being rendered to.
*
* @see Spatial#addControl(com.jme3.scene.control.Control)
- * @see Spatial#getControl(java.lang.Class)
+ * @see Spatial#getControl(java.lang.Class)
*/
public void runControlRender(RenderManager rm, ViewPort vp) {
if (controls.isEmpty()) {
@@ -766,26 +769,26 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* Add a control to the list of controls.
* @param control The control to add.
*
- * @see Spatial#removeControl(java.lang.Class)
+ * @see Spatial#removeControl(java.lang.Class)
*/
public void addControl(Control control) {
boolean before = requiresUpdates();
controls.add(control);
control.setSpatial(this);
boolean after = requiresUpdates();
-
+
// If the requirement to be updated has changed
// then we need to let the parent node know so it
// can rebuild its update list.
if( parent != null && before != after ) {
- parent.invalidateUpdateList();
+ parent.invalidateUpdateList();
}
}
/**
* Removes the first control that is an instance of the given class.
*
- * @see Spatial#addControl(com.jme3.scene.control.Control)
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
*/
public void removeControl(Class extends Control> controlType) {
boolean before = requiresUpdates();
@@ -797,23 +800,23 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
}
}
boolean after = requiresUpdates();
-
+
// If the requirement to be updated has changed
// then we need to let the parent node know so it
// can rebuild its update list.
if( parent != null && before != after ) {
- parent.invalidateUpdateList();
+ parent.invalidateUpdateList();
}
}
/**
* Removes the given control from this spatial's controls.
- *
+ *
* @param control The control to remove
* @return True if the control was successfully removed. False if the
* control is not assigned to this spatial.
*
- * @see Spatial#addControl(com.jme3.scene.control.Control)
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
*/
public boolean removeControl(Control control) {
boolean before = requiresUpdates();
@@ -823,14 +826,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
}
boolean after = requiresUpdates();
-
+
// If the requirement to be updated has changed
// then we need to let the parent node know so it
// can rebuild its update list.
if( parent != null && before != after ) {
- parent.invalidateUpdateList();
+ parent.invalidateUpdateList();
}
-
+
return result;
}
@@ -841,7 +844,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* @param controlType The superclass of the control to look for.
* @return The first instance in the list of the controlType class, or null.
*
- * @see Spatial#addControl(com.jme3.scene.control.Control)
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
*/
public T getControl(Class controlType) {
for (Control c : controls.getArray()) {
@@ -870,7 +873,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
/**
* @return The number of controls attached to this Spatial.
* @see Spatial#addControl(com.jme3.scene.control.Control)
- * @see Spatial#removeControl(java.lang.Class)
+ * @see Spatial#removeControl(java.lang.Class)
*/
public int getNumControls() {
return controls.size();
@@ -895,7 +898,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* Calling this when the Spatial is attached to a node
* will cause undefined results. User code should only call this
* method on Spatials having no parent.
- *
+ *
* @see Spatial#getWorldLightList()
* @see Spatial#getWorldTransform()
* @see Spatial#getWorldBound()
@@ -1150,9 +1153,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
/**
* removeLight
removes the given light from the Spatial.
- *
+ *
* @param light The light to remove.
- * @see Spatial#addLight(com.jme3.light.Light)
+ * @see Spatial#addLight(com.jme3.light.Light)
*/
public void removeLight(Light light) {
localLights.remove(light);
@@ -1344,12 +1347,43 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* Note that meshes of geometries are not cloned explicitly, they
* are shared if static, or specially cloned if animated.
*
- * All controls will be cloned using the Control.cloneForSpatial method
- * on the clone.
- *
- * @see Mesh#cloneForAnim()
+ * @see Mesh#cloneForAnim()
+ */
+ public Spatial clone( boolean cloneMaterial ) {
+
+ // Setup the cloner for the type of cloning we want to do.
+ Cloner cloner = new Cloner();
+
+ // First, we definitely do not want to clone our own parent
+ cloner.setClonedValue(parent, null);
+
+ // If we aren't cloning materials then we will make sure those
+ // aren't cloned also
+ if( !cloneMaterial ) {
+ cloner.setCloneFunction(Material.class, new IdentityCloneFunction());
+ }
+
+ // By default the meshes are not cloned. The geometry
+ // may choose to selectively force them to be cloned but
+ // normally they will be shared
+ cloner.setCloneFunction(Mesh.class, new IdentityCloneFunction());
+
+ // Clone it!
+ Spatial clone = cloner.clone(this);
+
+ // Because we've nulled the parent out we need to make sure
+ // the transforms and stuff get refreshed.
+ clone.setTransformRefresh();
+ clone.setLightListRefresh();
+ clone.setMatParamOverrideRefresh();
+
+ return clone;
+ }
+
+ /**
+ * The old clone() method that did not use the new Cloner utility.
*/
- public Spatial clone(boolean cloneMaterial) {
+ public Spatial oldClone(boolean cloneMaterial) {
try {
Spatial clone = (Spatial) super.clone();
if (worldBound != null) {
@@ -1419,7 +1453,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
* All controls will be cloned using the Control.cloneForSpatial method
* on the clone.
*
- * @see Mesh#cloneForAnim()
+ * @see Mesh#cloneForAnim()
*/
@Override
public Spatial clone() {
@@ -1433,7 +1467,73 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
*
* @see Spatial#clone()
*/
- public abstract Spatial deepClone();
+ public Spatial deepClone() {
+ // Setup the cloner for the type of cloning we want to do.
+ Cloner cloner = new Cloner();
+
+ // First, we definitely do not want to clone our own parent
+ cloner.setClonedValue(parent, null);
+
+ Spatial clone = cloner.clone(this);
+
+ // Because we've nulled the parent out we need to make sure
+ // the transforms and stuff get refreshed.
+ clone.setTransformRefresh();
+ clone.setLightListRefresh();
+ clone.setMatParamOverrideRefresh();
+
+ return clone;
+ }
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public Spatial jmeClone() {
+ try {
+ Spatial clone = (Spatial)super.clone();
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+
+ // Clone all of the fields that need fix-ups and/or potential
+ // sharing.
+ this.parent = cloner.clone(parent);
+ this.worldBound = cloner.clone(worldBound);
+ this.worldLights = cloner.clone(worldLights);
+ this.localLights = cloner.clone(localLights);
+ this.worldTransform = cloner.clone(worldTransform);
+ this.localTransform = cloner.clone(localTransform);
+ clone.worldOverrides = cloner.clone(worldOverrides);
+ clone.localOverrides = cloner.clone(localOverrides);
+ this.controls = cloner.clone(controls);
+
+ // Cloner doesn't handle maps on its own just yet.
+ // Note: this is more advanced cloning than the old clone() method
+ // did because it just shallow cloned the map. In this case, we want
+ // to avoid all of the nasty cloneForSpatial() fixup style code that
+ // used to inject stuff into the clone's user data. By using cloner
+ // to clone the user data we get this automatically.
+ if( userData != null ) {
+ userData = (HashMap)userData.clone();
+ for( Map.Entry e : userData.entrySet() ) {
+ Savable value = e.getValue();
+ if( value instanceof Cloneable ) {
+ // Note: all JmeCloneable objects are also Cloneable so this
+ // catches both cases.
+ e.setValue(cloner.clone(value));
+ }
+ }
+ }
+ }
public void setUserData(String key, Object data) {
if (userData == null) {
@@ -1441,7 +1541,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
}
if(data == null){
- userData.remove(key);
+ userData.remove(key);
}else if (data instanceof Savable) {
userData.put(key, (Savable) data);
} else {
@@ -1543,7 +1643,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
//changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
//the AnimControl creates the SkeletonControl for old files and add it to the spatial.
//The SkeletonControl must be the last in the stack so we add the list of all other control before it.
- //When backward compatibility won't be needed anymore this can be replaced by :
+ //When backward compatibility won't be needed anymore this can be replaced by :
//controls = ic.readSavableArrayList("controlsList", null));
controls.addAll(0, ic.readSavableArrayList("controlsList", null));
@@ -1606,9 +1706,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
/**
* setQueueBucket
determines at what phase of the
* rendering process this Spatial will rendered. See the
- * {@link Bucket} enum for an explanation of the various
+ * {@link Bucket} enum for an explanation of the various
* render queue buckets.
- *
+ *
* @param queueBucket
* The bucket to use for this Spatial.
*/
@@ -1693,7 +1793,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
*
* @return store if not null, otherwise, a new matrix containing the result.
*
- * @see Spatial#getWorldTransform()
+ * @see Spatial#getWorldTransform()
*/
public Matrix4f getLocalToWorldMatrix(Matrix4f store) {
if (store == null) {
diff --git a/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java b/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java
index 43cae6db5..05da60f0f 100644
--- a/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java
+++ b/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java
@@ -38,6 +38,8 @@ import com.jme3.export.OutputCapsule;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@@ -45,7 +47,7 @@ import java.io.IOException;
*
* @author Kirill Vainer
*/
-public abstract class AbstractControl implements Control {
+public abstract class AbstractControl implements Control, JmeCloneable {
protected boolean enabled = true;
protected Spatial spatial;
@@ -105,6 +107,20 @@ public abstract class AbstractControl implements Control {
}
}
+ @Override
+ public Object jmeClone() {
+ try {
+ return super.clone();
+ } catch( CloneNotSupportedException e ) {
+ throw new RuntimeException( "Can't clone control for spatial", e );
+ }
+ }
+
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.spatial = cloner.clone(spatial);
+ }
+
public void update(float tpf) {
if (!enabled)
return;
diff --git a/jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java b/jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java
index 7f54f901b..96d7bdf75 100644
--- a/jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java
+++ b/jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java
@@ -86,12 +86,13 @@ public class BillboardControl extends AbstractControl {
alignment = Alignment.Screen;
}
- public Control cloneForSpatial(Spatial spatial) {
- BillboardControl control = new BillboardControl();
- control.alignment = this.alignment;
- control.setSpatial(spatial);
- return control;
- }
+ // default implementation from AbstractControl is equivalent
+ //public Control cloneForSpatial(Spatial spatial) {
+ // BillboardControl control = new BillboardControl();
+ // control.alignment = this.alignment;
+ // control.setSpatial(spatial);
+ // return control;
+ //}
@Override
protected void controlUpdate(float tpf) {
diff --git a/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java b/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java
index a154cbc24..4eccdfa69 100644
--- a/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java
+++ b/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java
@@ -136,13 +136,14 @@ public class CameraControl extends AbstractControl {
// nothing to do
}
- @Override
- public Control cloneForSpatial(Spatial newSpatial) {
- CameraControl control = new CameraControl(camera, controlDir);
- control.setSpatial(newSpatial);
- control.setEnabled(isEnabled());
- return control;
- }
+ // default implementation from AbstractControl is equivalent
+ //@Override
+ //public Control cloneForSpatial(Spatial newSpatial) {
+ // CameraControl control = new CameraControl(camera, controlDir);
+ // control.setSpatial(newSpatial);
+ // control.setEnabled(isEnabled());
+ // return control;
+ //}
private static final String CONTROL_DIR_NAME = "controlDir";
private static final String CAMERA_NAME = "camera";
diff --git a/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java b/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java
index 029cc1b9a..36d29c542 100644
--- a/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java
+++ b/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java
@@ -167,13 +167,14 @@ public class LightControl extends AbstractControl {
// nothing to do
}
- @Override
- public Control cloneForSpatial(Spatial newSpatial) {
- LightControl control = new LightControl(light, controlDir);
- control.setSpatial(newSpatial);
- control.setEnabled(isEnabled());
- return control;
- }
+ // default implementation from AbstractControl is equivalent
+ //@Override
+ //public Control cloneForSpatial(Spatial newSpatial) {
+ // LightControl control = new LightControl(light, controlDir);
+ // control.setSpatial(newSpatial);
+ // control.setEnabled(isEnabled());
+ // return control;
+ //}
private static final String CONTROL_DIR_NAME = "controlDir";
private static final String LIGHT_NAME = "light";
diff --git a/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java b/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java
index 030ccbb3a..f6b657842 100644
--- a/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java
+++ b/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java
@@ -43,6 +43,8 @@ import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Spatial;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
/**
@@ -56,7 +58,7 @@ import java.io.IOException;
* and will update the spatial's LOD if the camera has moved by a specified
* amount.
*/
-public class LodControl extends AbstractControl implements Cloneable {
+public class LodControl extends AbstractControl implements Cloneable, JmeCloneable {
private float trisPerPixel = 1f;
private float distTolerance = 1f;
@@ -140,7 +142,16 @@ public class LodControl extends AbstractControl implements Cloneable {
clone.lastLevel = 0;
clone.numTris = numTris != null ? numTris.clone() : null;
return clone;
- }
+ }
+
+ @Override
+ public Object jmeClone() {
+ LodControl clone = (LodControl)super.jmeClone();
+ clone.lastDistance = 0;
+ clone.lastLevel = 0;
+ clone.numTris = numTris != null ? numTris.clone() : null;
+ return clone;
+ }
@Override
protected void controlUpdate(float tpf) {
diff --git a/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java b/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java
index a52bfb6ee..9c101c73e 100644
--- a/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java
+++ b/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java
@@ -35,6 +35,8 @@ import com.jme3.app.AppTask;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future;
@@ -85,6 +87,7 @@ public class UpdateControl extends AbstractControl {
}
+ @Override
public Control cloneForSpatial(Spatial newSpatial) {
UpdateControl control = new UpdateControl();
control.setSpatial(newSpatial);
@@ -93,4 +96,15 @@ public class UpdateControl extends AbstractControl {
return control;
}
+ @Override
+ public Object jmeClone() {
+ UpdateControl clone = (UpdateControl)super.jmeClone();
+
+ // This is kind of questionable since the tasks aren't cloned and have
+ // no reference to the new spatial or anything. They'll get run again
+ // but it's not clear to me why that would be desired. I'm doing it
+ // because the old cloneForSpatial() code does. FIXME? -pspeed
+ clone.taskQueue.addAll(taskQueue);
+ return clone;
+ }
}
diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java
index 7f0bb601b..c0bc90e3d 100644
--- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java
+++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java
@@ -47,19 +47,20 @@ import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.util.BufferUtils;
import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Arrays;
public class InstancedGeometry extends Geometry {
-
+
private static final int INSTANCE_SIZE = 16;
-
+
private VertexBuffer[] globalInstanceData;
private VertexBuffer transformInstanceData;
private Geometry[] geometries = new Geometry[1];
-
+
private int firstUnusedIndex = 0;
/**
@@ -71,12 +72,12 @@ public class InstancedGeometry extends Geometry {
setBatchHint(BatchHint.Never);
setMaxNumInstances(1);
}
-
+
/**
* Creates instanced geometry with the specified mode and name.
- *
- * @param name The name of the spatial.
- *
+ *
+ * @param name The name of the spatial.
+ *
* @see Spatial#Spatial(java.lang.String)
*/
public InstancedGeometry(String name) {
@@ -85,57 +86,57 @@ public class InstancedGeometry extends Geometry {
setBatchHint(BatchHint.Never);
setMaxNumInstances(1);
}
-
+
/**
- * Global user specified per-instance data.
- *
+ * Global user specified per-instance data.
+ *
* By default set to null
, specify an array of VertexBuffers
* via {@link #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) }.
- *
- * @return global user specified per-instance data.
- * @see #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[])
+ *
+ * @return global user specified per-instance data.
+ * @see #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[])
*/
public VertexBuffer[] getGlobalUserInstanceData() {
return globalInstanceData;
}
-
+
/**
* Specify global user per-instance data.
- *
+ *
* By default set to null
, specify an array of VertexBuffers
* that contain per-instance vertex attributes.
- *
+ *
* @param globalInstanceData global user per-instance data.
- *
- * @throws IllegalArgumentException If one of the VertexBuffers is not
+ *
+ * @throws IllegalArgumentException If one of the VertexBuffers is not
* {@link VertexBuffer#setInstanced(boolean) instanced}.
*/
public void setGlobalUserInstanceData(VertexBuffer[] globalInstanceData) {
this.globalInstanceData = globalInstanceData;
}
-
+
/**
* Specify camera specific user per-instance data.
- *
+ *
* @param transformInstanceData The transforms for each instance.
*/
public void setTransformUserInstanceData(VertexBuffer transformInstanceData) {
this.transformInstanceData = transformInstanceData;
}
-
+
/**
* Return user per-instance transform data.
- *
+ *
* @return The per-instance transform data.
*
- * @see #setTransformUserInstanceData(com.jme3.scene.VertexBuffer)
+ * @see #setTransformUserInstanceData(com.jme3.scene.VertexBuffer)
*/
public VertexBuffer getTransformUserInstanceData() {
return transformInstanceData;
}
-
- private void updateInstance(Matrix4f worldMatrix, float[] store,
- int offset, Matrix3f tempMat3,
+
+ private void updateInstance(Matrix4f worldMatrix, float[] store,
+ int offset, Matrix3f tempMat3,
Quaternion tempQuat) {
worldMatrix.toRotationMatrix(tempMat3);
tempMat3.invertLocal();
@@ -164,17 +165,17 @@ public class InstancedGeometry extends Geometry {
store[offset + 14] = worldMatrix.m23;
store[offset + 15] = tempQuat.getW();
}
-
+
/**
* Set the maximum amount of instances that can be rendered by this
* instanced geometry when mode is set to auto.
- *
+ *
* This re-allocates internal structures and therefore should be called
- * only when necessary.
- *
+ * only when necessary.
+ *
* @param maxNumInstances The maximum number of instances that can be
* rendered.
- *
+ *
* @throws IllegalStateException If mode is set to manual.
* @throws IllegalArgumentException If maxNumInstances is zero or negative
*/
@@ -182,14 +183,14 @@ public class InstancedGeometry extends Geometry {
if (maxNumInstances < 1) {
throw new IllegalArgumentException("maxNumInstances must be 1 or higher");
}
-
+
Geometry[] originalGeometries = geometries;
this.geometries = new Geometry[maxNumInstances];
-
+
if (originalGeometries != null) {
System.arraycopy(originalGeometries, 0, geometries, 0, originalGeometries.length);
}
-
+
// Resize instance data.
if (transformInstanceData != null) {
BufferUtils.destroyDirectBuffer(transformInstanceData.getData());
@@ -203,7 +204,7 @@ public class InstancedGeometry extends Geometry {
BufferUtils.createFloatBuffer(geometries.length * INSTANCE_SIZE));
}
}
-
+
public int getMaxNumInstances() {
return geometries.length;
}
@@ -211,12 +212,12 @@ public class InstancedGeometry extends Geometry {
public int getActualNumInstances() {
return firstUnusedIndex;
}
-
+
private void swap(int idx1, int idx2) {
Geometry g = geometries[idx1];
geometries[idx1] = geometries[idx2];
geometries[idx2] = g;
-
+
if (geometries[idx1] != null) {
InstancedNode.setGeometryStartIndex2(geometries[idx1], idx1);
}
@@ -224,7 +225,7 @@ public class InstancedGeometry extends Geometry {
InstancedNode.setGeometryStartIndex2(geometries[idx2], idx2);
}
}
-
+
private void sanitize(boolean insideEntriesNonNull) {
if (firstUnusedIndex >= geometries.length) {
throw new AssertionError();
@@ -234,7 +235,7 @@ public class InstancedGeometry extends Geometry {
if (geometries[i] == null) {
if (insideEntriesNonNull) {
throw new AssertionError();
- }
+ }
} else if (InstancedNode.getGeometryStartIndex2(geometries[i]) != i) {
throw new AssertionError();
}
@@ -245,55 +246,55 @@ public class InstancedGeometry extends Geometry {
}
}
}
-
+
public void updateInstances() {
FloatBuffer fb = (FloatBuffer) transformInstanceData.getData();
fb.limit(fb.capacity());
fb.position(0);
-
+
TempVars vars = TempVars.get();
{
float[] temp = vars.matrixWrite;
-
+
for (int i = 0; i < firstUnusedIndex; i++) {
Geometry geom = geometries[i];
if (geom == null) {
geom = geometries[firstUnusedIndex - 1];
-
+
if (geom == null) {
throw new AssertionError();
}
-
+
swap(i, firstUnusedIndex - 1);
-
+
while (geometries[firstUnusedIndex -1] == null) {
firstUnusedIndex--;
}
}
-
+
Matrix4f worldMatrix = geom.getWorldMatrix();
updateInstance(worldMatrix, temp, 0, vars.tempMat3, vars.quat1);
fb.put(temp);
}
}
vars.release();
-
+
fb.flip();
-
+
if (fb.limit() / INSTANCE_SIZE != firstUnusedIndex) {
throw new AssertionError();
}
transformInstanceData.updateData(fb);
}
-
+
public void deleteInstance(Geometry geom) {
int idx = InstancedNode.getGeometryStartIndex2(geom);
InstancedNode.setGeometryStartIndex2(geom, -1);
-
+
geometries[idx] = null;
-
+
if (idx == firstUnusedIndex - 1) {
// Deleting the last element.
// Move index back.
@@ -309,12 +310,12 @@ public class InstancedGeometry extends Geometry {
// Deleting element in the middle
}
}
-
+
public void addInstance(Geometry geometry) {
if (geometry == null) {
throw new IllegalArgumentException("geometry cannot be null");
}
-
+
// Take an index from the end.
if (firstUnusedIndex + 1 >= geometries.length) {
// No more room.
@@ -323,15 +324,15 @@ public class InstancedGeometry extends Geometry {
int freeIndex = firstUnusedIndex;
firstUnusedIndex++;
-
+
geometries[freeIndex] = geometry;
InstancedNode.setGeometryStartIndex2(geometry, freeIndex);
}
-
+
public Geometry[] getGeometries() {
return geometries;
}
-
+
public VertexBuffer[] getAllInstanceData() {
ArrayList allData = new ArrayList();
if (transformInstanceData != null) {
@@ -343,6 +344,18 @@ public class InstancedGeometry extends Geometry {
return allData.toArray(new VertexBuffer[allData.size()]);
}
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ super.cloneFields(cloner, original);
+
+ this.globalInstanceData = cloner.clone(globalInstanceData);
+ this.transformInstanceData = cloner.clone(transformInstanceData);
+ this.geometries = cloner.clone(geometries);
+ }
+
@Override
public void write(JmeExporter exporter) throws IOException {
super.write(exporter);
@@ -350,7 +363,7 @@ public class InstancedGeometry extends Geometry {
//capsule.write(currentNumInstances, "cur_num_instances", 1);
capsule.write(geometries, "geometries", null);
}
-
+
@Override
public void read(JmeImporter importer) throws IOException {
super.read(importer);
diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
index 61ec61956..42f8a7615 100644
--- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
+++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
@@ -44,20 +44,23 @@ import com.jme3.scene.control.Control;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.material.MatParam;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.util.HashMap;
+import java.util.Map;
public class InstancedNode extends GeometryGroupNode {
-
+
static int getGeometryStartIndex2(Geometry geom) {
return getGeometryStartIndex(geom);
}
-
+
static void setGeometryStartIndex2(Geometry geom, int startIndex) {
setGeometryStartIndex(geom, startIndex);
}
-
- private static final class InstanceTypeKey implements Cloneable {
+
+ private static final class InstanceTypeKey implements Cloneable, JmeCloneable {
Mesh mesh;
Material material;
@@ -68,7 +71,7 @@ public class InstancedNode extends GeometryGroupNode {
this.material = material;
this.lodLevel = lodLevel;
}
-
+
public InstanceTypeKey(){
}
@@ -95,7 +98,7 @@ public class InstancedNode extends GeometryGroupNode {
}
return true;
}
-
+
@Override
public InstanceTypeKey clone() {
try {
@@ -104,65 +107,94 @@ public class InstancedNode extends GeometryGroupNode {
throw new AssertionError();
}
}
+
+ @Override
+ public Object jmeClone() {
+ try {
+ return super.clone();
+ } catch( CloneNotSupportedException e ) {
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.mesh = cloner.clone(mesh);
+ this.material = cloner.clone(material);
+ }
}
-
- private static class InstancedNodeControl implements Control {
+
+ private static class InstancedNodeControl implements Control, JmeCloneable {
private InstancedNode node;
-
+
public InstancedNodeControl() {
}
-
+
public InstancedNodeControl(InstancedNode node) {
this.node = node;
}
-
+
@Override
public Control cloneForSpatial(Spatial spatial) {
- return this;
+ return this;
// WARNING: Sets wrong control on spatial. Will be
// fixed automatically by InstancedNode.clone() method.
}
-
+
+ @Override
+ public Object jmeClone() {
+ try {
+ return super.clone();
+ } catch( CloneNotSupportedException e ) {
+ throw new RuntimeException("Error cloning control", e);
+ }
+ }
+
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.node = cloner.clone(node);
+ }
+
public void setSpatial(Spatial spatial){
}
-
+
public void update(float tpf){
}
-
+
public void render(RenderManager rm, ViewPort vp) {
node.renderFromControl();
}
-
+
public void write(JmeExporter ex) throws IOException {
}
public void read(JmeImporter im) throws IOException {
}
}
-
+
protected InstancedNodeControl control;
-
- protected HashMap igByGeom
+
+ protected HashMap igByGeom
= new HashMap();
-
+
private InstanceTypeKey lookUp = new InstanceTypeKey();
-
- private HashMap instancesMap =
+
+ private HashMap instancesMap =
new HashMap();
-
+
public InstancedNode() {
super();
// NOTE: since we are deserializing,
// the control is going to be added automatically here.
}
-
+
public InstancedNode(String name) {
super(name);
control = new InstancedNodeControl(this);
addControl(control);
}
-
+
private void renderFromControl() {
for (InstancedGeometry ig : instancesMap.values()) {
ig.updateInstances();
@@ -191,7 +223,7 @@ public class InstancedNode extends GeometryGroupNode {
return ig;
}
-
+
private void addToInstancedGeometry(Geometry geom) {
Material material = geom.getMaterial();
MatParam param = material.getParam("UseInstancing");
@@ -200,20 +232,20 @@ public class InstancedNode extends GeometryGroupNode {
+ "parameter to true on the material prior "
+ "to adding it to InstancedNode");
}
-
+
InstancedGeometry ig = lookUpByGeometry(geom);
igByGeom.put(geom, ig);
geom.associateWithGroupNode(this, 0);
ig.addInstance(geom);
}
-
+
private void removeFromInstancedGeometry(Geometry geom) {
InstancedGeometry ig = igByGeom.remove(geom);
if (ig != null) {
ig.deleteInstance(geom);
}
}
-
+
private void relocateInInstancedGeometry(Geometry geom) {
InstancedGeometry oldIG = igByGeom.get(geom);
InstancedGeometry newIG = lookUpByGeometry(geom);
@@ -226,7 +258,7 @@ public class InstancedNode extends GeometryGroupNode {
igByGeom.put(geom, newIG);
}
}
-
+
private void ungroupSceneGraph(Spatial s) {
if (s instanceof Node) {
for (Spatial sp : ((Node) s).getChildren()) {
@@ -237,14 +269,14 @@ public class InstancedNode extends GeometryGroupNode {
if (g.isGrouped()) {
// Will invoke onGeometryUnassociated automatically.
g.unassociateFromGroupNode();
-
+
if (InstancedNode.getGeometryStartIndex(g) != -1) {
throw new AssertionError();
}
}
}
}
-
+
@Override
public Spatial detachChildAt(int index) {
Spatial s = super.detachChildAt(index);
@@ -253,7 +285,7 @@ public class InstancedNode extends GeometryGroupNode {
}
return s;
}
-
+
private void instance(Spatial n) {
if (n instanceof Geometry) {
Geometry g = (Geometry) n;
@@ -269,20 +301,20 @@ public class InstancedNode extends GeometryGroupNode {
}
}
}
-
+
public void instance() {
instance(this);
}
-
+
@Override
public Node clone() {
return clone(true);
}
-
+
@Override
public Node clone(boolean cloneMaterials) {
InstancedNode clone = (InstancedNode)super.clone(cloneMaterials);
-
+
if (instancesMap.size() > 0) {
// Remove all instanced geometries from the clone
for (int i = 0; i < clone.children.size(); i++) {
@@ -296,7 +328,7 @@ public class InstancedNode extends GeometryGroupNode {
}
}
}
-
+
// remove original control from the clone
clone.controls.remove(this.control);
@@ -307,12 +339,35 @@ public class InstancedNode extends GeometryGroupNode {
clone.lookUp = new InstanceTypeKey();
clone.igByGeom = new HashMap();
clone.instancesMap = new HashMap();
-
+
clone.instance();
-
+
return clone;
}
-
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ super.cloneFields(cloner, original);
+
+ this.control = cloner.clone(control);
+ this.lookUp = cloner.clone(lookUp);
+
+ HashMap newIgByGeom = new HashMap();
+ for( Map.Entry e : igByGeom.entrySet() ) {
+ newIgByGeom.put(cloner.clone(e.getKey()), cloner.clone(e.getValue()));
+ }
+ this.igByGeom = newIgByGeom;
+
+ HashMap newInstancesMap = new HashMap();
+ for( Map.Entry e : instancesMap.entrySet() ) {
+ newInstancesMap.put(cloner.clone(e.getKey()), cloner.clone(e.getValue()));
+ }
+ this.instancesMap = newInstancesMap;
+ }
+
@Override
public void onTransformChange(Geometry geom) {
// Handled automatically
diff --git a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java
index 46dc8390e..f882ac580 100644
--- a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java
+++ b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java
@@ -37,6 +37,7 @@ import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.material.Material;
+import com.jme3.material.RenderState;
import com.jme3.math.Matrix4f;
import com.jme3.math.Vector4f;
import com.jme3.post.Filter;
@@ -44,6 +45,7 @@ import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.texture.FrameBuffer;
+
import java.io.IOException;
/**
@@ -74,6 +76,9 @@ public abstract class AbstractShadowFilter ext
material = new Material(manager, "Common/MatDefs/Shadow/PostShadowFilter.j3md");
this.shadowRenderer = shadowRenderer;
this.shadowRenderer.setPostShadowMaterial(material);
+
+ //this is legacy setting for shadows with backface shadows
+ this.shadowRenderer.setRenderBackFacesShadows(true);
}
@Override
@@ -126,7 +131,7 @@ public abstract class AbstractShadowFilter ext
/**
* How far the shadows are rendered in the view
*
- * @see setShadowZExtend(float zFar)
+ * @see #setShadowZExtend(float zFar)
* @return shadowZExtend
*/
public float getShadowZExtend() {
@@ -248,6 +253,46 @@ public abstract class AbstractShadowFilter ext
shadowRenderer.setEdgeFilteringMode(filterMode);
}
+ /**
+ *
+ * !! WARNING !! this parameter is defaulted to true for the ShadowFilter.
+ * Setting it to true, may produce edges artifacts on shadows. *
+ *
+ * Set to true if you want back faces shadows on geometries.
+ * Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting.
+ *
+ * Setting this parameter will override this parameter for ALL materials in the scene.
+ * This also will automatically adjust the faceCullMode and the PolyOffset of the pre shadow pass.
+ * You can modify them by using {@link #getPreShadowForcedRenderState()}
+ *
+ * If you want to set it differently for each material in the scene you have to use the ShadowRenderer instead
+ * of the shadow filter.
+ *
+ * @param renderBackFacesShadows true or false.
+ */
+ public void setRenderBackFacesShadows(Boolean renderBackFacesShadows) {
+ shadowRenderer.setRenderBackFacesShadows(renderBackFacesShadows);
+ }
+
+ /**
+ * if this filter renders back faces shadows
+ * @return true if this filter renders back faces shadows
+ */
+ public boolean isRenderBackFacesShadows() {
+ return shadowRenderer.isRenderBackFacesShadows();
+ }
+
+ /**
+ * returns the pre shadows pass render state.
+ * use it to adjust the RenderState parameters of the pre shadow pass.
+ * Note that this will be overriden if the preShadow technique in the material has a ForcedRenderState
+ * @return the pre shadow render state.
+ */
+ public RenderState getPreShadowForcedRenderState() {
+ return shadowRenderer.getPreShadowForcedRenderState();
+ }
+
+
/**
* returns the the edge filtering mode
*
diff --git a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java
index bd70465cb..b55c6b2d2 100644
--- a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java
+++ b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java
@@ -31,10 +31,6 @@
*/
package com.jme3.shadow;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
import com.jme3.asset.AssetManager;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
@@ -42,6 +38,7 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.material.Material;
+import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Matrix4f;
import com.jme3.math.Vector2f;
@@ -67,6 +64,10 @@ import com.jme3.texture.Texture.ShadowCompareMode;
import com.jme3.texture.Texture2D;
import com.jme3.ui.Picture;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* abstract shadow renderer that holds commons feature to have for a shadow
* renderer
@@ -92,6 +93,9 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
protected EdgeFilteringMode edgeFilteringMode = EdgeFilteringMode.Bilinear;
protected CompareMode shadowCompareMode = CompareMode.Hardware;
protected Picture[] dispPic;
+ protected RenderState forcedRenderState = new RenderState();
+ protected Boolean renderBackFacesShadows;
+
/**
* true if the fallback material should be used, otherwise false
*/
@@ -181,6 +185,14 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
setShadowCompareMode(shadowCompareMode);
setEdgeFilteringMode(edgeFilteringMode);
setShadowIntensity(shadowIntensity);
+ initForcedRenderState();
+ }
+
+ protected void initForcedRenderState() {
+ forcedRenderState.setFaceCullMode(RenderState.FaceCullMode.Front);
+ forcedRenderState.setColorWrite(false);
+ forcedRenderState.setDepthWrite(true);
+ forcedRenderState.setDepthTest(true);
}
/**
@@ -356,9 +368,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
* rendered in the shadow map
*
* @param shadowMapIndex the index of the shadow map being rendered
- * @param sceneOccluders the occluders of the whole scene
- * @param sceneReceivers the receivers of the whole scene
- * @param shadowMapOcculders
+ * @param shadowMapOccluders the list of occluders
* @return
*/
protected abstract GeometryList getOccludersToRender(int shadowMapIndex, GeometryList shadowMapOccluders);
@@ -425,9 +435,11 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
renderManager.getRenderer().setFrameBuffer(shadowFB[shadowMapIndex]);
renderManager.getRenderer().clearBuffers(true, true, true);
+ renderManager.setForcedRenderState(forcedRenderState);
// render shadow casters to shadow map
viewPort.getQueue().renderShadowQueue(shadowMapOccluders, renderManager, shadowCam, true);
+ renderManager.setForcedRenderState(null);
}
boolean debugfrustums = false;
@@ -535,18 +547,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
private void setMatParams(GeometryList l) {
//iteration throught all the geometries of the list to gather the materials
- matCache.clear();
- for (int i = 0; i < l.size(); i++) {
- Material mat = l.get(i).getMaterial();
- //checking if the material has the post technique and adding it to the material cache
- if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) {
- if (!matCache.contains(mat)) {
- matCache.add(mat);
- }
- } else {
- needsfallBackMaterial = true;
- }
- }
+ buildMatCache(l);
//iterating through the mat cache and setting the parameters
for (Material mat : matCache) {
@@ -566,6 +567,10 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
if (fadeInfo != null) {
mat.setVector2("FadeInfo", fadeInfo);
}
+ if(renderBackFacesShadows != null){
+ mat.setBoolean("BackfaceShadows", renderBackFacesShadows);
+ }
+
setMaterialParameters(mat);
}
@@ -577,6 +582,21 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
}
+ private void buildMatCache(GeometryList l) {
+ matCache.clear();
+ for (int i = 0; i < l.size(); i++) {
+ Material mat = l.get(i).getMaterial();
+ //checking if the material has the post technique and adding it to the material cache
+ if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) {
+ if (!matCache.contains(mat)) {
+ matCache.add(mat);
+ }
+ } else {
+ needsfallBackMaterial = true;
+ }
+ }
+ }
+
/**
* for internal use only
*/
@@ -587,7 +607,10 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
postshadowMat.setTexture(shadowMapStringCache[j], shadowMaps[j]);
}
if (fadeInfo != null) {
- postshadowMat.setVector2("FadeInfo", fadeInfo);
+ postshadowMat.setVector2("FadeInfo", fadeInfo);
+ }
+ if(renderBackFacesShadows != null){
+ postshadowMat.setBoolean("BackfaceShadows", renderBackFacesShadows);
}
}
@@ -730,6 +753,48 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
@Deprecated
public void setFlushQueues(boolean flushQueues) {}
+
+ /**
+ * returns the pre shadows pass render state.
+ * use it to adjust the RenderState parameters of the pre shadow pass.
+ * Note that this will be overriden if the preShadow technique in the material has a ForcedRenderState
+ * @return the pre shadow render state.
+ */
+ public RenderState getPreShadowForcedRenderState() {
+ return forcedRenderState;
+ }
+
+ /**
+ * Set to true if you want back faces shadows on geometries.
+ * Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting.
+ *
+ * Also note that setting this parameter will override this parameter for ALL materials in the scene.
+ * You can alternatively change this parameter on a single material using {@link Material#setBoolean(String, boolean)}
+ *
+ * This also will automatically adjust the faceCullMode and the PolyOffset of the pre shadow pass.
+ * You can modify them by using {@link #getPreShadowForcedRenderState()}
+ *
+ * @param renderBackFacesShadows true or false.
+ */
+ public void setRenderBackFacesShadows(Boolean renderBackFacesShadows) {
+ this.renderBackFacesShadows = renderBackFacesShadows;
+ if(renderBackFacesShadows) {
+ getPreShadowForcedRenderState().setPolyOffset(5, 3);
+ getPreShadowForcedRenderState().setFaceCullMode(RenderState.FaceCullMode.Back);
+ }else{
+ getPreShadowForcedRenderState().setPolyOffset(0, 0);
+ getPreShadowForcedRenderState().setFaceCullMode(RenderState.FaceCullMode.Front);
+ }
+ }
+
+ /**
+ * if this processor renders back faces shadows
+ * @return true if this processor renders back faces shadows
+ */
+ public boolean isRenderBackFacesShadows() {
+ return renderBackFacesShadows != null?renderBackFacesShadows:false;
+ }
+
/**
* De-serialize this instance, for example when loading from a J3O file.
*
diff --git a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java
index acf8a9677..33adf1c09 100644
--- a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java
+++ b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java
@@ -215,6 +215,7 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer {
@Override
protected void setMaterialParameters(Material material) {
material.setColor("Splits", splits);
+ material.setVector3("LightDir", light.getDirection());
if (fadeInfo != null) {
material.setVector2("FadeInfo", fadeInfo);
}
@@ -224,6 +225,7 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer {
protected void clearMaterialParameters(Material material) {
material.clearParam("Splits");
material.clearParam("FadeInfo");
+ material.clearParam("LightDir");
}
/**
diff --git a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java
index 2134e0a7e..e6516df0b 100644
--- a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java
+++ b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java
@@ -157,6 +157,8 @@ public abstract class JmeSystemDelegate {
return false;
} else if (arch.equals("aarch64")) {
return true;
+ } else if (arch.equals("armv7") || arch.equals("armv7l")) {
+ return false;
} else if (arch.equals("arm")) {
return false;
} else {
diff --git a/jme3-core/src/main/java/com/jme3/util/IntMap.java b/jme3-core/src/main/java/com/jme3/util/IntMap.java
index 38ffb2431..b572fad22 100644
--- a/jme3-core/src/main/java/com/jme3/util/IntMap.java
+++ b/jme3-core/src/main/java/com/jme3/util/IntMap.java
@@ -32,19 +32,21 @@
package com.jme3.util;
import com.jme3.util.IntMap.Entry;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* Similar to a {@link Map} except that ints are used as keys.
- *
+ *
* Taken from http://code.google.com/p/skorpios/
- *
- * @author Nate
+ *
+ * @author Nate
*/
-public final class IntMap implements Iterable>, Cloneable {
-
+public final class IntMap implements Iterable>, Cloneable, JmeCloneable {
+
private Entry[] table;
private final float loadFactor;
private int size, mask, capacity, threshold;
@@ -93,6 +95,26 @@ public final class IntMap implements Iterable>, Cloneable {
return null;
}
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public Object jmeClone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.table = cloner.clone(table);
+ }
+
public boolean containsValue(Object value) {
Entry[] table = this.table;
for (int i = table.length; i-- > 0;){
@@ -228,7 +250,7 @@ public final class IntMap implements Iterable>, Cloneable {
idx = 0;
el = 0;
}
-
+
public boolean hasNext() {
return el < size;
}
@@ -255,20 +277,20 @@ public final class IntMap implements Iterable>, Cloneable {
// the entry was null. find another non-null entry.
cur = table[++idx];
} while (cur == null);
-
+
Entry e = cur;
cur = cur.next;
el ++;
-
+
return e;
}
public void remove() {
}
-
+
}
-
- public static final class Entry implements Cloneable {
+
+ public static final class Entry implements Cloneable, JmeCloneable {
final int key;
T value;
@@ -303,5 +325,20 @@ public final class IntMap implements Iterable>, Cloneable {
}
return null;
}
+
+ @Override
+ public Object jmeClone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ this.value = cloner.clone(value);
+ this.next = cloner.clone(next);
+ }
}
}
diff --git a/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java b/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java
index 27f129f2f..a0657c753 100644
--- a/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java
+++ b/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java
@@ -43,7 +43,7 @@ import java.util.*;
* the list is changing.
*
* All modifications, including set() operations will cause a copy of the
- * data to be created that replaces the old version. Because this list is
+ * data to be created that replaces the old version. Because this list is
* not designed for threading concurrency it further optimizes the "many modifications"
* case by buffering them as a normal ArrayList until the next time the contents
* are accessed.
@@ -63,16 +63,16 @@ import java.util.*;
* Even after ListIterator.remove() or Iterator.remove() is called, this change
* is not reflected in the iterator instance as it is still refering to its
* original snapshot.
- *
+ *
*
* @version $Revision$
* @author Paul Speed
*/
-public class SafeArrayList implements List {
-
+public class SafeArrayList implements List, Cloneable {
+
// Implementing List directly to avoid accidentally acquiring
// incorrect or non-optimal behavior from AbstractList. For
- // example, the default iterator() method will not work for
+ // example, the default iterator() method will not work for
// this list.
// Note: given the particular use-cases this was intended,
@@ -81,30 +81,48 @@ public class SafeArrayList implements List {
// SafeArrayList-specific methods could then be exposed
// for the classes like Node and Spatial to use to manage
// the list. This was the callers couldn't remove a child
- // without it being detached properly, for example.
+ // without it being detached properly, for example.
- private Class elementType;
+ private Class elementType;
private List buffer;
private E[] backingArray;
private int size = 0;
-
+
public SafeArrayList(Class elementType) {
- this.elementType = elementType;
+ this.elementType = elementType;
}
-
+
public SafeArrayList(Class elementType, Collection extends E> c) {
- this.elementType = elementType;
+ this.elementType = elementType;
addAll(c);
}
+ public SafeArrayList clone() {
+ try {
+ SafeArrayList clone = (SafeArrayList)super.clone();
+
+ // Clone whichever backing store is currently active
+ if( backingArray != null ) {
+ clone.backingArray = backingArray.clone();
+ }
+ if( buffer != null ) {
+ clone.buffer = (List)((ArrayList)buffer).clone();
+ }
+
+ return clone;
+ } catch( CloneNotSupportedException e ) {
+ throw new AssertionError();
+ }
+ }
+
protected final T[] createArray(Class type, int size) {
- return (T[])java.lang.reflect.Array.newInstance(type, size);
+ return (T[])java.lang.reflect.Array.newInstance(type, size);
}
-
+
protected final E[] createArray(int size) {
- return createArray(elementType, size);
+ return createArray(elementType, size);
}
-
+
/**
* Returns a current snapshot of this List's backing array that
* is guaranteed not to change through further List manipulation.
@@ -114,10 +132,10 @@ public class SafeArrayList implements List {
public final E[] getArray() {
if( backingArray != null )
return backingArray;
-
+
if( buffer == null ) {
backingArray = createArray(0);
- } else {
+ } else {
// Only keep the array or the buffer but never both at
// the same time. 1) it saves space, 2) it keeps the rest
// of the code safer.
@@ -126,35 +144,35 @@ public class SafeArrayList implements List {
}
return backingArray;
}
-
+
protected final List getBuffer() {
if( buffer != null )
return buffer;
-
+
if( backingArray == null ) {
buffer = new ArrayList();
- } else {
+ } else {
// Only keep the array or the buffer but never both at
// the same time. 1) it saves space, 2) it keeps the rest
- // of the code safer.
+ // of the code safer.
buffer = new ArrayList( Arrays.asList(backingArray) );
backingArray = null;
}
return buffer;
}
-
+
public final int size() {
- return size;
+ return size;
}
-
+
public final boolean isEmpty() {
return size == 0;
}
-
+
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
-
+
public Iterator iterator() {
return listIterator();
}
@@ -162,70 +180,70 @@ public class SafeArrayList implements List {
public Object[] toArray() {
return getArray();
}
-
+
public T[] toArray(T[] a) {
-
+
E[] array = getArray();
if (a.length < array.length) {
return (T[])Arrays.copyOf(array, array.length, a.getClass());
- }
-
+ }
+
System.arraycopy( array, 0, a, 0, array.length );
-
+
if (a.length > array.length) {
a[array.length] = null;
}
-
+
return a;
}
-
+
public boolean add(E e) {
boolean result = getBuffer().add(e);
size = getBuffer().size();
return result;
}
-
+
public boolean remove(Object o) {
boolean result = getBuffer().remove(o);
size = getBuffer().size();
return result;
}
-
+
public boolean containsAll(Collection> c) {
return Arrays.asList(getArray()).containsAll(c);
}
-
+
public boolean addAll(Collection extends E> c) {
boolean result = getBuffer().addAll(c);
size = getBuffer().size();
return result;
}
-
+
public boolean addAll(int index, Collection extends E> c) {
boolean result = getBuffer().addAll(index, c);
size = getBuffer().size();
return result;
}
-
+
public boolean removeAll(Collection> c) {
boolean result = getBuffer().removeAll(c);
size = getBuffer().size();
return result;
}
-
+
public boolean retainAll(Collection> c) {
boolean result = getBuffer().retainAll(c);
size = getBuffer().size();
return result;
}
-
+
public void clear() {
getBuffer().clear();
size = 0;
}
-
+
public boolean equals(Object o) {
- if( o == this )
+ if( o == this )
return true;
if( !(o instanceof List) ) //covers null too
return false;
@@ -240,9 +258,9 @@ public class SafeArrayList implements List {
if( o1 == null || !o1.equals(o2) )
return false;
}
- return !(i1.hasNext() || !i2.hasNext());
+ return !(i1.hasNext() || !i2.hasNext());
}
-
+
public int hashCode() {
// Exactly the hash code described in the List interface, basically
E[] array = getArray();
@@ -252,30 +270,30 @@ public class SafeArrayList implements List {
}
return result;
}
-
+
public final E get(int index) {
if( backingArray != null )
return backingArray[index];
if( buffer != null )
return buffer.get(index);
- throw new IndexOutOfBoundsException( "Index:" + index + ", Size:0" );
+ throw new IndexOutOfBoundsException( "Index:" + index + ", Size:0" );
}
-
+
public E set(int index, E element) {
return getBuffer().set(index, element);
}
-
+
public void add(int index, E element) {
getBuffer().add(index, element);
size = getBuffer().size();
}
-
+
public E remove(int index) {
E result = getBuffer().remove(index);
size = getBuffer().size();
return result;
}
-
+
public int indexOf(Object o) {
E[] array = getArray();
for( int i = 0; i < array.length; i++ ) {
@@ -289,7 +307,7 @@ public class SafeArrayList implements List {
}
return -1;
}
-
+
public int lastIndexOf(Object o) {
E[] array = getArray();
for( int i = array.length - 1; i >= 0; i-- ) {
@@ -303,29 +321,29 @@ public class SafeArrayList implements List {
}
return -1;
}
-
+
public ListIterator listIterator() {
return new ArrayIterator(getArray(), 0);
}
-
+
public ListIterator listIterator(int index) {
return new ArrayIterator(getArray(), index);
}
-
+
public List subList(int fromIndex, int toIndex) {
-
+
// So far JME doesn't use subList that I can see so I'm nerfing it.
List raw = Arrays.asList(getArray()).subList(fromIndex, toIndex);
return Collections.unmodifiableList(raw);
}
-
+
public String toString() {
-
+
E[] array = getArray();
if( array.length == 0 ) {
return "[]";
}
-
+
StringBuilder sb = new StringBuilder();
sb.append('[');
for( int i = 0; i < array.length; i++ ) {
@@ -337,63 +355,63 @@ public class SafeArrayList implements List {
sb.append(']');
return sb.toString();
}
-
+
protected class ArrayIterator implements ListIterator {
private E[] array;
private int next;
private int lastReturned;
-
+
protected ArrayIterator( E[] array, int index ) {
this.array = array;
this.next = index;
this.lastReturned = -1;
}
-
+
public boolean hasNext() {
return next != array.length;
}
-
+
public E next() {
if( !hasNext() )
throw new NoSuchElementException();
lastReturned = next++;
return array[lastReturned];
}
-
+
public boolean hasPrevious() {
- return next != 0;
- }
-
+ return next != 0;
+ }
+
public E previous() {
if( !hasPrevious() )
throw new NoSuchElementException();
lastReturned = --next;
return array[lastReturned];
}
-
+
public int nextIndex() {
- return next;
+ return next;
}
-
+
public int previousIndex() {
return next - 1;
}
-
+
public void remove() {
// This operation is not so easy to do but we will fake it.
// The issue is that the backing list could be completely
// different than the one this iterator is a snapshot of.
- // We'll just remove(element) which in most cases will be
+ // We'll just remove(element) which in most cases will be
// correct. If the list had earlier .equals() equivalent
// elements then we'll remove one of those instead. Either
// way, none of those changes are reflected in this iterator.
SafeArrayList.this.remove( array[lastReturned] );
}
-
+
public void set(E e) {
throw new UnsupportedOperationException();
}
-
+
public void add(E e) {
throw new UnsupportedOperationException();
}
diff --git a/jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java b/jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java
new file mode 100644
index 000000000..cb5873007
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2016 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.util.clone;
+
+
+/**
+ * Provides custom cloning for a particular object type. Once
+ * registered with the Cloner, this function object will be called twice
+ * for any cloned object that matches the class for which it was registered.
+ * It will first call cloneObject() to shallow clone the object and then call
+ * cloneFields() to deep clone the object's values.
+ *
+ * This two step process is important because this is what allows
+ * circular references in the cloned object graph.
+ *
+ * @author Paul Speed
+ */
+public interface CloneFunction {
+
+ /**
+ * Performs a shallow clone of the specified object. This is similar
+ * to the JmeCloneable.clone() method in semantics and is the first part
+ * of a two part cloning process. Once the shallow clone is created, it
+ * is cached and CloneFunction.cloneFields() is called. In this way,
+ * the CloneFunction interface can completely take over the JmeCloneable
+ * style cloning for an object that doesn't otherwise implement that interface.
+ *
+ * @param cloner The cloner performing the cloning operation.
+ * @param original The original object that needs to be cloned.
+ */
+ public T cloneObject( Cloner cloner, T original );
+
+
+ /**
+ * Performs a deep clone of the specified clone's fields. This is similar
+ * to the JmeCloneable.cloneFields() method in semantics and is the second part
+ * of a two part cloning process. Once the shallow clone is created, it
+ * is cached and CloneFunction.cloneFields() is called. In this way,
+ * the CloneFunction interface can completely take over the JmeCloneable
+ * style cloning for an object that doesn't otherwise implement that interface.
+ *
+ * @param cloner The cloner performing the cloning operation.
+ * @param clone The clone previously returned from cloneObject().
+ * @param original The original object that was cloned. This is provided for
+ * the very special case where field cloning needs to refer to
+ * the original object. Mostly the necessary fields should already
+ * be on the clone.
+ */
+ public void cloneFields( Cloner cloner, T clone, T original );
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java
new file mode 100644
index 000000000..ba202e463
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) 2016 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.util.clone;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A deep clone utility that provides similar object-graph-preserving
+ * qualities to typical serialization schemes. An internal registry
+ * of cloned objects is kept to be used by other objects in the deep
+ * clone process that implement JmeCloneable.
+ *
+ * By default, objects that do not implement JmeCloneable will
+ * be treated like normal Java Cloneable objects. If the object does
+ * not implement the JmeCloneable or the regular JDK Cloneable interfaces
+ * AND has no special handling defined then an IllegalArgumentException
+ * will be thrown.
+ *
+ * Enhanced object cloning is done in a two step process. First,
+ * the object is cloned using the normal Java clone() method and stored
+ * in the clone registry. After that, if it implements JmeCloneable then
+ * its cloneFields() method is called to deep clone any of the fields.
+ * This two step process has a few benefits. First, it means that objects
+ * can easily have a regular shallow clone implementation just like any
+ * normal Java objects. Second, the deep cloning of fields happens after
+ * creation wich means that the clone is available to future field cloning
+ * to resolve circular references.
+ *
+ * Similar to Java serialization, the handling of specific object
+ * types can be customized. This allows certain objects to be cloned gracefully
+ * even if they aren't normally Cloneable. This can also be used as a
+ * sort of filter to keep certain types of objects from being cloned.
+ * (For example, adding the IdentityCloneFunction for Mesh.class would cause
+ * all mesh instances to be shared with the original object graph.)
+ *
+ * By default, the Cloner registers serveral default clone functions
+ * as follows:
+ *
+ * - java.util.ArrayList: ListCloneFunction
+ *
- java.util.LinkedList: ListCloneFunction
+ *
- java.util.concurrent.CopyOnWriteArrayList: ListCloneFunction
+ *
- java.util.Vector: ListCloneFunction
+ *
- java.util.Stack: ListCloneFunction
+ *
- com.jme3.util.SafeArrayList: ListCloneFunction
+ *
+ *
+ * Usage:
+ *
+ * // Example 1: using an instantiated, reusable cloner.
+ * Cloner cloner = new Cloner();
+ * Foo fooClone = cloner.clone(foo);
+ * cloner.clearIndex(); // prepare it for reuse
+ * Foo fooClone2 = cloner.clone(foo);
+ *
+ * // Example 2: using the utility method that self-instantiates a temporary cloner.
+ * Foo fooClone = Cloner.deepClone(foo);
+ *
+ *
+ *
+ * @author Paul Speed
+ */
+public class Cloner {
+
+ static Logger log = Logger.getLogger(Cloner.class.getName());
+
+ /**
+ * Keeps track of the objects that have been cloned so far.
+ */
+ private IdentityHashMap
* @param name the name of the scene element. This is required for
* identification and comparison purposes.
- * @param patchSize size of the individual patches (geometry). Power of 2 plus 1,
+ * @param patchSize size of the individual patches (geometry). Power of 2 plus 1,
* must be smaller than totalSize. (eg. 33, 65...)
- * @param totalSize the size of this entire terrain (on one side). Power of 2 plus 1
+ * @param totalSize the size of this entire terrain (on one side). Power of 2 plus 1
* (eg. 513, 1025, 2049...)
* @param heightMap The height map to generate the terrain from (a flat
- * height map will be generated if this is null). The size of one side of the heightmap
+ * height map will be generated if this is null). The size of one side of the heightmap
* must match the totalSize. So a 513x513 heightmap is needed for a terrain with totalSize of 513.
*/
public TerrainQuad(String name, int patchSize, int totalSize, float[] heightMap) {
this(name, patchSize, totalSize, Vector3f.UNIT_XYZ, heightMap);
-
+
affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2);
fixNormalEdges(affectedAreaBBox);
addControl(new NormalRecalcControl(this));
}
-
+
/**
- *
+ *
* @param name the name of the scene element. This is required for
* identification and comparison purposes.
* @param patchSize size of the individual patches
@@ -176,7 +177,7 @@ public class TerrainQuad extends Node implements Terrain {
}
/**
- *
+ *
* @param name the name of the scene element. This is required for
* identification and comparison purposes.
* @param patchSize size of the individual patches
@@ -192,9 +193,9 @@ public class TerrainQuad extends Node implements Terrain {
//fixNormalEdges(affectedAreaBBox);
//addControl(new NormalRecalcControl(this));
}
-
+
/**
- *
+ *
* @param name the name of the scene element. This is required for
* identification and comparison purposes.
* @param patchSize size of the individual patches
@@ -217,17 +218,17 @@ public class TerrainQuad extends Node implements Terrain {
Vector2f offset, float offsetAmount)
{
super(name);
-
+
if (heightMap == null)
heightMap = generateDefaultHeightMap(quadSize);
-
+
if (!FastMath.isPowerOfTwo(quadSize - 1)) {
throw new RuntimeException("size given: " + quadSize + " Terrain quad sizes may only be (2^N + 1)");
}
if (FastMath.sqrt(heightMap.length) > quadSize) {
Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Heightmap size is larger than the terrain size. Make sure your heightmap image is the same size as the terrain!");
}
-
+
this.offset = offset;
this.offsetAmount = offsetAmount;
this.totalSize = totalSize;
@@ -248,7 +249,7 @@ public class TerrainQuad extends Node implements Terrain {
public void recalculateAllNormals() {
affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), totalSize*2, Float.MAX_VALUE, totalSize*2);
}
-
+
/**
* Create just a flat heightmap
*/
@@ -267,11 +268,11 @@ public class TerrainQuad extends Node implements Terrain {
//TODO background-thread this if it ends up being expensive
fixNormals(affectedAreaBBox); // the affected patches
fixNormalEdges(affectedAreaBBox); // the edges between the patches
-
+
setNormalRecalcNeeded(null); // set to false
}
}
-
+
/**
* Caches the transforms (except rotation) so the LOD calculator,
* which runs on a separate thread, can access them safely.
@@ -343,7 +344,7 @@ public class TerrainQuad extends Node implements Terrain {
public Material getMaterial() {
return getMaterial(null);
}
-
+
public Material getMaterial(Vector3f worldLocation) {
// get the material from one of the children. They all share the same material
if (children != null) {
@@ -362,7 +363,7 @@ public class TerrainQuad extends Node implements Terrain {
public int getNumMajorSubdivisions() {
return 1;
}
-
+
protected boolean calculateLod(List location, HashMap updates, LodCalculator lodCalculator) {
@@ -434,7 +435,7 @@ public class TerrainQuad extends Node implements Terrain {
utp.setBottomLod(utpD.getNewLod());
utpD.setTopLod(utp.getNewLod());
}
-
+
if (left != null) {
UpdatedTerrainPatch utpL = updated.get(left.getName());
if (utpL == null) {
@@ -478,7 +479,7 @@ public class TerrainQuad extends Node implements Terrain {
}
}
}
-
+
/**
* Find any neighbours that should have their edges seamed because another neighbour
* changed its LOD to a greater value (less detailed)
@@ -587,10 +588,10 @@ public class TerrainQuad extends Node implements Terrain {
/**
* Quadrants, world coordinates, and heightmap coordinates (Y-up):
- *
+ *
* -z
- * -u |
- * -v 1|3
+ * -u |
+ * -v 1|3
* -x ----+---- x
* 2|4 u
* | v
@@ -668,7 +669,7 @@ public class TerrainQuad extends Node implements Terrain {
quad3.setLocalTranslation(origin3);
quad3.quadrant = 3;
this.attachChild(quad3);
-
+
// 4 lower right of heightmap, lower right quad
float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1,
split - 1, split);
@@ -892,7 +893,7 @@ public class TerrainQuad extends Node implements Terrain {
}
return false;
}
-
+
/**
* This will cause all normals for this terrain quad to be recalculated
*/
@@ -1024,14 +1025,14 @@ public class TerrainQuad extends Node implements Terrain {
int col;
int row;
Spatial child;
-
+
QuadrantChild(int col, int row, Spatial child) {
this.col = col;
this.row = row;
this.child = child;
}
}
-
+
private QuadrantChild findMatchingChild(int x, int z) {
int quad = findQuadrant(x, z);
int split = (size + 1) >> 1;
@@ -1069,7 +1070,7 @@ public class TerrainQuad extends Node implements Terrain {
}
return null;
}
-
+
/**
* Get the interpolated height of the terrain at the specified point.
* @param xz the location to get the height for
@@ -1090,7 +1091,7 @@ public class TerrainQuad extends Node implements Terrain {
* gets an interpolated value at the specified point
*/
protected float getHeight(int x, int z, float xm, float zm) {
-
+
QuadrantChild match = findMatchingChild(x,z);
if (match != null) {
if (match.child instanceof TerrainQuad) {
@@ -1107,10 +1108,10 @@ public class TerrainQuad extends Node implements Terrain {
float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)(totalSize-1) / 2f);
float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)(totalSize-1) / 2f);
Vector3f normal = getNormal(x, z, xz);
-
+
return normal;
}
-
+
protected Vector3f getNormal(float x, float z, Vector2f xz) {
x-=0.5f;
z-=0.5f;
@@ -1125,15 +1126,15 @@ public class TerrainQuad extends Node implements Terrain {
// v3--v4 | Z
// |
// <-------Y
- // X
+ // X
Vector3f n1 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.ceil(z));
Vector3f n2 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.ceil(z));
Vector3f n3 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.floor(z));
Vector3f n4 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.floor(z));
-
+
return n1.add(n2).add(n3).add(n4).normalize();
}
-
+
public void setHeight(Vector2f xz, float height) {
List coord = new ArrayList();
coord.add(xz);
@@ -1291,7 +1292,7 @@ public class TerrainQuad extends Node implements Terrain {
return (x >= 0 && x <= totalSize && z >= 0 && z <= totalSize);
}
-
+
public int getTerrainSize() {
return totalSize;
}
@@ -1750,7 +1751,7 @@ public class TerrainQuad extends Node implements Terrain {
totalSize = c.readInt("totalSize", 0);
//lodCalculator = (LodCalculator) c.readSavable("lodCalculator", createDefaultLodCalculator());
//lodCalculatorFactory = (LodCalculatorFactory) c.readSavable("lodCalculatorFactory", null);
-
+
if ( !(getParent() instanceof TerrainQuad) ) {
BoundingBox all = new BoundingBox(getWorldTranslation(), totalSize, totalSize, totalSize);
affectedAreaBBox = all;
@@ -1793,10 +1794,10 @@ public class TerrainQuad extends Node implements Terrain {
quadClone.quadrant = quadrant;
//quadClone.lodCalculatorFactory = lodCalculatorFactory.clone();
//quadClone.lodCalculator = lodCalculator.clone();
-
+
TerrainLodControl lodControlCloned = this.getControl(TerrainLodControl.class);
TerrainLodControl lodControl = quadClone.getControl(TerrainLodControl.class);
-
+
if (lodControlCloned != null && !(getParent() instanceof TerrainQuad)) {
//lodControlCloned.setLodCalculator(lodControl.getLodCalculator().clone());
}
@@ -1806,7 +1807,27 @@ public class TerrainQuad extends Node implements Terrain {
return quadClone;
}
-
+
+ /**
+ * Called internally by com.jme3.util.clone.Cloner. Do not call directly.
+ */
+ @Override
+ public void cloneFields( Cloner cloner, Object original ) {
+ super.cloneFields(cloner, original);
+
+ this.stepScale = cloner.clone(stepScale);
+ this.offset = cloner.clone(offset);
+
+ // This was not cloned before... I think that's a mistake.
+ this.affectedAreaBBox = cloner.clone(affectedAreaBBox);
+
+ // picker is not cloneable and not cloned. This also seems like
+ // a mistake if you ever load the same terrain twice.
+ // this.picker = cloner.clone(picker);
+
+ // neighbourFinder is also not cloned. Maybe that's ok.
+ }
+
@Override
protected void setParent(Node parent) {
super.setParent(parent);
@@ -1815,7 +1836,7 @@ public class TerrainQuad extends Node implements Terrain {
clearCaches();
}
}
-
+
/**
* Removes any cached references this terrain is holding, in particular
* the TerrainPatch's neighbour references.
@@ -1834,7 +1855,7 @@ public class TerrainQuad extends Node implements Terrain {
}
}
}
-
+
public int getMaxLod() {
if (maxLod < 0)
maxLod = Math.max(1, (int) (FastMath.log(size-1)/FastMath.log(2)) -1); // -1 forces our minimum of 4 triangles wide
diff --git a/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.j3m b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.j3m
index 91967d5c4..5d732d0c0 100644
--- a/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.j3m
+++ b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.j3m
@@ -1,11 +1,11 @@
Material Signpost : Common/MatDefs/Light/Lighting.j3md {
MaterialParameters {
Shininess: 4.0
- DiffuseMap: Models/Sign Post/Sign Post.jpg
- NormalMap: Models/Sign Post/Sign Post_normal.jpg
- SpecularMap: Models/Sign Post/Sign Post_specular.jpg
+ DiffuseMap: "Models/Sign Post/Sign Post.jpg"
+ NormalMap: "Models/Sign Post/Sign Post_normal.jpg"
+ SpecularMap: "Models/Sign Post/Sign Post_specular.jpg"
UseMaterialColors : true
- Ambient : 0.5 0.5 0.5 1.0
+ Ambient : 1.0 1.0 1.0 1.0
Diffuse : 1.0 1.0 1.0 1.0
Specular : 1.0 1.0 1.0 1.0
}
diff --git a/jme3-testdata/src/main/resources/Models/Test/BasicCubeLow.obj b/jme3-testdata/src/main/resources/Models/Test/BasicCubeLow.obj
new file mode 100644
index 000000000..fb00257de
--- /dev/null
+++ b/jme3-testdata/src/main/resources/Models/Test/BasicCubeLow.obj
@@ -0,0 +1,46 @@
+# Blender v2.76 (sub 0) OBJ File: 'BasicCube.blend'
+# www.blender.org
+o Cube
+v 1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 -0.999999
+v 0.999999 1.000000 1.000001
+v -1.000000 1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+vt 0.500000 0.250043
+vt 0.749957 0.250043
+vt 0.749957 0.500000
+vt 0.000087 0.500000
+vt 0.000087 0.250043
+vt 0.250043 0.250043
+vt 0.250043 0.500000
+vt 0.250043 0.000087
+vt 0.500000 0.000087
+vt 0.999913 0.250043
+vt 0.999913 0.500000
+vt 0.500000 0.500000
+vt 0.500000 0.749957
+vt 0.250044 0.749957
+vn 0.577300 -0.577300 0.577300
+vn -0.577300 -0.577300 0.577300
+vn -0.577300 -0.577300 -0.577300
+vn -0.577300 0.577300 -0.577300
+vn -0.577300 0.577300 0.577300
+vn 0.577300 0.577300 0.577300
+vn 0.577300 0.577300 -0.577300
+vn 0.577300 -0.577300 -0.577300
+s 1
+f 2/1/1 3/2/2 4/3/3
+f 8/4/4 7/5/5 6/6/6
+f 5/7/7 6/6/6 2/1/1
+f 6/6/6 7/8/5 3/9/2
+f 3/2/2 7/10/5 8/11/4
+f 1/12/8 4/13/3 8/14/4
+f 1/12/8 2/1/1 4/3/3
+f 5/7/7 8/4/4 6/6/6
+f 1/12/8 5/7/7 2/1/1
+f 2/1/1 6/6/6 3/9/2
+f 4/3/3 3/2/2 8/11/4
+f 5/7/7 1/12/8 8/14/4
diff --git a/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o b/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o
index 1d56eefa9..d67a2263d 100644
Binary files a/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o and b/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o differ
diff --git a/jme3-testdata/src/main/resources/Models/Test/Normal_pixel.png b/jme3-testdata/src/main/resources/Models/Test/Normal_pixel.png
new file mode 100644
index 000000000..d9adc6971
Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Test/Normal_pixel.png differ