diff --git a/.gitignore b/.gitignore index 2e2c2ed44..49ca1902d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,34 @@ -*.dll -*.so -*.jnilib -*.dylib -*.iml -**/nbproject/private/ /.gradle/ -/.nb-gradle/ +/.nb-gradle/private/ +/.nb-gradle/profiles/private/ /.idea/ /dist/ /build/ -/bin/ /netbeans/ -/.classpath -/.project -/.settings +/sdk/jdks/local/ +/jme3-core/build/ /jme3-core/src/main/resources/com/jme3/system/version.properties -/jme3-*/build/ +/jme3-plugins/build/ +/jme3-desktop/build/ +/jme3-android-native/build/ +/jme3-android/build/ +/jme3-android-examples/build/ +/jme3-blender/build/ +/jme3-effects/build/ +/jme3-bullet/build/ +/jme3-terrain/build/ +/jme3-bullet-native/build/ +/jme3-bullet-native-android/build/ +/jme3-jogg/build/ +/jme3-jbullet/build/ +/jme3-lwjgl/build/ +/jme3-networking/build/ +/jme3-niftygui/build/ +/jme3-testdata/build/ +/jme3-examples/build/ +/jme3-jogl/build/ +/jme3-ios/build/ +/jme3-gl-autogen/build/ /jme3-bullet-native/bullet.zip /jme3-bullet-native/bullet-2.82-r2704/ /jme3-android-native/openal-soft/ @@ -25,24 +38,113 @@ /jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.h /jme3-android-native/src/native/jme_decode/com_jme3_texture_plugins_AndroidNativeImageLoader.h /jme3-android-native/stb_image.h -/sdk/dist/ -/sdk/jdks/local/ -/sdk/build/ /sdk/jme3-tests-template/src/com/jme3/gde/templates/tests/JmeTestsProject.zip /sdk/jme3-tests-template/src/com/jme3/gde/templates/tests/JME3TestsAndroidProject.zip /sdk/jme3-project-testdata/release/ /sdk/JME3TestsTemplateAndroid/src/jme3test/ /sdk/JME3TestsTemplate/src/jme3test/ +/sdk/build/ /sdk/jme3-core-baselibs/release/ /sdk/jme3-core-libraries/release/ /sdk/jme3-project-baselibs/release/ /sdk/jme3-project-libraries/release/ -/sdk/jme3-*/build/ -/sdk/nbi/stub/ext/components/products/jdk/build/ -/sdk/nbi/stub/ext/components/products/jdk/dist/ +/sdk/jme3-codepalette/build/ +/sdk/jme3-core-libraries/build/ +/sdk/jme3-code-check/build/ +/sdk/jme3-core-baselibs/build/ +/sdk/jme3-documentation/build/ +/sdk/jme3-core-updatecenters/build/ +/sdk/jme3-project-testdata/build/ +/sdk/jme3-project-libraries/build/ +/sdk/jme3-project-baselibs/build/ +/sdk/jme3-templates/build/ +/sdk/jme3-texture-editor/build/ +/sdk/jme3-tests-template/build/ +/sdk/jme3-upgrader/build/ +/sdk/jme3-core/build/ +/sdk/jme3-obfuscate/build/ +/sdk/jme3-gui/build/ +/sdk/jme3-cinematics/build/ +/sdk/jme3-terrain-editor/build/ +/sdk/jme3-lwjgl-applet/build/ +/sdk/jme3-blender/build/ +/sdk/jme3-navmesh-gen/build/ +/sdk/jme3-angelfont/build/ +/sdk/jme3-materialeditor/build/ +/sdk/jme3-android/build/ +/sdk/jme3-desktop-executables/build/ +/sdk/jme3-ogrexml/build/ +/sdk/jme3-ogretools/build/ +/sdk/jme3-scenecomposer/build/ +/sdk/jme3-assetpack-support/build/ +/sdk/jme3-model-importer/build/ +/sdk/jme3-wavefront/build/ +/sdk/jme3-vehicle-creator/build/ +/sdk/jme3-welcome-screen/build/ +/sdk/jme3-glsl-support/build/ +/sdk/jme3-dark-laf/build/ +/sdk/nbproject/private/ +/sdk/jme3-scenecomposer/nbproject/private/ +/sdk/jme3-core/nbproject/private/ +/sdk/jme3-core-baselibs/nbproject/private/ +/sdk/jme3-welcome-screen/nbproject/private/ +/sdk/jme3-lwjgl-applet/nbproject/private/ +/sdk/jme3-ogrexml/nbproject/private/ +/sdk/jme3-upgrader/nbproject/private/ +/sdk/jme3-obfuscate/nbproject/private/ +/sdk/jme3-navmesh-gen/nbproject/private/ +/sdk/jme3-wavefront/nbproject/private/ +/sdk/jme3-project-libraries/nbproject/private/ +/sdk/jme3-ogretools/nbproject/private/ +/sdk/jme3-assetpack-support/nbproject/private/ +/sdk/jme3-cinematics/nbproject/private/ +/sdk/jme3-model-importer/nbproject/private/ +/sdk/jme3-desktop-executables/nbproject/private/ +/sdk/jme3-glsl-support/nbproject/private/ +/sdk/jme3-android/nbproject/private/ +/sdk/jme3-angelfont/nbproject/private/ +/sdk/jme3-codepalette/nbproject/private/ +/sdk/jme3-documentation/nbproject/private/ +/sdk/jme3-vehicle-creator/nbproject/private/ +/sdk/jme3-code-check/nbproject/private/ +/sdk/jme3-blender/nbproject/private/ +/sdk/jme3-core-libraries/nbproject/private/ +/sdk/jme3-core-updatecenters/nbproject/private/ +/sdk/jme3-gui/nbproject/private/ +/sdk/jme3-materialeditor/nbproject/private/ +/sdk/jme3-project-baselibs/nbproject/private/ +/sdk/jme3-project-testdata/nbproject/private/ +/sdk/jme3-templates/nbproject/private/ +/sdk/jme3-terrain-editor/nbproject/private/ +/sdk/jme3-tests-template/nbproject/private/ +/sdk/jme3-texture-editor/nbproject/private/ +/sdk/JME3TestsTemplate/nbproject/private/ +/sdk/JME3TestsTemplateAndroid/nbproject/private/ +/bin +/.classpath +/.project +/.settings +*.dll +*.so +*.jnilib +*.dylib +*.iml +.DS_Store +/sdk/dist/ !/jme3-bullet-native/libs/native/windows/x86_64/bulletjme.dll !/jme3-bullet-native/libs/native/windows/x86/bulletjme.dll !/jme3-bullet-native/libs/native/osx/x86/libbulletjme.dylib !/jme3-bullet-native/libs/native/osx/x86_64/libbulletjme.dylib !/jme3-bullet-native/libs/native/linux/x86/libbulletjme.so !/jme3-bullet-native/libs/native/linux/x86_64/libbulletjme.so +/.nb-gradle/ +/sdk/ant-jme/nbproject/private/ +/sdk/nbi/stub/ext/engine/nbproject/private/ +/sdk/nbi/stub/ext/components/products/jdk/nbproject/private/ +/sdk/nbi/stub/ext/components/products/blender/nbproject/private/ +/sdk/nbi/stub/ext/components/products/helloworld/nbproject/private/ +/sdk/BasicGameTemplate/nbproject/private/ +/sdk/nbi/stub/ext/components/products/jdk/build/ +/sdk/nbi/stub/ext/components/products/jdk/dist/ +/sdk/jme3-dark-laf/nbproject/private/ +jme3-lwjgl3/build/ diff --git a/common.gradle b/common.gradle index d275c19f5..2cad48bf2 100644 --- a/common.gradle +++ b/common.gradle @@ -8,7 +8,7 @@ apply plugin: 'maven' group = 'org.jmonkeyengine' version = jmePomVersion -sourceCompatibility = '1.6' +sourceCompatibility = '1.7' [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' repositories { diff --git a/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java b/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java index b103e92df..142a23d98 100644 --- a/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java +++ b/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java @@ -50,7 +50,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt /** * The jme3 application object */ - protected Application app = null; + protected LegacyApplication app = null; /** * Sets the desired RGB size for the surfaceview. 16 = RGB565, 24 = RGB888. @@ -178,7 +178,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt private boolean inConfigChange = false; private class DataObject { - protected Application app = null; + protected LegacyApplication app = null; } @Override @@ -241,7 +241,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt try { if (app == null) { @SuppressWarnings("unchecked") - Class clazz = (Class) Class.forName(appClass); + Class clazz = (Class) Class.forName(appClass); app = clazz.newInstance(); } diff --git a/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java b/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java index 5673c8609..fed2d8863 100644 --- a/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java +++ b/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java @@ -207,7 +207,7 @@ public class AndroidHarnessFragment extends Fragment implements protected ImageView splashImageView = null; final private String ESCAPE_EVENT = "TouchEscape"; private boolean firstDrawFrame = true; - private Application app = null; + private LegacyApplication app = null; private int viewWidth = 0; private int viewHeight = 0; @@ -258,7 +258,7 @@ public class AndroidHarnessFragment extends Fragment implements try { if (app == null) { @SuppressWarnings("unchecked") - Class clazz = (Class) Class.forName(appClass); + Class clazz = (Class) Class.forName(appClass); app = clazz.newInstance(); } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java index bddc5fab3..0a7fc3838 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java @@ -430,7 +430,7 @@ public class AndroidTouchInput implements TouchInput { return; } - logger.log(Level.INFO, "event: {0}", event); + //logger.log(Level.INFO, "event: {0}", event); inputEventQueue.add(event); if (event instanceof TouchEvent) { diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java index 1d76fc02f..12aff2f4f 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java @@ -215,8 +215,8 @@ public class Edge { /** * The method computes the crossing pint of this edge and another edge. If - * there is no crossing then null is returned. This method also allows to - * get the crossing point of the straight lines that contain these edges if + * there is no crossing then null is returned. Also null is returned if the edges are parallel. + * This method also allows to get the crossing point of the straight lines that contain these edges if * you set the 'extend' parameter to true. * * @param edge @@ -227,7 +227,7 @@ public class Edge { * @param extendSecondEdge * set to true to find a crossing point along the whole * straight that contains the given edge - * @return cross point on null if none exist + * @return cross point on null if none exist or the edges are parallel */ public Vector3f getCrossPoint(Edge edge, boolean extendThisEdge, boolean extendSecondEdge) { Vector3d P1 = new Vector3d(this.getFirstVertex()); @@ -235,6 +235,11 @@ public class Edge { Vector3d u = new Vector3d(this.getSecondVertex()).subtract(P1).normalizeLocal(); Vector3d v = new Vector3d(edge.getSecondVertex()).subtract(P2).normalizeLocal(); + if(Math.abs(u.dot(v)) >= 1 - FastMath.DBL_EPSILON) { + // the edges are parallel; do not care about the crossing point + return null; + } + double t1 = 0, t2 = 0; if(u.x == 0 && v.x == 0) { t2 = (u.z * (P2.y - P1.y) - u.y * (P2.z - P1.z)) / (u.y * v.z - u.z * v.y); @@ -262,11 +267,11 @@ public class Edge { // the lines cross, check if p1 and p2 are within the edges Vector3d p = p1.subtract(P1); double cos = p.dot(u) / p.length(); - if (extendThisEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() <= this.getLength()) { + if (extendThisEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() - this.getLength() <= FastMath.FLT_EPSILON) { // p1 is inside the first edge, lets check the other edge now p = p2.subtract(P2); cos = p.dot(v) / p.length(); - if(extendSecondEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() <= edge.getLength()) { + if(extendSecondEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() - edge.getLength() <= FastMath.FLT_EPSILON) { return p1.toVector3f(); } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java index a41d58ff0..97c730df1 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java @@ -146,7 +146,6 @@ public class Face implements Comparator { /** * @return current indexes of the face (if it is already triangulated then more than one index group will be in the result list) */ - @SuppressWarnings("unchecked") public List> getCurrentIndexes() { if (triangulatedFaces == null) { return Arrays.asList(indexes.getAll()); @@ -279,16 +278,6 @@ public class Face implements Comparator { // two special cases will improve the computations speed if(face.getIndexes().size() == 3) { triangulatedFaces.add(face.getIndexes().clone()); - } else if(face.getIndexes().size() == 4) { - // in case face has 4 verts we use the plain triangulation - indexes[0] = face.getIndex(0); - indexes[1] = face.getIndex(1); - indexes[2] = face.getIndex(2); - triangulatedFaces.add(new IndexesLoop(indexes)); - - indexes[1] = face.getIndex(2); - indexes[2] = face.getIndex(3); - triangulatedFaces.add(new IndexesLoop(indexes)); } else { int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1; while (face.vertexCount() > 0) { diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java index 5284b964a..49482c87f 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java @@ -286,30 +286,33 @@ public class MeshHelper extends AbstractBlenderHelper { List> result = new ArrayList>(); Structure parent = blenderContext.peekParent(); - Structure defbase = (Structure) parent.getFieldValue("defbase"); - List groupNames = new ArrayList(); - List defs = defbase.evaluateListBase(); - for (Structure def : defs) { - groupNames.add(def.getFieldValue("name").toString()); - } + if(parent != null) { + // the mesh might be saved without its parent (it is then unused) + Structure defbase = (Structure) parent.getFieldValue("defbase"); + List groupNames = new ArrayList(); + List defs = defbase.evaluateListBase(); + for (Structure def : defs) { + groupNames.add(def.getFieldValue("name").toString()); + } - Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices - if (pDvert.isNotNull()) {// assigning weights and bone indices - List dverts = pDvert.fetchData(); - for (Structure dvert : dverts) { - Map weightsForVertex = new HashMap(); - Pointer pDW = (Pointer) dvert.getFieldValue("dw"); - if (pDW.isNotNull()) { - List dw = pDW.fetchData(); - for (Structure deformWeight : dw) { - int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue(); - float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue(); - String groupName = groupNames.get(groupIndex); + Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices + if (pDvert.isNotNull()) {// assigning weights and bone indices + List dverts = pDvert.fetchData(); + for (Structure dvert : dverts) { + Map weightsForVertex = new HashMap(); + Pointer pDW = (Pointer) dvert.getFieldValue("dw"); + if (pDW.isNotNull()) { + List dw = pDW.fetchData(); + for (Structure deformWeight : dw) { + int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue(); + float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue(); + String groupName = groupNames.get(groupIndex); - weightsForVertex.put(groupName, weight); + weightsForVertex.put(groupName, weight); + } } + result.add(weightsForVertex); } - result.add(weightsForVertex); } } return result; diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java index 7c69fdf51..d4f3658c0 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java @@ -41,6 +41,8 @@ import com.jme3.math.Vector3f; 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; /** @@ -49,7 +51,7 @@ import java.io.IOException; * * @author normenhansen */ -public abstract class AbstractPhysicsControl implements PhysicsControl { +public abstract class AbstractPhysicsControl implements PhysicsControl, JmeCloneable { private final Quaternion tmp_inverseWorldRotation = new Quaternion(); protected Spatial spatial; @@ -161,6 +163,12 @@ public abstract class AbstractPhysicsControl implements PhysicsControl { } + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.spatial = cloner.clone(spatial); + createSpatialData(this.spatial); + } + public void setSpatial(Spatial spatial) { if (this.spatial != null && this.spatial != spatial) { removeSpatialData(this.spatial); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java index c91c1be1d..30190f1f1 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java @@ -50,6 +50,8 @@ import com.jme3.renderer.ViewPort; 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; import java.util.List; import java.util.logging.Level; @@ -68,7 +70,7 @@ import java.util.logging.Logger; * * @author normenhansen */ -public class BetterCharacterControl extends AbstractPhysicsControl implements PhysicsTickListener { +public class BetterCharacterControl extends AbstractPhysicsControl implements PhysicsTickListener, JmeCloneable { protected static final Logger logger = Logger.getLogger(BetterCharacterControl.class.getName()); protected PhysicsRigidBody rigidBody; @@ -663,12 +665,21 @@ public class BetterCharacterControl extends AbstractPhysicsControl implements Ph rigidBody.setUserObject(null); } + @Override public Control cloneForSpatial(Spatial spatial) { BetterCharacterControl control = new BetterCharacterControl(radius, height, mass); control.setJumpForce(jumpForce); return control; } + @Override + public Object jmeClone() { + BetterCharacterControl control = new BetterCharacterControl(radius, height, mass); + control.setJumpForce(jumpForce); + control.spatial = this.spatial; + return control; + } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java index d521dbec7..7ef80778c 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java @@ -44,13 +44,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; /** * You might want to try BetterCharacterControl as well. * @author normenhansen */ -public class CharacterControl extends PhysicsCharacter implements PhysicsControl { +public class CharacterControl extends PhysicsCharacter implements PhysicsControl, JmeCloneable { protected Spatial spatial; protected boolean enabled = true; @@ -87,6 +89,7 @@ public class CharacterControl extends PhysicsCharacter implements PhysicsControl return spatial.getWorldTranslation(); } + @Override public Control cloneForSpatial(Spatial spatial) { CharacterControl control = new CharacterControl(collisionShape, stepHeight); control.setCcdMotionThreshold(getCcdMotionThreshold()); @@ -103,6 +106,29 @@ public class CharacterControl extends PhysicsCharacter implements PhysicsControl return control; } + @Override + public Object jmeClone() { + CharacterControl control = new CharacterControl(collisionShape, stepHeight); + control.setCcdMotionThreshold(getCcdMotionThreshold()); + control.setCcdSweptSphereRadius(getCcdSweptSphereRadius()); + control.setCollideWithGroups(getCollideWithGroups()); + control.setCollisionGroup(getCollisionGroup()); + control.setFallSpeed(getFallSpeed()); + control.setGravity(getGravity()); + control.setJumpSpeed(getJumpSpeed()); + control.setMaxSlope(getMaxSlope()); + control.setPhysicsLocation(getPhysicsLocation()); + control.setUpAxis(getUpAxis()); + control.setApplyPhysicsLocal(isApplyPhysicsLocal()); + control.spatial = this.spatial; + return control; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.spatial = cloner.clone(spatial); + } + public void setSpatial(Spatial spatial) { this.spatial = spatial; setUserObject(spatial); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java index 9ed150556..70c636507 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java @@ -44,6 +44,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; /** @@ -51,7 +53,7 @@ import java.io.IOException; * overlaps with other physics objects (e.g. aggro radius). * @author normenhansen */ -public class GhostControl extends PhysicsGhostObject implements PhysicsControl { +public class GhostControl extends PhysicsGhostObject implements PhysicsControl, JmeCloneable { protected Spatial spatial; protected boolean enabled = true; @@ -93,6 +95,7 @@ public class GhostControl extends PhysicsGhostObject implements PhysicsControl { return spatial.getWorldRotation(); } + @Override public Control cloneForSpatial(Spatial spatial) { GhostControl control = new GhostControl(collisionShape); control.setCcdMotionThreshold(getCcdMotionThreshold()); @@ -105,6 +108,25 @@ public class GhostControl extends PhysicsGhostObject implements PhysicsControl { return control; } + @Override + public Object jmeClone() { + GhostControl control = new GhostControl(collisionShape); + control.setCcdMotionThreshold(getCcdMotionThreshold()); + control.setCcdSweptSphereRadius(getCcdSweptSphereRadius()); + control.setCollideWithGroups(getCollideWithGroups()); + control.setCollisionGroup(getCollisionGroup()); + control.setPhysicsLocation(getPhysicsLocation()); + control.setPhysicsRotation(getPhysicsRotationMatrix()); + control.setApplyPhysicsLocal(isApplyPhysicsLocal()); + control.spatial = this.spatial; + return control; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.spatial = cloner.clone(spatial); + } + public void setSpatial(Spatial spatial) { this.spatial = spatial; setUserObject(spatial); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java index d81d6a267..6c25b6662 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java @@ -61,6 +61,8 @@ import com.jme3.scene.Node; 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; import java.util.*; import java.util.logging.Level; @@ -92,7 +94,7 @@ import java.util.logging.Logger; * * @author Normen Hansen and Rémy Bouquet (Nehon) */ -public class KinematicRagdollControl extends AbstractPhysicsControl implements PhysicsCollisionListener { +public class KinematicRagdollControl extends AbstractPhysicsControl implements PhysicsCollisionListener, JmeCloneable { protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName()); protected List listeners; @@ -910,6 +912,7 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P public void render(RenderManager rm, ViewPort vp) { } + @Override public Control cloneForSpatial(Spatial spatial) { KinematicRagdollControl control = new KinematicRagdollControl(preset, weightThreshold); control.setMode(mode); @@ -919,6 +922,17 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P return control; } + @Override + public Object jmeClone() { + KinematicRagdollControl control = new KinematicRagdollControl(preset, weightThreshold); + control.setMode(mode); + control.setRootMass(rootMass); + control.setWeightThreshold(weightThreshold); + control.setApplyPhysicsLocal(applyLocal); + control.spatial = this.spatial; + return control; + } + public Vector3f setIKTarget(Bone bone, Vector3f worldPos, int chainLength) { Vector3f target = worldPos.subtract(targetModel.getWorldTranslation()); ikTargets.put(bone.getName(), target); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java index baad952a0..25a23cc7c 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java @@ -51,13 +51,15 @@ import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; import com.jme3.scene.shape.Box; import com.jme3.scene.shape.Sphere; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; /** * * @author normenhansen */ -public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl { +public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl, JmeCloneable { protected Spatial spatial; protected boolean enabled = true; @@ -89,6 +91,7 @@ public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl super(shape, mass); } + @Override public Control cloneForSpatial(Spatial spatial) { RigidBodyControl control = new RigidBodyControl(collisionShape, mass); control.setAngularFactor(getAngularFactor()); @@ -115,6 +118,39 @@ public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl return control; } + @Override + public Object jmeClone() { + RigidBodyControl control = new RigidBodyControl(collisionShape, mass); + control.setAngularFactor(getAngularFactor()); + control.setAngularSleepingThreshold(getAngularSleepingThreshold()); + control.setCcdMotionThreshold(getCcdMotionThreshold()); + control.setCcdSweptSphereRadius(getCcdSweptSphereRadius()); + control.setCollideWithGroups(getCollideWithGroups()); + control.setCollisionGroup(getCollisionGroup()); + control.setDamping(getLinearDamping(), getAngularDamping()); + control.setFriction(getFriction()); + control.setGravity(getGravity()); + control.setKinematic(isKinematic()); + control.setKinematicSpatial(isKinematicSpatial()); + control.setLinearSleepingThreshold(getLinearSleepingThreshold()); + control.setPhysicsLocation(getPhysicsLocation(null)); + control.setPhysicsRotation(getPhysicsRotationMatrix(null)); + control.setRestitution(getRestitution()); + + if (mass > 0) { + control.setAngularVelocity(getAngularVelocity()); + control.setLinearVelocity(getLinearVelocity()); + } + control.setApplyPhysicsLocal(isApplyPhysicsLocal()); + control.spatial = this.spatial; + return control; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.spatial = cloner.clone(spatial); + } + public void setSpatial(Spatial spatial) { this.spatial = spatial; setUserObject(spatial); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java index 0dc033331..7ad36b3ae 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java @@ -46,6 +46,8 @@ import com.jme3.renderer.ViewPort; 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; import java.io.IOException; import java.util.Iterator; @@ -53,7 +55,7 @@ import java.util.Iterator; * * @author normenhansen */ -public class VehicleControl extends PhysicsVehicle implements PhysicsControl { +public class VehicleControl extends PhysicsVehicle implements PhysicsControl, JmeCloneable { protected Spatial spatial; protected boolean enabled = true; @@ -106,6 +108,7 @@ public class VehicleControl extends PhysicsVehicle implements PhysicsControl { return spatial.getWorldRotation(); } + @Override public Control cloneForSpatial(Spatial spatial) { VehicleControl control = new VehicleControl(collisionShape, mass); control.setAngularFactor(getAngularFactor()); @@ -155,6 +158,63 @@ public class VehicleControl extends PhysicsVehicle implements PhysicsControl { return control; } + @Override + public Object jmeClone() { + VehicleControl control = new VehicleControl(collisionShape, mass); + control.setAngularFactor(getAngularFactor()); + control.setAngularSleepingThreshold(getAngularSleepingThreshold()); + control.setAngularVelocity(getAngularVelocity()); + control.setCcdMotionThreshold(getCcdMotionThreshold()); + control.setCcdSweptSphereRadius(getCcdSweptSphereRadius()); + control.setCollideWithGroups(getCollideWithGroups()); + control.setCollisionGroup(getCollisionGroup()); + control.setDamping(getLinearDamping(), getAngularDamping()); + control.setFriction(getFriction()); + control.setGravity(getGravity()); + control.setKinematic(isKinematic()); + control.setLinearSleepingThreshold(getLinearSleepingThreshold()); + control.setLinearVelocity(getLinearVelocity()); + control.setPhysicsLocation(getPhysicsLocation()); + control.setPhysicsRotation(getPhysicsRotationMatrix()); + control.setRestitution(getRestitution()); + + control.setFrictionSlip(getFrictionSlip()); + control.setMaxSuspensionTravelCm(getMaxSuspensionTravelCm()); + control.setSuspensionStiffness(getSuspensionStiffness()); + control.setSuspensionCompression(tuning.suspensionCompression); + control.setSuspensionDamping(tuning.suspensionDamping); + control.setMaxSuspensionForce(getMaxSuspensionForce()); + + for (Iterator it = wheels.iterator(); it.hasNext();) { + VehicleWheel wheel = it.next(); + VehicleWheel newWheel = control.addWheel(wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), wheel.isFrontWheel()); + newWheel.setFrictionSlip(wheel.getFrictionSlip()); + newWheel.setMaxSuspensionTravelCm(wheel.getMaxSuspensionTravelCm()); + newWheel.setSuspensionStiffness(wheel.getSuspensionStiffness()); + newWheel.setWheelsDampingCompression(wheel.getWheelsDampingCompression()); + newWheel.setWheelsDampingRelaxation(wheel.getWheelsDampingRelaxation()); + newWheel.setMaxSuspensionForce(wheel.getMaxSuspensionForce()); + + // Copy the wheel spatial reference directly for now. They'll + // get fixed up in the cloneFields() method + newWheel.setWheelSpatial(wheel.getWheelSpatial()); + } + control.setApplyPhysicsLocal(isApplyPhysicsLocal()); + + control.spatial = spatial; + return control; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.spatial = cloner.clone(spatial); + + for( VehicleWheel wheel : wheels ) { + Spatial spatial = cloner.clone(wheel.getWheelSpatial()); + wheel.setWheelSpatial(spatial); + } + } + public void setSpatial(Spatial spatial) { this.spatial = spatial; setUserObject(spatial); diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java index bed5caaee..3dd317624 100644 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java @@ -70,6 +70,10 @@ public class ConeCollisionShape extends CollisionShape { public float getRadius() { return radius; } + + public float getHeight() { + return height; + } public void write(JmeExporter ex) throws IOException { super.write(ex); diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimControl.java b/jme3-core/src/main/java/com/jme3/animation/AnimControl.java index 13328cddf..36dbb6be5 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AnimControl.java +++ b/jme3-core/src/main/java/com/jme3/animation/AnimControl.java @@ -38,11 +38,14 @@ import com.jme3.scene.Mesh; import com.jme3.scene.Spatial; import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.Control; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import com.jme3.util.TempVars; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.Map; import java.util.Map.Entry; /** @@ -65,7 +68,7 @@ import java.util.Map.Entry; * * @author Kirill Vainer */ -public final class AnimControl extends AbstractControl implements Cloneable { +public final class AnimControl extends AbstractControl implements Cloneable, JmeCloneable { /** * Skeleton object must contain corresponding data for the targets' weight buffers. @@ -108,6 +111,7 @@ public final class AnimControl extends AbstractControl implements Cloneable { /** * Internal use only. */ + @Override public Control cloneForSpatial(Spatial spatial) { try { AnimControl clone = (AnimControl) super.clone(); @@ -130,6 +134,32 @@ public final class AnimControl extends AbstractControl implements Cloneable { } } + @Override + public Object jmeClone() { + AnimControl clone = (AnimControl) super.jmeClone(); + clone.channels = new ArrayList(); + clone.listeners = new ArrayList(); + + return clone; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + super.cloneFields(cloner, original); + + this.skeleton = cloner.clone(skeleton); + + // Note cloneForSpatial() never actually cloned the animation map... just its reference + HashMap newMap = new HashMap<>(); + + // animationMap is cloned, but only ClonableTracks will be cloned as they need a reference to a cloned spatial + for( Map.Entry e : animationMap.entrySet() ) { + newMap.put(e.getKey(), cloner.clone(e.getValue())); + } + + this.animationMap = newMap; + } + /** * @param animations Set the animations that this AnimControl * will be capable of playing. The animations should be compatible diff --git a/jme3-core/src/main/java/com/jme3/animation/Animation.java b/jme3-core/src/main/java/com/jme3/animation/Animation.java index 5ae34628e..9fe47db9f 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Animation.java +++ b/jme3-core/src/main/java/com/jme3/animation/Animation.java @@ -35,6 +35,8 @@ import com.jme3.export.*; import com.jme3.scene.Spatial; import com.jme3.util.SafeArrayList; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; /** @@ -42,7 +44,7 @@ import java.io.IOException; * * @author Kirill Vainer, Marcin Roguski (Kaelthas) */ -public class Animation implements Savable, Cloneable { +public class Animation implements Savable, Cloneable, JmeCloneable { /** * The name of the animation. @@ -190,6 +192,33 @@ public class Animation implements Savable, Cloneable { } } + @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 ) { + + // There is some logic here that I'm copying but I'm not sure if + // it's a mistake or not. If a track is not a CloneableTrack then it + // isn't cloned at all... even though they all implement clone() methods. -pspeed + SafeArrayList newTracks = new SafeArrayList<>(Track.class); + for( Track track : tracks ) { + if( track instanceof ClonableTrack ) { + newTracks.add(cloner.clone(track)); + } else { + // this is the part that seems fishy + newTracks.add(track); + } + } + this.tracks = newTracks; + } + @Override public String toString() { return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']'; diff --git a/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java b/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java index 7bb5ca0ce..fc67686d2 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java @@ -39,6 +39,8 @@ import com.jme3.export.OutputCapsule; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -174,6 +176,7 @@ public class AudioTrack implements ClonableTrack { * @param spatial the Spatial holding the AnimControl * @return the cloned Track with proper reference */ + @Override public Track cloneForSpatial(Spatial spatial) { AudioTrack audioTrack = new AudioTrack(); audioTrack.length = this.length; @@ -192,7 +195,27 @@ public class AudioTrack implements ClonableTrack { return audioTrack; } - /** + @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 ) { + // Duplicating the old cloned state from cloneForSpatial() + this.initialized = false; + this.started = false; + this.played = false; + this.audio = cloner.clone(audio); + } + + + /** * recursive function responsible for finding the newly cloned AudioNode * * @param spat diff --git a/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java b/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java index ae7ae6703..bfdd87abd 100644 --- a/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java @@ -32,6 +32,7 @@ package com.jme3.animation; import com.jme3.scene.Spatial; +import com.jme3.util.clone.JmeCloneable; /** * An interface that allow to clone a Track for a given Spatial. @@ -43,7 +44,7 @@ import com.jme3.scene.Spatial; * * @author Nehon */ -public interface ClonableTrack extends Track { +public interface ClonableTrack extends Track, JmeCloneable { /** * Allows to clone the track for a given Spatial. diff --git a/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java b/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java index 85674e223..89500c1cd 100644 --- a/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java @@ -44,6 +44,8 @@ import com.jme3.scene.Spatial.CullHint; import com.jme3.scene.control.AbstractControl; 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; import java.util.logging.Level; import java.util.logging.Logger; @@ -116,6 +118,17 @@ public class EffectTrack implements ClonableTrack { } } + @Override + public Object jmeClone() { + KillParticleControl c = new KillParticleControl(); + //this control should be removed as it shouldn't have been persisted in the first place + //In the quest to find the less hackish solution to achieve this, + //making it remove itself from the spatial in the first update loop when loaded was the less bad. + c.remove = true; + c.spatial = spatial; + return c; + } + @Override protected void controlRender(RenderManager rm, ViewPort vp) { } @@ -125,8 +138,8 @@ public class EffectTrack implements ClonableTrack { KillParticleControl c = new KillParticleControl(); //this control should be removed as it shouldn't have been persisted in the first place - //In the quest to find the less hackish solution to achieve this, - //making it remove itself from the spatial in the first update loop when loaded was the less bad. + //In the quest to find the less hackish solution to achieve this, + //making it remove itself from the spatial in the first update loop when loaded was the less bad. c.remove = true; c.setSpatial(spatial); return c; @@ -243,7 +256,7 @@ public class EffectTrack implements ClonableTrack { public float[] getKeyFrameTimes() { return new float[] { startOffset }; } - + /** * Clone this track * @@ -263,6 +276,7 @@ public class EffectTrack implements ClonableTrack { * @param spatial the Spatial holding the AnimControl * @return the cloned Track with proper reference */ + @Override public Track cloneForSpatial(Spatial spatial) { EffectTrack effectTrack = new EffectTrack(); effectTrack.particlesPerSeconds = this.particlesPerSeconds; @@ -283,6 +297,21 @@ public class EffectTrack implements ClonableTrack { return effectTrack; } + @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.emitter = cloner.clone(emitter); + } + /** * recursive function responsible for finding the newly cloned Emitter * diff --git a/jme3-core/src/main/java/com/jme3/animation/Skeleton.java b/jme3-core/src/main/java/com/jme3/animation/Skeleton.java index b0d3419d8..8ae3ca507 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Skeleton.java +++ b/jme3-core/src/main/java/com/jme3/animation/Skeleton.java @@ -34,6 +34,8 @@ package com.jme3.animation; import com.jme3.export.*; import com.jme3.math.Matrix4f; import com.jme3.util.TempVars; +import com.jme3.util.clone.JmeCloneable; +import com.jme3.util.clone.Cloner; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -45,7 +47,7 @@ import java.util.List; * * @author Kirill Vainer */ -public final class Skeleton implements Savable { +public final class Skeleton implements Savable, JmeCloneable { private Bone[] rootBones; private Bone[] boneList; @@ -118,6 +120,15 @@ public final class Skeleton implements Savable { public Skeleton() { } + @Override + public Object jmeClone() { + return new Skeleton(this); + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + } + private void createSkinningMatrices() { skinningMatrixes = new Matrix4f[boneList.length]; for (int i = 0; i < skinningMatrixes.length; i++) { diff --git a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java index 36c8c6e38..f4d735f36 100644 --- a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java +++ b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java @@ -46,6 +46,8 @@ import com.jme3.scene.control.Control; import com.jme3.shader.VarType; import com.jme3.util.SafeArrayList; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.nio.Buffer; import java.nio.ByteBuffer; @@ -63,7 +65,7 @@ import java.util.logging.Logger; * * @author Rémy Bouquet Based on AnimControl by Kirill Vainer */ -public class SkeletonControl extends AbstractControl implements Cloneable { +public class SkeletonControl extends AbstractControl implements Cloneable, JmeCloneable { /** * The skeleton of the model. @@ -348,6 +350,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable { } } + @Override public Control cloneForSpatial(Spatial spatial) { Node clonedNode = (Node) spatial; SkeletonControl clone = new SkeletonControl(); @@ -388,6 +391,29 @@ public class SkeletonControl extends AbstractControl implements Cloneable { return clone; } + @Override + public Object jmeClone() { + return super.jmeClone(); + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + super.cloneFields(cloner, original); + + this.skeleton = cloner.clone(skeleton); + + // If the targets were cloned then this will clone them. If the targets + // were shared then this will share them. + this.targets = cloner.clone(targets); + + // Not automatic set cloning yet + Set newMaterials = new HashSet(); + for( Material m : this.materials ) { + newMaterials.add(cloner.clone(m)); + } + this.materials = newMaterials; + } + /** * * @param boneName the name of the bone diff --git a/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java b/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java index b6bff101a..7ee927cd3 100644 --- a/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java +++ b/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java @@ -36,6 +36,8 @@ import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import com.jme3.export.Savable; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.ArrayList; @@ -48,7 +50,7 @@ import java.util.ArrayList; * * @author Nehon */ -public class TrackInfo implements Savable { +public class TrackInfo implements Savable, JmeCloneable { ArrayList tracks = new ArrayList(); @@ -72,4 +74,18 @@ public class TrackInfo implements Savable { public void addTrack(Track track) { tracks.add(track); } + + @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.tracks = cloner.clone(tracks); + } } diff --git a/jme3-core/src/main/java/com/jme3/app/Application.java b/jme3-core/src/main/java/com/jme3/app/Application.java index 581ff8174..82e4959a7 100644 --- a/jme3-core/src/main/java/com/jme3/app/Application.java +++ b/jme3-core/src/main/java/com/jme3/app/Application.java @@ -33,112 +33,53 @@ 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.input.InputManager; 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 Application class represents an instance of a - * real-time 3D rendering jME application. - * - * An Application 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. - * + * The Application interface represents the minimum exposed + * capabilities of a concrete jME3 application. */ -public class Application implements SystemListener { - - private static final Logger logger = Logger.getLogger(Application.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 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 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 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 c) { boolean result = getBuffer().addAll(c); size = getBuffer().size(); return result; } - + public boolean addAll(int index, Collection 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 index = new IdentityHashMap(); + + /** + * Custom functions for cloning objects. + */ + private Map functions = new HashMap(); + + /** + * Cache the clone methods once for all cloners. + */ + private static final Map methodCache = new ConcurrentHashMap<>(); + + /** + * Creates a new cloner with only default clone functions and an empty + * object index. + */ + public Cloner() { + // Register some standard types + ListCloneFunction listFunction = new ListCloneFunction(); + functions.put(java.util.ArrayList.class, listFunction); + functions.put(java.util.LinkedList.class, listFunction); + functions.put(java.util.concurrent.CopyOnWriteArrayList.class, listFunction); + functions.put(java.util.Vector.class, listFunction); + functions.put(java.util.Stack.class, listFunction); + functions.put(com.jme3.util.SafeArrayList.class, listFunction); + } + + /** + * Convenience utility function that creates a new Cloner, uses it to + * deep clone the object, and then returns the result. + */ + public static T deepClone( T object ) { + return new Cloner().clone(object); + } + + /** + * Deeps clones the specified object, reusing previous clones when possible. + * + *

Object cloning priority works as follows:

+ *
    + *
  • If the object has already been cloned then its clone is returned. + *
  • If there is a custom CloneFunction then it is called to clone the object. + *
  • If the object implements Cloneable then its clone() method is called, arrays are + * deep cloned with entries passing through clone(). + *
  • If the object implements JmeCloneable then its cloneFields() method is called on the + * clone. + *
  • Else an IllegalArgumentException is thrown. + *
+ * + * Note: objects returned by this method may not have yet had their cloneField() + * method called. + */ + public T clone( T object ) { + return clone(object, true); + } + + /** + * Internal method to work around a Java generics typing issue by + * isolating the 'bad' case into a method with suppressed warnings. + */ + @SuppressWarnings("unchecked") + private Class objectClass( T object ) { + // This should be 100% allowed without a cast but Java generics + // is not that smart sometimes. + // Wrapping it in a method at least isolates the warning suppression + return (Class)object.getClass(); + } + + /** + * Deeps clones the specified object, reusing previous clones when possible. + * + *

Object cloning priority works as follows:

+ *
    + *
  • If the object has already been cloned then its clone is returned. + *
  • If useFunctions is true and there is a custom CloneFunction then it is + * called to clone the object. + *
  • If the object implements Cloneable then its clone() method is called, arrays are + * deep cloned with entries passing through clone(). + *
  • If the object implements JmeCloneable then its cloneFields() method is called on the + * clone. + *
  • Else an IllegalArgumentException is thrown. + *
+ * + *

The abililty to selectively use clone functions is useful when + * being called from a clone function.

+ * + * Note: objects returned by this method may not have yet had their cloneField() + * method called. + */ + public T clone( T object, boolean useFunctions ) { + + if( object == null ) { + return null; + } + + if( log.isLoggable(Level.FINER) ) { + log.finer("cloning:" + object.getClass() + "@" + System.identityHashCode(object)); + } + + Class type = objectClass(object); + + // Check the index to see if we already have it + Object clone = index.get(object); + if( clone != null ) { + if( log.isLoggable(Level.FINER) ) { + log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object) + + " as cached:" + clone.getClass() + "@" + System.identityHashCode(clone)); + } + return type.cast(clone); + } + + // See if there is a custom function... that trumps everything. + CloneFunction f = getCloneFunction(type); + if( f != null ) { + T result = f.cloneObject(this, object); + + // Store the object in the identity map so that any circular references + // are resolvable. + index.put(object, result); + + // Now call the function again to deep clone the fields + f.cloneFields(this, result, object); + + if( log.isLoggable(Level.FINER) ) { + if( result == null ) { + log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object) + + " as transformed:null"); + } else { + log.finer("clone:" + object.getClass() + "@" + System.identityHashCode(object) + + " as transformed:" + result.getClass() + "@" + System.identityHashCode(result)); + } + } + return result; + } + + if( object.getClass().isArray() ) { + // Perform an array clone + clone = arrayClone(object); + + // Array clone already indexes the clone + } else if( object instanceof JmeCloneable ) { + // Use the two-step cloning semantics + clone = ((JmeCloneable)object).jmeClone(); + + // Store the object in the identity map so that any circular references + // are resolvable + index.put(object, clone); + + ((JmeCloneable)clone).cloneFields(this, object); + } else if( object instanceof Cloneable ) { + + // Perform a regular Java shallow clone + try { + clone = javaClone(object); + } catch( CloneNotSupportedException e ) { + throw new IllegalArgumentException("Object is not cloneable, type:" + type, e); + } + + // Store the object in the identity map so that any circular references + // are resolvable + index.put(object, clone); + } else { + throw new IllegalArgumentException("Object is not cloneable, type:" + type); + } + + if( log.isLoggable(Level.FINER) ) { + log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object) + + " as " + clone.getClass() + "@" + System.identityHashCode(clone)); + } + return type.cast(clone); + } + + /** + * Sets a custom CloneFunction for implementations of the specified Java type. Some + * inheritance checks are made but no disambiguation is performed. + *

Note: in the general case, it is better to register against specific classes and + * not super-classes or super-interfaces unless you know specifically that they are cloneable.

+ *

By default ListCloneFunction is registered for ArrayList, LinkedList, CopyOnWriteArrayList, + * Vector, Stack, and JME's SafeArrayList.

+ */ + public void setCloneFunction( Class type, CloneFunction function ) { + if( function == null ) { + functions.remove(type); + } else { + functions.put(type, function); + } + } + + /** + * Returns a previously registered clone function for the specified type or null + * if there is no custom clone function for the type. + */ + @SuppressWarnings("unchecked") + public CloneFunction getCloneFunction( Class type ) { + CloneFunction result = (CloneFunction)functions.get(type); + if( result == null ) { + // Do a more exhaustive search + for( Map.Entry e : functions.entrySet() ) { + if( e.getKey().isAssignableFrom(type) ) { + result = e.getValue(); + break; + } + } + if( result != null ) { + // Cache it for later + functions.put(type, result); + } + } + return result; + } + + /** + * Forces an object to be added to the indexing cache such that attempts + * to clone the 'original' will always result in the 'clone' being returned. + * This can be used to stub out specific values from being cloned or to + * force global shared instances to be used even if the object is cloneable + * normally. + */ + public void setClonedValue( T original, T clone ) { + index.put(original, clone); + } + + /** + * Returns true if the specified object has already been cloned + * by this cloner during this session. Cloned objects are cached + * for later use and it's sometimes convenient to know if some + * objects have already been cloned. + */ + public boolean isCloned( Object o ) { + return index.containsKey(o); + } + + /** + * Clears the object index allowing the cloner to be reused for a brand new + * cloning operation. + */ + public void clearIndex() { + index.clear(); + } + + /** + * Performs a raw shallow Java clone using reflection. This call does NOT + * check against the clone index and so will return new objects every time + * it is called. That's because these are shallow clones and have not (and may + * not ever, depending on the caller) get resolved. + * + *

This method is provided as a convenient way for CloneFunctions to call + * clone() and objects without necessarily knowing their real type.

+ */ + public T javaClone( T object ) throws CloneNotSupportedException { + Method m = methodCache.get(object.getClass()); + if( m == null ) { + try { + // Lookup the method and cache it + m = object.getClass().getMethod("clone"); + } catch( NoSuchMethodException e ) { + throw new CloneNotSupportedException("No public clone method found for:" + object.getClass()); + } + methodCache.put(object.getClass(), m); + + // Note: yes we might cache the method twice... but so what? + } + + try { + Class type = objectClass(object); + return type.cast(m.invoke(object)); + } catch( IllegalAccessException | InvocationTargetException e ) { + throw new RuntimeException("Error cloning object of type:" + object.getClass(), e); + } + } + + /** + * Clones a primitive array by coping it and clones an object + * array by coping it and then running each of its values through + * Cloner.clone(). + */ + protected T arrayClone( T object ) { + + // Java doesn't support the cloning of arrays through reflection unless + // you open access to Object's protected clone array... which requires + // elevated privileges. So we will do a work-around that is slightly less + // elegant. + // This should be 100% allowed without a case but Java generics + // is not that smart + Class type = objectClass(object); + Class elementType = type.getComponentType(); + int size = Array.getLength(object); + Object clone = Array.newInstance(elementType, size); + + // Store the clone for later lookups + index.put(object, clone); + + if( elementType.isPrimitive() ) { + // Then our job is a bit easier + System.arraycopy(object, 0, clone, 0, size); + } else { + // Else it's an object array so we'll clone it and its children + for( int i = 0; i < size; i++ ) { + Object element = clone(Array.get(object, i)); + Array.set(clone, i, element); + } + } + + return type.cast(clone); + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java b/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java new file mode 100644 index 000000000..ac3dce6ee --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java @@ -0,0 +1,58 @@ +/* + * 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; + + +/** + * A CloneFunction implementation that simply returns the + * the passed object without cloning it. This is useful for + * forcing some object types (like Meshes) to be shared between + * the original and cloned object graph. + * + * @author Paul Speed + */ +public class IdentityCloneFunction implements CloneFunction { + + /** + * Returns the object directly. + */ + public T cloneObject( Cloner cloner, T object ) { + return object; + } + + /** + * Does nothing. + */ + public void cloneFields( Cloner cloner, T clone, T object ) { + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java b/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java new file mode 100644 index 000000000..6b278b222 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java @@ -0,0 +1,99 @@ +/* + * 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; + + +/** + * Indicates an object that wishes to more actively participate in the + * two-part deep copying process provided by the Cloner. Objects implementing + * this interface can access the already cloned object graph to resolve + * their local dependencies in a way that will be equivalent to the + * original object graph. In other words, if two objects in the graph + * share the same target reference then the cloned version will share + * the cloned reference. + * + *

For example, if an object wishes to deep clone one of its fields + * then it will call cloner.clone(object) instead of object.clone(). + * The cloner will keep track of any clones already created for 'object' + * and return that instead of a new clone.

+ * + *

Cloning of a JmeCloneable object is done in two parts. First, + * the standard Java clone() method is called to create a shallow clone + * of the object. Second, the cloner wil lcall the cloneFields() method + * to let the object deep clone any of its fields that should be cloned.

+ * + *

This two part process is necessary to facilitate circular references. + * When an object calls cloner.clone() during its cloneFields() method, it + * may get only a shallow copy that will be filled in later.

+ * + * @author Paul Speed + */ +public interface JmeCloneable extends Cloneable { + + /** + * Performs a regular shallow clone of the object. Some fields + * may also be cloned but generally only if they will never be + * shared with other objects. (For example, local Vector3fs and so on.) + * + *

This method is separate from the regular clone() method + * so that objects might still maintain their own regular java clone() + * semantics (perhaps even using Cloner for those methods). However, + * because Java's clone() has specific features in the sense of Object's + * clone() implementation, it's usually best to have some path for + * subclasses to bypass the public clone() method that might be cloning + * fields and instead get at the superclass protected clone() methods. + * For example, through super.jmeClone() or another protected clone + * method that some base class eventually calls super.clone() in.

+ */ + public Object jmeClone(); + + /** + * Implemented to perform deep cloning for this object, resolving + * local cloned references using the specified cloner. The object + * can call cloner.clone(fieldValue) to deep clone any of its fields. + * + *

Note: during normal clone operations the original object + * will not be needed as the clone has already had all of the fields + * shallow copied.

+ * + * @param cloner The cloner that is performing the cloning operation. The + * cloneFields method can call back into the cloner to make + * clones if its subordinate fields. + * @param original The original object from which this object was cloned. + * This is provided for the very rare case that this object needs + * to refer to its original for some reason. In general, all of + * the relevant values should have been transferred during the + * shallow clone and this object need merely clone what it wants. + */ + public void cloneFields( Cloner cloner, Object original ); +} diff --git a/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java b/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java new file mode 100644 index 000000000..a1f269d67 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java @@ -0,0 +1,70 @@ +/* + * 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.util.List; + +/** + * A CloneFunction implementation that deep clones a list by + * creating a new list and cloning its values using the cloner. + * + * @author Paul Speed + */ +public class ListCloneFunction implements CloneFunction { + + public T cloneObject( Cloner cloner, T object ) { + try { + T clone = cloner.javaClone(object); + return clone; + } catch( CloneNotSupportedException e ) { + throw new IllegalArgumentException("Clone not supported for type:" + object.getClass(), e); + } + } + + /** + * Clones the elements of the list. + */ + @SuppressWarnings("unchecked") + public void cloneFields( Cloner cloner, T clone, T object ) { + for( int i = 0; i < clone.size(); i++ ) { + // Need to clone the clones... because T might + // have done something special in its clone method that + // we will have to adhere to. For example, clone may have nulled + // out some things or whatever that might be implementation specific. + // At any rate, if it's a proper clone then the clone will already + // have shallow versions of the elements that we can clone. + clone.set(i, cloner.clone(clone.get(i))); + } + } +} + diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md index 1a3cbba07..570cf6864 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md @@ -113,6 +113,8 @@ MaterialDef Phong Lighting { //For instancing Boolean UseInstancing + + Boolean BackfaceShadows: false } Technique { @@ -212,26 +214,19 @@ MaterialDef Phong Lighting { INSTANCING : UseInstancing } - ForcedRenderState { - FaceCull Off - DepthTest On - DepthWrite On - PolyOffset 5 3 - ColorWrite Off - } - } Technique PostShadow15{ - VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert - FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag + VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag WorldParameters { WorldViewProjectionMatrix WorldMatrix ViewProjectionMatrix ViewMatrix + NormalMatrix } Defines { @@ -245,6 +240,7 @@ MaterialDef Phong Lighting { POINTLIGHT : LightViewProjectionMatrix5 NUM_BONES : NumberOfBones INSTANCING : UseInstancing + BACKFACE_SHADOWS: BackfaceShadows } ForcedRenderState { @@ -263,6 +259,7 @@ MaterialDef Phong Lighting { WorldMatrix ViewProjectionMatrix ViewMatrix + NormalMatrix } Defines { diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag index 254806d87..3ece6268f 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag @@ -41,8 +41,7 @@ varying vec3 SpecularSum; #ifdef NORMALMAP uniform sampler2D m_NormalMap; - varying vec3 vTangent; - varying vec3 vBinormal; + varying vec4 vTangent; #endif varying vec3 vNormal; @@ -71,7 +70,7 @@ uniform float m_Shininess; void main(){ #if !defined(VERTEX_LIGHTING) #if defined(NORMALMAP) - mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz)); + mat3 tbnMat = mat3(vTangent.xyz, vTangent.w * cross( (vNormal), (vTangent.xyz)), vNormal.xyz); if (!gl_FrontFacing) { diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert index 1fde8e13d..c607b3891 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert @@ -39,8 +39,7 @@ attribute vec3 inNormal; varying vec3 vPos; #ifdef NORMALMAP attribute vec4 inTangent; - varying vec3 vTangent; - varying vec3 vBinormal; + varying vec4 vTangent; #endif #else #ifdef COLORRAMP @@ -104,8 +103,7 @@ void main(){ #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) - vTangent = TransformNormal(modelSpaceTan); - vBinormal = cross(wvNormal, vTangent)* inTangent.w; + vTangent = vec4(TransformNormal(modelSpaceTan).xyz,inTangent.w); vNormal = wvNormal; vPos = wvPosition; #elif !defined(VERTEX_LIGHTING) diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md index 8dd6fc5ca..68f07f98d 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md @@ -51,6 +51,8 @@ MaterialDef Unshaded { Float PCFEdge Float ShadowMapSize + + Boolean BackfaceShadows: true } Technique { @@ -147,8 +149,8 @@ MaterialDef Unshaded { Technique PostShadow15{ - VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert - FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag + VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -169,6 +171,7 @@ MaterialDef Unshaded { POINTLIGHT : LightViewProjectionMatrix5 NUM_BONES : NumberOfBones INSTANCING : UseInstancing + BACKFACE_SHADOWS: BackfaceShadows } ForcedRenderState { @@ -201,6 +204,7 @@ MaterialDef Unshaded { POINTLIGHT : LightViewProjectionMatrix5 NUM_BONES : NumberOfBones INSTANCING : UseInstancing + BACKFACE_SHADOWS: BackfaceShadows } ForcedRenderState { diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag index 4e42c5784..a2d191895 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag @@ -1,4 +1,5 @@ #import "Common/ShaderLib/Shadows.glsllib" +#import "Common/ShaderLib/GLSLCompat.glsllib" #if defined(PSSM) || defined(FADE) varying float shadowPosition; @@ -8,6 +9,9 @@ varying vec4 projCoord0; varying vec4 projCoord1; varying vec4 projCoord2; varying vec4 projCoord3; +#ifndef BACKFACE_SHADOWS + varying float nDotL; +#endif #ifdef POINTLIGHT varying vec4 projCoord4; @@ -45,9 +49,15 @@ void main(){ if(alpha<=m_AlphaDiscardThreshold){ discard; } + #endif + #ifndef BACKFACE_SHADOWS + if(nDotL > 0.0){ + discard; + } #endif - + + float shadow = 1.0; #ifdef POINTLIGHT @@ -70,11 +80,11 @@ void main(){ #endif #ifdef FADE - shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y)); + shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y)); #endif - shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity); - gl_FragColor = vec4(shadow, shadow, shadow, 1.0); + shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity); + gl_FragColor = vec4(shadow, shadow, shadow, 1.0); } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md index af80bdae3..928637adf 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md @@ -29,12 +29,14 @@ MaterialDef Post Shadow { Float PCFEdge Float ShadowMapSize + + Boolean BackfaceShadows: false } Technique { - VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert - FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag + VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -49,6 +51,7 @@ MaterialDef Post Shadow { FADE : FadeInfo PSSM : Splits POINTLIGHT : LightViewProjectionMatrix5 + BACKFACE_SHADOWS: BackfaceShadows } RenderState { @@ -75,6 +78,7 @@ MaterialDef Post Shadow { FADE : FadeInfo PSSM : Splits POINTLIGHT : LightViewProjectionMatrix5 + BACKFACE_SHADOWS: BackfaceShadows } RenderState { diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert index 482231032..de4490820 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert @@ -1,11 +1,12 @@ #import "Common/ShaderLib/Instancing.glsllib" #import "Common/ShaderLib/Skinning.glsllib" +#import "Common/ShaderLib/GLSLCompat.glsllib" + uniform mat4 m_LightViewProjectionMatrix0; uniform mat4 m_LightViewProjectionMatrix1; uniform mat4 m_LightViewProjectionMatrix2; uniform mat4 m_LightViewProjectionMatrix3; -uniform vec3 m_LightPos; varying vec4 projCoord0; varying vec4 projCoord1; @@ -15,12 +16,14 @@ varying vec4 projCoord3; #ifdef POINTLIGHT uniform mat4 m_LightViewProjectionMatrix4; uniform mat4 m_LightViewProjectionMatrix5; + uniform vec3 m_LightPos; varying vec4 projCoord4; varying vec4 projCoord5; varying vec4 worldPos; #else + uniform vec3 m_LightDir; #ifndef PSSM - uniform vec3 m_LightDir; + uniform vec3 m_LightPos; varying float lightDot; #endif #endif @@ -28,12 +31,15 @@ varying vec4 projCoord3; #if defined(PSSM) || defined(FADE) varying float shadowPosition; #endif -varying vec3 lightVec; varying vec2 texCoord; - attribute vec3 inPosition; +#ifndef BACKFACE_SHADOWS + attribute vec3 inNormal; + varying float nDotL; +#endif + #ifdef DISCARD_ALPHA attribute vec2 inTexCoord; #endif @@ -51,16 +57,17 @@ void main(){ Skinning_Compute(modelSpacePos); #endif gl_Position = TransformWorldViewProjection(modelSpacePos); + vec3 lightDir; #if defined(PSSM) || defined(FADE) - shadowPosition = gl_Position.z; + shadowPosition = gl_Position.z; #endif #ifndef POINTLIGHT vec4 worldPos=vec4(0.0); #endif // get the vertex in world space - worldPos = g_WorldMatrix * modelSpacePos; + worldPos = TransformWorld(modelSpacePos); #ifdef DISCARD_ALPHA texCoord = inTexCoord; @@ -75,8 +82,21 @@ void main(){ projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos; #else #ifndef PSSM - vec3 lightDir = worldPos.xyz - m_LightPos; + //Spot light + lightDir = worldPos.xyz - m_LightPos; lightDot = dot(m_LightDir,lightDir); #endif #endif + + #ifndef BACKFACE_SHADOWS + vec3 normal = normalize(TransformWorld(vec4(inNormal,0.0))).xyz; + #ifdef POINTLIGHT + lightDir = worldPos.xyz - m_LightPos; + #else + #ifdef PSSM + lightDir = m_LightDir; + #endif + #endif + nDotL = dot(normal, lightDir); + #endif } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag deleted file mode 100644 index 2eb9541e5..000000000 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag +++ /dev/null @@ -1,80 +0,0 @@ -#import "Common/ShaderLib/Shadows15.glsllib" - -out vec4 outFragColor; - -#if defined(PSSM) || defined(FADE) -in float shadowPosition; -#endif - -in vec4 projCoord0; -in vec4 projCoord1; -in vec4 projCoord2; -in vec4 projCoord3; - -#ifdef POINTLIGHT - in vec4 projCoord4; - in vec4 projCoord5; - in vec4 worldPos; - uniform vec3 m_LightPos; -#else - #ifndef PSSM - in float lightDot; - #endif -#endif - -#ifdef DISCARD_ALPHA - #ifdef COLOR_MAP - uniform sampler2D m_ColorMap; - #else - uniform sampler2D m_DiffuseMap; - #endif - uniform float m_AlphaDiscardThreshold; - varying vec2 texCoord; -#endif - -#ifdef FADE -uniform vec2 m_FadeInfo; -#endif - -void main(){ - - #ifdef DISCARD_ALPHA - #ifdef COLOR_MAP - float alpha = texture2D(m_ColorMap,texCoord).a; - #else - float alpha = texture2D(m_DiffuseMap,texCoord).a; - #endif - - if(alpha < m_AlphaDiscardThreshold){ - discard; - } - #endif - - float shadow = 1.0; - #ifdef POINTLIGHT - shadow = getPointLightShadows(worldPos, m_LightPos, - m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5, - projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5); - #else - #ifdef PSSM - shadow = getDirectionalLightShadows(m_Splits, shadowPosition, - m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3, - projCoord0, projCoord1, projCoord2, projCoord3); - #else - //spotlight - if(lightDot < 0){ - outFragColor = vec4(1.0); - return; - } - shadow = getSpotLightShadows(m_ShadowMap0,projCoord0); - #endif - #endif - - #ifdef FADE - shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y)); - #endif - - shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity); - outFragColor = vec4(shadow, shadow, shadow, 1.0); -} - diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert deleted file mode 100644 index 20565de7c..000000000 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert +++ /dev/null @@ -1,82 +0,0 @@ -#import "Common/ShaderLib/Instancing.glsllib" -#import "Common/ShaderLib/Skinning.glsllib" -uniform mat4 m_LightViewProjectionMatrix0; -uniform mat4 m_LightViewProjectionMatrix1; -uniform mat4 m_LightViewProjectionMatrix2; -uniform mat4 m_LightViewProjectionMatrix3; - - -out vec4 projCoord0; -out vec4 projCoord1; -out vec4 projCoord2; -out vec4 projCoord3; - -#ifdef POINTLIGHT - uniform mat4 m_LightViewProjectionMatrix4; - uniform mat4 m_LightViewProjectionMatrix5; - out vec4 projCoord4; - out vec4 projCoord5; - out vec4 worldPos; -#else - #ifndef PSSM - uniform vec3 m_LightPos; - uniform vec3 m_LightDir; - out float lightDot; - #endif -#endif - -#if defined(PSSM) || defined(FADE) - out float shadowPosition; -#endif -out vec3 lightVec; - -out vec2 texCoord; - -in vec3 inPosition; - -#ifdef DISCARD_ALPHA - in vec2 inTexCoord; -#endif - -const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0, - 0.0, 0.5, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.0, - 0.5, 0.5, 0.5, 1.0); - - -void main(){ - vec4 modelSpacePos = vec4(inPosition, 1.0); - - #ifdef NUM_BONES - Skinning_Compute(modelSpacePos); - #endif - gl_Position = TransformWorldViewProjection(modelSpacePos); - - #if defined(PSSM) || defined(FADE) - shadowPosition = gl_Position.z; - #endif - - #ifndef POINTLIGHT - vec4 worldPos=vec4(0.0); - #endif - // get the vertex in world space - worldPos = TransformWorld(modelSpacePos); - - #ifdef DISCARD_ALPHA - texCoord = inTexCoord; - #endif - // populate the light view matrices array and convert vertex to light viewProj space - projCoord0 = biasMat * m_LightViewProjectionMatrix0 * worldPos; - projCoord1 = biasMat * m_LightViewProjectionMatrix1 * worldPos; - projCoord2 = biasMat * m_LightViewProjectionMatrix2 * worldPos; - projCoord3 = biasMat * m_LightViewProjectionMatrix3 * worldPos; - #ifdef POINTLIGHT - projCoord4 = biasMat * m_LightViewProjectionMatrix4 * worldPos; - projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos; - #else - #ifndef PSSM - vec3 lightDir = worldPos.xyz - m_LightPos; - lightDot = dot(m_LightDir,lightDir); - #endif - #endif -} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag index 74e59482b..b144dca60 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag @@ -18,14 +18,16 @@ uniform mat4 m_LightViewProjectionMatrix1; uniform mat4 m_LightViewProjectionMatrix2; uniform mat4 m_LightViewProjectionMatrix3; +uniform vec2 g_ResolutionInverse; + #ifdef POINTLIGHT uniform vec3 m_LightPos; uniform mat4 m_LightViewProjectionMatrix4; uniform mat4 m_LightViewProjectionMatrix5; #else + uniform vec3 m_LightDir; #ifndef PSSM - uniform vec3 m_LightPos; - uniform vec3 m_LightDir; + uniform vec3 m_LightPos; #endif #endif @@ -39,6 +41,19 @@ vec3 getPosition(in float depth, in vec2 uv){ return pos.xyz / pos.w; } +vec3 approximateNormal(in vec4 worldPos,in vec2 texCoord){ + float step = g_ResolutionInverse.x ; + float stepy = g_ResolutionInverse.y ; + float depth2 = texture2D(m_DepthTexture,texCoord + vec2(step,-stepy)).r; + float depth3 = texture2D(m_DepthTexture,texCoord + vec2(-step,-stepy)).r; + vec4 worldPos2 = vec4(getPosition(depth2,texCoord + vec2(step,-stepy)),1.0); + vec4 worldPos3 = vec4(getPosition(depth3,texCoord + vec2(-step,-stepy)),1.0); + + vec3 v1 = (worldPos - worldPos2).xyz; + vec3 v2 = (worldPos3 - worldPos2).xyz; + return normalize(cross(v1, v2)); +} + void main(){ #if !defined( RENDER_SHADOWS ) gl_FragColor = texture2D(m_Texture,texCoord); @@ -48,6 +63,7 @@ void main(){ float depth = texture2D(m_DepthTexture,texCoord).r; vec4 color = texture2D(m_Texture,texCoord); + //Discard shadow computation on the sky if(depth == 1.0){ gl_FragColor = color; @@ -56,6 +72,19 @@ void main(){ // get the vertex in world space vec4 worldPos = vec4(getPosition(depth,texCoord),1.0); + vec3 normal = approximateNormal(worldPos, texCoord); + + vec3 lightDir; + #ifdef PSSM + lightDir = m_LightDir; + #else + lightDir = worldPos.xyz - m_LightPos; + #endif + float ndotl = dot(normal, lightDir); + if(ndotl > -0.0){ + gl_FragColor = color; + return; + } #if (!defined(POINTLIGHT) && !defined(PSSM)) vec3 lightDir = worldPos.xyz - m_LightPos; diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md index 9a78752ae..ced2f9fdb 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md @@ -38,13 +38,15 @@ MaterialDef Post Shadow { Texture2D Texture Texture2D DepthTexture + Boolean BackfaceShadows: true } Technique { VertexShader GLSL150: Common/MatDefs/Shadow/PostShadowFilter15.vert FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadowFilter15.frag - WorldParameters { + WorldParameters { + ResolutionInverse } Defines { @@ -59,7 +61,7 @@ MaterialDef Post Shadow { POINTLIGHT : LightViewProjectionMatrix5 //if no shadow map don't render shadows RENDER_SHADOWS : ShadowMap0 - + BACKFACE_SHADOWS : BackfaceShadows } } @@ -68,7 +70,8 @@ MaterialDef Post Shadow { VertexShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.vert FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.frag - WorldParameters { + WorldParameters { + ResolutionInverse } Defines { @@ -79,6 +82,7 @@ MaterialDef Post Shadow { FADE : FadeInfo PSSM : Splits POINTLIGHT : LightViewProjectionMatrix5 + BACKFACE_SHADOWS : BackfaceShadows } } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag index b8dcff90f..054382cd6 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag @@ -1,5 +1,5 @@ #import "Common/ShaderLib/MultiSample.glsllib" -#import "Common/ShaderLib/Shadows15.glsllib" +#import "Common/ShaderLib/Shadows.glsllib" uniform COLORTEXTURE m_Texture; @@ -20,14 +20,16 @@ uniform mat4 m_LightViewProjectionMatrix1; uniform mat4 m_LightViewProjectionMatrix2; uniform mat4 m_LightViewProjectionMatrix3; +uniform vec2 g_ResolutionInverse; + #ifdef POINTLIGHT uniform vec3 m_LightPos; uniform mat4 m_LightViewProjectionMatrix4; uniform mat4 m_LightViewProjectionMatrix5; #else + uniform vec3 m_LightDir; #ifndef PSSM - uniform vec3 m_LightPos; - uniform vec3 m_LightDir; + uniform vec3 m_LightPos; #endif #endif @@ -41,6 +43,23 @@ vec3 getPosition(in float depth, in vec2 uv){ return pos.xyz / pos.w; } +#ifndef BACKFACE_SHADOWS + vec3 approximateNormal(in float depth,in vec4 worldPos,in vec2 texCoord, in int numSample){ + float step = g_ResolutionInverse.x ; + float stepy = g_ResolutionInverse.y ; + float depth1 = fetchTextureSample(m_DepthTexture,texCoord + vec2(-step,stepy),numSample).r; + float depth2 = fetchTextureSample(m_DepthTexture,texCoord + vec2(step,stepy),numSample).r; + vec3 v1, v2; + vec4 worldPos1 = vec4(getPosition(depth1,texCoord + vec2(-step,stepy)),1.0); + vec4 worldPos2 = vec4(getPosition(depth2,texCoord + vec2(step,stepy)),1.0); + + v1 = normalize((worldPos1 - worldPos)).xyz; + v2 = normalize((worldPos2 - worldPos)).xyz; + return normalize(cross(v2, v1)); + + } +#endif + vec4 main_multiSample(in int numSample){ float depth = fetchTextureSample(m_DepthTexture,texCoord,numSample).r;//getDepth(m_DepthTexture,texCoord).r; vec4 color = fetchTextureSample(m_Texture,texCoord,numSample); @@ -52,12 +71,27 @@ vec4 main_multiSample(in int numSample){ // get the vertex in world space vec4 worldPos = vec4(getPosition(depth,texCoord),1.0); - + + + vec3 lightDir; + #ifdef PSSM + lightDir = m_LightDir; + #else + lightDir = worldPos.xyz - m_LightPos; + #endif + + #ifndef BACKFACE_SHADOWS + vec3 normal = approximateNormal(depth, worldPos, texCoord, numSample); + float ndotl = dot(normal, lightDir); + if(ndotl > 0.0){ + return color; + } + #endif + #if (!defined(POINTLIGHT) && !defined(PSSM)) - vec3 lightDir = worldPos.xyz - m_LightPos; - if( dot(m_LightDir,lightDir)<0){ - return color; - } + if( dot(m_LightDir,lightDir)<0){ + return color; + } #endif // populate the light view matrices array and convert vertex to light viewProj space diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib index d2f1a942b..9934db637 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib @@ -1,22 +1,57 @@ -#ifdef HARDWARE_SHADOWS - #define SHADOWMAP sampler2DShadow - #define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r -#else - #define SHADOWMAP sampler2D - #define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r) -#endif +#if __VERSION__ >= 130 + // Because gpu_shader5 is actually where those + // gather functions are declared to work on shadowmaps + #extension GL_ARB_gpu_shader5 : enable + #define IVEC2 ivec2 + #ifdef HARDWARE_SHADOWS + #define SHADOWMAP sampler2DShadow + #define SHADOWCOMPAREOFFSET(tex,coord,offset) textureProjOffset(tex, coord, offset) + #define SHADOWCOMPARE(tex,coord) textureProj(tex, coord) + #define SHADOWGATHER(tex,coord) textureGather(tex, coord.xy, coord.z) + #else + #define SHADOWMAP sampler2D + #define SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z, textureProjOffset(tex, coord, offset).r) + #define SHADOWCOMPARE(tex,coord) step(coord.z, textureProj(tex, coord).r) + #define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy)) + #endif -#if FILTER_MODE == 0 - #define GETSHADOW Shadow_DoShadowCompare - #define KERNEL 1.0 -#elif FILTER_MODE == 1 + #if FILTER_MODE == 0 + #define GETSHADOW Shadow_Nearest + #define KERNEL 1.0 + #elif FILTER_MODE == 1 + #ifdef HARDWARE_SHADOWS + #define GETSHADOW Shadow_Nearest + #else + #define GETSHADOW Shadow_DoBilinear_2x2 + #endif + #define KERNEL 1.0 + #endif +#else + #define IVEC2 vec2 #ifdef HARDWARE_SHADOWS - #define GETSHADOW Shadow_DoShadowCompare + #define SHADOWMAP sampler2DShadow + #define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r #else - #define GETSHADOW Shadow_DoBilinear_2x2 + #define SHADOWMAP sampler2D + #define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r) #endif - #define KERNEL 1.0 -#elif FILTER_MODE == 2 + + #if FILTER_MODE == 0 + #define GETSHADOW Shadow_DoShadowCompare + #define KERNEL 1.0 + #elif FILTER_MODE == 1 + #ifdef HARDWARE_SHADOWS + #define GETSHADOW Shadow_DoShadowCompare + #else + #define GETSHADOW Shadow_DoBilinear_2x2 + #endif + #define KERNEL 1.0 + #endif + + +#endif + +#if FILTER_MODE == 2 #define GETSHADOW Shadow_DoDither_2x2 #define KERNEL 1.0 #elif FILTER_MODE == 3 @@ -30,14 +65,13 @@ #define KERNEL 8.0 #endif - uniform SHADOWMAP m_ShadowMap0; uniform SHADOWMAP m_ShadowMap1; uniform SHADOWMAP m_ShadowMap2; uniform SHADOWMAP m_ShadowMap3; #ifdef POINTLIGHT -uniform SHADOWMAP m_ShadowMap4; -uniform SHADOWMAP m_ShadowMap5; + uniform SHADOWMAP m_ShadowMap4; + uniform SHADOWMAP m_ShadowMap5; #endif #ifdef PSSM @@ -49,73 +83,91 @@ uniform float m_ShadowIntensity; const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE); float shadowBorderScale = 1.0; -float Shadow_DoShadowCompareOffset(SHADOWMAP tex, vec4 projCoord, vec2 offset){ - vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2 * shadowBorderScale, projCoord.zw); - return SHADOWCOMPARE(tex, coord); -} - -float Shadow_DoShadowCompare(SHADOWMAP tex, vec4 projCoord){ +float Shadow_DoShadowCompare(in SHADOWMAP tex,in vec4 projCoord){ return SHADOWCOMPARE(tex, projCoord); } -float Shadow_BorderCheck(vec2 coord){ +float Shadow_BorderCheck(in vec2 coord){ // Fastest, "hack" method (uses 4-5 instructions) vec4 t = vec4(coord.xy, 0.0, 1.0); t = step(t.wwxy, t.xyzz); return dot(t,t); } -float Shadow_Nearest(SHADOWMAP tex, vec4 projCoord){ +float Shadow_Nearest(in SHADOWMAP tex,in vec4 projCoord){ float border = Shadow_BorderCheck(projCoord.xy); if (border > 0.0){ return 1.0; } - return Shadow_DoShadowCompare(tex,projCoord); + return SHADOWCOMPARE(tex, projCoord); +} + +float Shadow_DoShadowCompareOffset(in SHADOWMAP tex,in vec4 projCoord,in vec2 offset){ + vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2 * shadowBorderScale, projCoord.zw); + return SHADOWCOMPARE(tex, coord); } -float Shadow_DoDither_2x2(SHADOWMAP tex, vec4 projCoord){ + +float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){ float border = Shadow_BorderCheck(projCoord.xy); if (border > 0.0) return 1.0; - float shadow = 0.0; - vec2 o = mod(floor(gl_FragCoord.xy), 2.0); - shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5, 1.5) + o); - shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5, 1.5) + o); - shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5, -0.5) + o); - shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5, -0.5) + o); - shadow *= 0.25 ; + IVEC2 o = IVEC2(mod(floor(gl_FragCoord.xy), 2.0)); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2(-1.5, 1.5)+o)); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2( 0.5, 1.5)+o)); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2(-1.5, -0.5)+o)); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2( 0.5, -0.5)+o)); + shadow *= 0.25; return shadow; } -float Shadow_DoBilinear_2x2(SHADOWMAP tex, vec4 projCoord){ +float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){ float border = Shadow_BorderCheck(projCoord.xy); - if (border > 0.0) + if (border > 0.0){ return 1.0; + } + vec4 gather = vec4(0.0); - gather.x = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 0.0)); - gather.y = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 0.0)); - gather.z = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 1.0)); - gather.w = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 1.0)); - - vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE ); - vec2 mx = mix( gather.xz, gather.yw, f.x ); - return mix( mx.x, mx.y, f.y ); + #if __VERSION__ >= 130 + #ifdef GL_ARB_gpu_shader5 + vec4 coord = vec4(projCoord.xyz / projCoord.www,0.0); + gather = SHADOWGATHER(tex, coord); + #else + gather.x = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 1)); + gather.y = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 1)); + gather.z = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 0)); + gather.w = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 0)); + #endif + #else + gather.x = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 0.0)); + gather.y = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 0.0)); + gather.z = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 1.0)); + gather.w = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 1.0)); + #endif + + vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE ); + vec2 mx = mix( gather.wx, gather.zy, f.x ); + return mix( mx.x, mx.y, f.y ); } -float Shadow_DoPCF(SHADOWMAP tex, vec4 projCoord){ +float Shadow_DoPCF(in SHADOWMAP tex,in vec4 projCoord){ + float shadow = 0.0; float border = Shadow_BorderCheck(projCoord.xy); if (border > 0.0) return 1.0; + float bound = KERNEL * 0.5 - 0.5; bound *= PCFEDGE; for (float y = -bound; y <= bound; y += PCFEDGE){ for (float x = -bound; x <= bound; x += PCFEDGE){ - shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) + - border, - 0.0, 1.0); + #if __VERSION__ < 130 + shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) + border, 0.0, 1.0); + #else + shadow += Shadow_DoShadowCompareOffset(tex, projCoord, vec2(x,y)); + #endif } } @@ -123,51 +175,51 @@ float Shadow_DoPCF(SHADOWMAP tex, vec4 projCoord){ return shadow; } - //12 tap poisson disk - const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016); - const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201); - const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521); - const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819); - const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957); - const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841); - const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516); - const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126); - const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693); - const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707); - const vec2 poissonDisk10 = vec2(0.5653144, 0.60262); - const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419); - -float Shadow_DoPCFPoisson(SHADOWMAP tex, vec4 projCoord){ +const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016); +const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201); +const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521); +const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819); +const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957); +const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841); +const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516); +const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126); +const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693); +const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707); +const vec2 poissonDisk10 = vec2(0.5653144, 0.60262); +const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419); + + +float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){ float shadow = 0.0; float border = Shadow_BorderCheck(projCoord.xy); - if (border > 0.0) + if (border > 0.0){ return 1.0; + } - vec2 texelSize = vec2( 4.0 * PCFEDGE * shadowBorderScale); - - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk0 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk1 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk2 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk3 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk4 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk5 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk6 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk7 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk8 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk9 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk10 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk11 * texelSize); - - shadow = shadow * 0.08333333333;//this is divided by 12 - return shadow; -} + vec2 texelSize = pixSize2 * 4.0 * PCFEDGE * shadowBorderScale; + + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk0 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk1 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk2 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk3 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk4 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk5 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk6 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk7 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk8 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk9 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk10 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk11 * texelSize, projCoord.zw)); + //this is divided by 12 + return shadow * 0.08333333333; +} -#ifdef POINTLIGHT - float getPointLightShadows(vec4 worldPos,vec3 lightPos, - SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3,SHADOWMAP shadowMap4,SHADOWMAP shadowMap5, - vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3,vec4 projCoord4,vec4 projCoord5){ +#ifdef POINTLIGHT + float getPointLightShadows(in vec4 worldPos,in vec3 lightPos, + in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,in SHADOWMAP shadowMap4,in SHADOWMAP shadowMap5, + in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3,in vec4 projCoord4,in vec4 projCoord5){ float shadow = 1.0; vec3 vect = worldPos.xyz - lightPos; vec3 absv= abs(vect); @@ -190,42 +242,41 @@ float Shadow_DoPCFPoisson(SHADOWMAP tex, vec4 projCoord){ }else{ shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w); } - } + } return shadow; } #else #ifdef PSSM - float getDirectionalLightShadows(vec4 splits,float shadowPosition, - SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3, - vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3){ - float shadow = 1.0; + float getDirectionalLightShadows(in vec4 splits,in float shadowPosition, + in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3, + in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3){ + float shadow = 1.0; if(shadowPosition < splits.x){ - shadow = GETSHADOW(shadowMap0, projCoord0 ); + shadow = GETSHADOW(shadowMap0, projCoord0 ); }else if( shadowPosition < splits.y){ shadowBorderScale = 0.5; - shadow = GETSHADOW(shadowMap1, projCoord1); + shadow = GETSHADOW(shadowMap1, projCoord1); }else if( shadowPosition < splits.z){ shadowBorderScale = 0.25; - shadow = GETSHADOW(shadowMap2, projCoord2); + shadow = GETSHADOW(shadowMap2, projCoord2); }else if( shadowPosition < splits.w){ shadowBorderScale = 0.125; - shadow = GETSHADOW(shadowMap3, projCoord3); + shadow = GETSHADOW(shadowMap3, projCoord3); } return shadow; } #else - float getSpotLightShadows(SHADOWMAP shadowMap, vec4 projCoord){ - float shadow = 1.0; + float getSpotLightShadows(in SHADOWMAP shadowMap,in vec4 projCoord){ + float shadow = 1.0; projCoord /= projCoord.w; - shadow = GETSHADOW(shadowMap, projCoord); - + shadow = GETSHADOW(shadowMap,projCoord); + //a small falloff to make the shadow blend nicely into the not lighten - //we translate the texture coordinate value to a -1,1 range so the length + //we translate the texture coordinate value to a -1,1 range so the length //of the texture coordinate vector is actually the radius of the lighten area on the ground projCoord = projCoord * 2.0 - 1.0; float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1; return mix(shadow,1.0,clamp(fallOff,0.0,1.0)); - } #endif #endif diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib deleted file mode 100644 index 5c9e0fa1c..000000000 --- a/jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib +++ /dev/null @@ -1,242 +0,0 @@ -// Because gpu_shader5 is actually where those -// gather functions are declared to work on shadowmaps -#extension GL_ARB_gpu_shader5 : enable - -#ifdef HARDWARE_SHADOWS - #define SHADOWMAP sampler2DShadow - #define SHADOWCOMPAREOFFSET(tex,coord,offset) textureProjOffset(tex, coord, offset) - #define SHADOWCOMPARE(tex,coord) textureProj(tex, coord) - #define SHADOWGATHER(tex,coord) textureGather(tex, coord.xy, coord.z) -#else - #define SHADOWMAP sampler2D - #define SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z, textureProjOffset(tex, coord, offset).r) - #define SHADOWCOMPARE(tex,coord) step(coord.z, textureProj(tex, coord).r) - #define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy)) -#endif - - -#if FILTER_MODE == 0 - #define GETSHADOW Shadow_Nearest - #define KERNEL 1.0 -#elif FILTER_MODE == 1 - #ifdef HARDWARE_SHADOWS - #define GETSHADOW Shadow_Nearest - #else - #define GETSHADOW Shadow_DoBilinear_2x2 - #endif - #define KERNEL 1.0 -#elif FILTER_MODE == 2 - #define GETSHADOW Shadow_DoDither_2x2 - #define KERNEL 1.0 -#elif FILTER_MODE == 3 - #define GETSHADOW Shadow_DoPCF - #define KERNEL 4.0 -#elif FILTER_MODE == 4 - #define GETSHADOW Shadow_DoPCFPoisson - #define KERNEL 4.0 -#elif FILTER_MODE == 5 - #define GETSHADOW Shadow_DoPCF - #define KERNEL 8.0 -#endif - - - -uniform SHADOWMAP m_ShadowMap0; -uniform SHADOWMAP m_ShadowMap1; -uniform SHADOWMAP m_ShadowMap2; -uniform SHADOWMAP m_ShadowMap3; -#ifdef POINTLIGHT -uniform SHADOWMAP m_ShadowMap4; -uniform SHADOWMAP m_ShadowMap5; -#endif - -#ifdef PSSM -uniform vec4 m_Splits; -#endif -uniform float m_ShadowIntensity; - -const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE); -float shadowBorderScale = 1.0; - -float Shadow_BorderCheck(in vec2 coord){ - // Fastest, "hack" method (uses 4-5 instructions) - vec4 t = vec4(coord.xy, 0.0, 1.0); - t = step(t.wwxy, t.xyzz); - return dot(t,t); -} - -float Shadow_Nearest(in SHADOWMAP tex, in vec4 projCoord){ - float border = Shadow_BorderCheck(projCoord.xy); - if (border > 0.0){ - return 1.0; - } - return SHADOWCOMPARE(tex,projCoord); -} - -float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){ - float border = Shadow_BorderCheck(projCoord.xy); - if (border > 0.0) - return 1.0; - - vec2 pixSize = pixSize2 * shadowBorderScale; - - float shadow = 0.0; - ivec2 o = ivec2(mod(floor(gl_FragCoord.xy), 2.0)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2(-1.5, 1.5)+o), projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2( 0.5, 1.5)+o), projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2(-1.5, -0.5)+o), projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2( 0.5, -0.5)+o), projCoord.zw)); - shadow *= 0.25; - return shadow; -} - -float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){ - float border = Shadow_BorderCheck(projCoord.xy); - if (border > 0.0) - return 1.0; - - #ifdef GL_ARB_gpu_shader5 - vec4 coord = vec4(projCoord.xyz / projCoord.www,0.0); - vec4 gather = SHADOWGATHER(tex, coord); - #else - vec4 gather = vec4(0.0); - gather.x = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 1)); - gather.y = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 1)); - gather.z = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 0)); - gather.w = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 0)); - #endif - - vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE ); - vec2 mx = mix( gather.wx, gather.zy, f.x ); - return mix( mx.x, mx.y, f.y ); -} - -float Shadow_DoPCF(in SHADOWMAP tex, in vec4 projCoord){ - - vec2 pixSize = pixSize2 * shadowBorderScale; - float shadow = 0.0; - float border = Shadow_BorderCheck(projCoord.xy); - if (border > 0.0) - return 1.0; - - float bound = KERNEL * 0.5 - 0.5; - bound *= PCFEDGE; - for (float y = -bound; y <= bound; y += PCFEDGE){ - for (float x = -bound; x <= bound; x += PCFEDGE){ - vec4 coord = vec4(projCoord.xy + vec2(x,y) * pixSize, projCoord.zw); - shadow += SHADOWCOMPARE(tex, coord); - } - } - - shadow = shadow / (KERNEL * KERNEL); - return shadow; -} - - -//12 tap poisson disk - const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016); - const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201); - const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521); - const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819); - const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957); - const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841); - const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516); - const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126); - const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693); - const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707); - const vec2 poissonDisk10 = vec2(0.5653144, 0.60262); - const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419); - - -float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){ - - float shadow = 0.0; - float border = Shadow_BorderCheck(projCoord.xy); - if (border > 0.0){ - return 1.0; - } - - vec2 texelSize = pixSize2 * 4.0 * PCFEDGE * shadowBorderScale; - - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk0 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk1 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk2 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk3 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk4 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk5 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk6 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk7 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk8 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk9 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk10 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk11 * texelSize, projCoord.zw)); - - //this is divided by 12 - return shadow * 0.08333333333; -} - -#ifdef POINTLIGHT - float getPointLightShadows(in vec4 worldPos,in vec3 lightPos, - in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,in SHADOWMAP shadowMap4,in SHADOWMAP shadowMap5, - in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3,in vec4 projCoord4,in vec4 projCoord5){ - float shadow = 1.0; - vec3 vect = worldPos.xyz - lightPos; - vec3 absv= abs(vect); - float maxComp = max(absv.x,max(absv.y,absv.z)); - if(maxComp == absv.y){ - if(vect.y < 0.0){ - shadow = GETSHADOW(shadowMap0, projCoord0 / projCoord0.w); - }else{ - shadow = GETSHADOW(shadowMap1, projCoord1 / projCoord1.w); - } - }else if(maxComp == absv.z){ - if(vect.z < 0.0){ - shadow = GETSHADOW(shadowMap2, projCoord2 / projCoord2.w); - }else{ - shadow = GETSHADOW(shadowMap3, projCoord3 / projCoord3.w); - } - }else if(maxComp == absv.x){ - if(vect.x < 0.0){ - shadow = GETSHADOW(shadowMap4, projCoord4 / projCoord4.w); - }else{ - shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w); - } - } - return shadow; - } -#else - #ifdef PSSM - float getDirectionalLightShadows(in vec4 splits,in float shadowPosition, - in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3, - in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3){ - float shadow = 1.0; - if(shadowPosition < splits.x){ - shadow = GETSHADOW(shadowMap0, projCoord0 ); - }else if( shadowPosition < splits.y){ - shadowBorderScale = 0.5; - shadow = GETSHADOW(shadowMap1, projCoord1); - }else if( shadowPosition < splits.z){ - shadowBorderScale = 0.25; - shadow = GETSHADOW(shadowMap2, projCoord2); - }else if( shadowPosition < splits.w){ - shadowBorderScale = 0.125; - shadow = GETSHADOW(shadowMap3, projCoord3); - } - return shadow; - } - #else - float getSpotLightShadows(in SHADOWMAP shadowMap,in vec4 projCoord){ - float shadow = 1.0; - projCoord /= projCoord.w; - shadow = GETSHADOW(shadowMap,projCoord); - - //a small falloff to make the shadow blend nicely into the not lighten - //we translate the texture coordinate value to a -1,1 range so the length - //of the texture coordinate vector is actually the radius of the lighten area on the ground - projCoord = projCoord * 2.0 - 1.0; - float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1; - return mix(shadow,1.0,clamp(fallOff,0.0,1.0)); - - } - #endif -#endif diff --git a/jme3-core/src/main/resources/com/jme3/system/.gitignore b/jme3-core/src/main/resources/com/jme3/system/.gitignore new file mode 100644 index 000000000..13ee572a7 --- /dev/null +++ b/jme3-core/src/main/resources/com/jme3/system/.gitignore @@ -0,0 +1 @@ +version.properties diff --git a/jme3-core/src/main/resources/com/jme3/system/version.properties b/jme3-core/src/main/resources/com/jme3/system/version.properties deleted file mode 100644 index 98168a14e..000000000 --- a/jme3-core/src/main/resources/com/jme3/system/version.properties +++ /dev/null @@ -1,11 +0,0 @@ -# THIS IS AN AUTO-GENERATED FILE.. -# DO NOT MODIFY! -build.date=1900-01-01 -git.revision=0 -git.branch=unknown -git.hash= -git.hash.short= -git.tag= -name.full=jMonkeyEngine 3.1.0-UNKNOWN -version.number=3.1.0 -version.tag=SNAPSHOT \ No newline at end of file diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java index f5125a7a1..8b0f831c5 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java @@ -178,9 +178,23 @@ public class J3MLoader implements AssetLoader { return matchList; } - private boolean isTexturePathDeclaredTheTraditionalWay(final int numberOfValues, final int numberOfTextureOptions, final String texturePath) { - return (numberOfValues > 1 && (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Flip ") || - texturePath.startsWith("Repeat ") || texturePath.startsWith("Repeat Flip "))) || numberOfTextureOptions == 0; + private boolean isTexturePathDeclaredTheTraditionalWay(final List optionValues, final String texturePath) { + final boolean startsWithOldStyle = texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Flip ") || + texturePath.startsWith("Repeat ") || texturePath.startsWith("Repeat Flip "); + + if (!startsWithOldStyle) { + return false; + } + + if (optionValues.size() == 1 && (optionValues.get(0).textureOption == TextureOption.Flip || optionValues.get(0).textureOption == TextureOption.Repeat)) { + return true; + } else if (optionValues.size() == 2 && optionValues.get(0).textureOption == TextureOption.Flip && optionValues.get(1).textureOption == TextureOption.Repeat) { + return true; + } else if (optionValues.size() == 2 && optionValues.get(0).textureOption == TextureOption.Repeat && optionValues.get(1).textureOption == TextureOption.Flip) { + return true; + } + + return false; } private Texture parseTextureType(final VarType type, final String value) { @@ -196,7 +210,7 @@ public class J3MLoader implements AssetLoader { String texturePath = value.trim(); // If there are no valid "new" texture options specified but the path is split into several parts, lets parse the old way. - if (isTexturePathDeclaredTheTraditionalWay(textureValues.size(), textureOptionValues.size(), texturePath)) { + if (isTexturePathDeclaredTheTraditionalWay(textureOptionValues, texturePath)) { boolean flipY = false; if (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Repeat Flip ")) { @@ -453,6 +467,8 @@ public class J3MLoader implements AssetLoader { renderState.setDepthFunc(RenderState.TestFunction.valueOf(split[1])); }else if (split[0].equals("AlphaFunc")){ renderState.setAlphaFunc(RenderState.TestFunction.valueOf(split[1])); + }else if (split[0].equals("LineWidth")){ + renderState.setLineWidth(Float.parseFloat(split[1])); } else { throw new MatParseException(null, split[0], statement); } @@ -679,6 +695,7 @@ public class J3MLoader implements AssetLoader { material = new Material(def); material.setKey(key); + material.setName(split[0].trim()); // material.setAssetName(fileName); }else if (split.length == 1){ if (extending){ diff --git a/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java b/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java index 6438baaac..f45eb9bdf 100644 --- a/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java +++ b/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java @@ -80,6 +80,7 @@ public class J3MLoaderTest { final Texture textureMin = Mockito.mock(Texture.class); final Texture textureMag = Mockito.mock(Texture.class); final Texture textureCombined = Mockito.mock(Texture.class); + final Texture textureLooksLikeOldStyle = Mockito.mock(Texture.class); final TextureKey textureKeyNoParameters = setupMockForTexture("Empty", "empty.png", false, textureNoParameters); final TextureKey textureKeyFlip = setupMockForTexture("Flip", "flip.png", true, textureFlip); @@ -88,6 +89,7 @@ public class J3MLoaderTest { setupMockForTexture("Min", "min.png", false, textureMin); setupMockForTexture("Mag", "mag.png", false, textureMag); setupMockForTexture("Combined", "combined.png", true, textureCombined); + setupMockForTexture("LooksLikeOldStyle", "oldstyle.png", true, textureLooksLikeOldStyle); j3MLoader.load(assetInfo); diff --git a/jme3-core/src/test/resources/texture-parameters-newstyle.j3m b/jme3-core/src/test/resources/texture-parameters-newstyle.j3m index a7619d948..e4b15ad4a 100644 --- a/jme3-core/src/test/resources/texture-parameters-newstyle.j3m +++ b/jme3-core/src/test/resources/texture-parameters-newstyle.j3m @@ -6,6 +6,7 @@ Material Test : matdef.j3md { Min: MinTrilinear "min.png" Mag: MagBilinear "mag.png" RepeatAxis: WrapRepeat_T "repeat-axis.png" - Combined: MagNearest MinBilinearNoMipMaps Flip WrapRepeat "combined.png" + Combined: Flip MagNearest MinBilinearNoMipMaps WrapRepeat "combined.png" + LooksLikeOldStyle: Flip WrapRepeat "oldstyle.png" } } \ No newline at end of file diff --git a/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java b/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java index 18fc82a61..159bd95ac 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java +++ b/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java @@ -50,12 +50,12 @@ import javax.swing.SwingUtilities; */ public class AppletHarness extends Applet { - public static final HashMap appToApplet - = new HashMap(); + public static final HashMap appToApplet + = new HashMap(); protected JmeCanvasContext context; protected Canvas canvas; - protected Application app; + protected LegacyApplication app; protected String appClass; protected URL appCfg = null; @@ -103,7 +103,7 @@ public class AppletHarness extends Applet { JmeSystem.setLowPermissions(true); try{ - Class clazz = (Class) Class.forName(appClass); + Class clazz = (Class) Class.forName(appClass); app = clazz.newInstance(); }catch (ClassNotFoundException ex){ ex.printStackTrace(); diff --git a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert index 00aa103ff..3b09ec310 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert +++ b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert @@ -20,5 +20,5 @@ void main(void) Skinning_Compute(modelSpacePos,modelSpaceNormals); #endif normal = normalize(TransformNormal(modelSpaceNormals)); - gl_Position = g_WorldViewProjectionMatrix * modelSpacePos; + gl_Position = TransformWorldViewProjection(modelSpacePos); } \ No newline at end of file diff --git a/jme3-examples/build.gradle b/jme3-examples/build.gradle index 371a7aaab..ce7a7cbd6 100644 --- a/jme3-examples/build.gradle +++ b/jme3-examples/build.gradle @@ -11,6 +11,9 @@ task run(dependsOn: 'build', type:JavaExec) { jvmArgs "-XstartOnFirstThread" jvmArgs "-Djava.awt.headless=true" } + + systemProperty "java.util.logging.config.file", System.getProperty("java.util.logging.config.file") + if( assertions == "true" ){ enableAssertions = true; } diff --git a/jme3-examples/src/main/java/jme3test/TestChooser.java b/jme3-examples/src/main/java/jme3test/TestChooser.java index a2251945e..f5f54958e 100644 --- a/jme3-examples/src/main/java/jme3test/TestChooser.java +++ b/jme3-examples/src/main/java/jme3test/TestChooser.java @@ -260,8 +260,8 @@ public class TestChooser extends JDialog { for (int i = 0; i < appClass.length; i++) { Class clazz = (Class)appClass[i]; try { - Object app = clazz.newInstance(); - if (app instanceof Application) { + if (Application.class.isAssignableFrom(clazz)) { + Object app = clazz.newInstance(); if (app instanceof SimpleApplication) { final Method settingMethod = clazz.getMethod("setShowSettings", boolean.class); settingMethod.invoke(app, showSetting); @@ -283,7 +283,7 @@ public class TestChooser extends JDialog { } } else { final Method mainMethod = clazz.getMethod("main", (new String[0]).getClass()); - mainMethod.invoke(app, new Object[]{new String[0]}); + mainMethod.invoke(clazz, new Object[]{new String[0]}); } // wait for destroy System.gc(); diff --git a/jme3-examples/src/main/java/jme3test/app/TestApplication.java b/jme3-examples/src/main/java/jme3test/app/TestApplication.java index e5306fdc8..1559318bf 100644 --- a/jme3-examples/src/main/java/jme3test/app/TestApplication.java +++ b/jme3-examples/src/main/java/jme3test/app/TestApplication.java @@ -32,19 +32,19 @@ package jme3test.app; -import com.jme3.app.Application; +import com.jme3.app.LegacyApplication; import com.jme3.system.AppSettings; import com.jme3.system.JmeContext.Type; /** - * Test Application functionality, such as create, restart, destroy, etc. + * Test LegacyApplication functionality, such as create, restart, destroy, etc. * @author Kirill */ public class TestApplication { public static void main(String[] args) throws InterruptedException{ System.out.println("Creating application.."); - Application app = new Application(); + LegacyApplication app = new LegacyApplication(); System.out.println("Starting application in LWJGL mode.."); app.start(); System.out.println("Waiting 5 seconds"); @@ -54,7 +54,7 @@ public class TestApplication { Thread.sleep(2000); System.out.println("Starting in fullscreen mode"); - app = new Application(); + app = new LegacyApplication(); AppSettings settings = new AppSettings(true); settings.setFullscreen(true); settings.setResolution(-1,-1); // current width/height @@ -65,7 +65,7 @@ public class TestApplication { Thread.sleep(2000); System.out.println("Creating offscreen buffer application"); - app = new Application(); + app = new LegacyApplication(); app.start(Type.OffscreenSurface); Thread.sleep(3000); System.out.println("Destroying offscreen buffer"); diff --git a/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java b/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java index cf80c9005..5d3298279 100644 --- a/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java +++ b/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java @@ -32,7 +32,7 @@ package jme3test.app; -import com.jme3.app.Application; +import com.jme3.app.LegacyApplication; import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; import com.jme3.scene.shape.Box; @@ -40,7 +40,7 @@ import com.jme3.scene.shape.Box; /** * Test a bare-bones application, without SimpleApplication. */ -public class TestBareBonesApp extends Application { +public class TestBareBonesApp extends LegacyApplication { private Geometry boxGeom; @@ -72,7 +72,7 @@ public class TestBareBonesApp extends Application { // do some animation float tpf = timer.getTimePerFrame(); boxGeom.rotate(tpf * 2, tpf * 4, tpf * 3); - + // dont forget to update the scenes boxGeom.updateLogicalState(tpf); boxGeom.updateGeometricState(); diff --git a/jme3-examples/src/main/java/jme3test/app/TestCloneSpatial.java b/jme3-examples/src/main/java/jme3test/app/TestCloneSpatial.java new file mode 100644 index 000000000..b55262a96 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestCloneSpatial.java @@ -0,0 +1,213 @@ +/* + * 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 jme3test.app; + +import java.lang.reflect.*; +import java.util.*; + +import com.jme3.light.*; +import com.jme3.material.*; +import com.jme3.math.*; +import com.jme3.scene.*; +import com.jme3.scene.control.*; +import com.jme3.scene.shape.*; +import com.jme3.util.clone.*; + + +/** + * + * + * @author Paul Speed + */ +public class TestCloneSpatial { + + public static void main( String... args ) throws Exception { + + // Setup a test node with some children, controls, etc. + Node root = new Node("rootNode"); + + // A root light + DirectionalLight rootLight = new DirectionalLight(); + root.addLight(rootLight); + + Box sharedBox = new Box(1, 1, 1); + Geometry geom1 = new Geometry("box1", sharedBox); + Material sharedMaterial = new Material(); // not a valid material, just for testing + geom1.setMaterial(sharedMaterial); + + Geometry geom2 = new Geometry("box2", sharedBox); + geom2.setMaterial(sharedMaterial); + + root.attachChild(geom1); + root.attachChild(geom2); + + // Add some controls + geom1.addControl(new BillboardControl()); + geom2.addControl(new BillboardControl()); + + // A light that will only affect the children and be controlled + // by one child + PointLight childLight = new PointLight(); + geom1.addLight(childLight); + geom2.addLight(childLight); + + geom1.addControl(new LightControl(childLight)); + + // Set some shared user data also + Vector3f sharedUserData = new Vector3f(1, 2, 3); + geom1.setUserData("shared", sharedUserData); + geom2.setUserData("shared", sharedUserData); + + dump("", root); + + System.out.println("-------- cloning spatial --------------"); + Node clone = root.clone(true); + dump("", clone); + + System.out.println("-------- cloning spatial without cloning material --------------"); + clone = root.clone(false); + dump("", clone); + } + + + /** + * Debug dump to check structure and identity + */ + public static void dump( String indent, Spatial s ) { + if( s instanceof Node ) { + dump(indent, (Node)s); + } else if( s instanceof Geometry ) { + dump(indent, (Geometry)s); + } + } + + public static void dump( String indent, Node n ) { + System.out.println(indent + objectToString(n)); + dumpSpatialProperties(indent + " ", n); + if( !n.getChildren().isEmpty() ) { + System.out.println(indent + " children:"); + for( Spatial s : n.getChildren() ) { + dump(indent + " ", s); + } + } + } + + public static void dump( String indent, Geometry g ) { + System.out.println(indent + objectToString(g)); + //System.out.println(indent + " mesh:" + objectToString(g.getMesh())); + //System.out.println(indent + " material:" + objectToString(g.getMaterial())); + dumpSpatialProperties(indent + " ", g); + } + + public static void dump( String indent, Control ctl ) { + System.out.println(indent + objectToString(ctl)); + if( ctl instanceof AbstractControl ) { + System.out.println(indent + " spatial:" + objectToString(((AbstractControl)ctl).getSpatial())); + } + } + + private static void dumpSpatialProperties( String indent, Spatial s ) { + dumpProperties(indent, s, "children"); + + if( !s.getUserDataKeys().isEmpty() ) { + System.out.println(indent + "userData:"); + for( String key : s.getUserDataKeys() ) { + System.out.println(indent + " " + key + ":" + objectToString(s.getUserData(key))); + } + } + + if( s.getNumControls() > 0 ) { + System.out.println(indent + "controls:"); + for( int i = 0; i < s.getNumControls(); i++ ) { + Control ctl = s.getControl(i); + //dump(indent + " ", ctl); + dumpObject(indent + " ", ctl); + } + } + + LightList lights = s.getLocalLightList(); + if( lights.size() > 0 ) { + System.out.println(indent + "lights:"); + for( Light l : lights ) { + dumpObject(indent + " ", l); + } + } + } + + private static void dumpObject( String indent, Object o ) { + System.out.println(indent + objectToString(o)); + dumpProperties(indent + " ", o); + } + + private static void dumpProperties( String indent, Object o, String... skip ) { + if( o == null ) { + return; + } + Set skipSet = new HashSet<>(Arrays.asList(skip)); + for( Method m : o.getClass().getMethods() ) { + if( m.getParameterTypes().length > 0 ) { + continue; + } + String name = m.getName(); + if( "getClass".equals(name) ) { + continue; + } + if( !name.startsWith("get") ) { + continue; + } + Class type = m.getReturnType(); + if( type.isPrimitive() || type.isEnum() ) { + continue; + } + name = name.substring(3); + if( skipSet.contains(name.toLowerCase()) ) { + continue; + } + try { + Object value = m.invoke(o); + System.out.println(indent + name + ":" + objectToString(value)); + } catch( Exception e ) { + throw new RuntimeException("Error with method:" + m, e); + } + } + } + + private static String objectToString( Object o ) { + if( o == null ) { + return null; + } + String s = o + "@" + System.identityHashCode(o); + s = s.replaceAll("\\r?\\n", ""); + return s; + } +} diff --git a/jme3-examples/src/main/java/jme3test/app/TestCloner.java b/jme3-examples/src/main/java/jme3test/app/TestCloner.java new file mode 100644 index 000000000..4ae105603 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestCloner.java @@ -0,0 +1,367 @@ +/* + * 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 jme3test.app; + +import java.util.*; + +import com.jme3.util.clone.*; + +/** + * + * + * @author Paul Speed + */ +public class TestCloner { + + public static void main( String... args ) { + + System.out.println("Clone test:"); + + Cloner cloner = new Cloner(); + + RegularObject ro = new RegularObject(42); + System.out.println("Regular Object:" + ro); + RegularObject roCloneLegacy = ro.clone(); + System.out.println("Regular Object Clone:" + roCloneLegacy); + RegularObject roClone = cloner.clone(ro); + System.out.println("cloner: Regular Object Clone:" + roClone); + + System.out.println("------------------------------------"); + System.out.println(); + + cloner = new Cloner(); + RegularSubclass rsc = new RegularSubclass(69, "test"); + System.out.println("Regular subclass:" + rsc); + RegularSubclass rscCloneLegacy = (RegularSubclass)rsc.clone(); + System.out.println("Regular subclass Clone:" + rscCloneLegacy); + RegularSubclass rscClone = cloner.clone(rsc); + System.out.println("cloner: Regular subclass Clone:" + rscClone); + + System.out.println("------------------------------------"); + System.out.println(); + + cloner = new Cloner(); + Parent parent = new Parent("Foo", 34); + System.out.println("Parent:" + parent); + Parent parentCloneLegacy = parent.clone(); + System.out.println("Parent Clone:" + parentCloneLegacy); + Parent parentClone = cloner.clone(parent); + System.out.println("cloner: Parent Clone:" + parentClone); + + System.out.println("------------------------------------"); + System.out.println(); + + cloner = new Cloner(); + GraphNode root = new GraphNode("root"); + GraphNode child1 = root.addLink("child1"); + GraphNode child2 = root.addLink("child2"); + GraphNode shared = child1.addLink("shared"); + child2.addLink(shared); + + // Add a circular reference to get fancy + shared.addLink(root); + + System.out.println("Simple graph:"); + root.dump(" "); + + GraphNode rootClone = cloner.clone(root); + System.out.println("clone:"); + rootClone.dump(" "); + + System.out.println("original:"); + root.dump(" "); + + GraphNode reclone = Cloner.deepClone(root); + System.out.println("reclone:"); + reclone.dump(" "); + + System.out.println("------------------------------------"); + System.out.println(); + cloner = new Cloner(); + + ArrayHolder arrays = new ArrayHolder(5, 3, 7, 3, 7, 2, 1, 4); + System.out.println("Array holder:" + arrays); + ArrayHolder arraysClone = cloner.clone(arrays); + System.out.println("Array holder clone:" + arraysClone); + + + + } + + public static class RegularObject implements Cloneable { + protected int i; + + public RegularObject( int i ) { + this.i = i; + } + + public RegularObject clone() { + try { + return (RegularObject)super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException(e); + } + } + + public String toString() { + return getClass().getSimpleName() + "@" + System.identityHashCode(this) + + "[i=" + i + "]"; + } + } + + public static class RegularSubclass extends RegularObject { + protected String name; + + public RegularSubclass( int i, String name ) { + super(i); + this.name = name; + } + + public String toString() { + return getClass().getSimpleName() + "@" + System.identityHashCode(this) + + "[i=" + i + ", name=" + name + "]"; + } + } + + public static class Parent implements Cloneable, JmeCloneable { + + private RegularObject ro; + private RegularSubclass rsc; + + public Parent( String name, int age ) { + this.ro = new RegularObject(age); + this.rsc = new RegularSubclass(age, name); + } + + public Parent clone() { + try { + return (Parent)super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException(e); + } + } + + public Parent jmeClone() { + // Ok to delegate to clone() in this case because no deep + // cloning is done there. + return clone(); + } + + public void cloneFields( Cloner cloner, Object original ) { + this.ro = cloner.clone(ro); + this.rsc = cloner.clone(rsc); + } + + public String toString() { + return getClass().getSimpleName() + "@" + System.identityHashCode(this) + + "[ro=" + ro + ", rsc=" + rsc + "]"; + } + } + + public static class GraphNode implements Cloneable, JmeCloneable { + + private String name; + private List links = new ArrayList<>(); + + public GraphNode( String name ) { + this.name = name; + } + + public void dump( String indent ) { + dump(indent, new HashSet()); + } + + private void dump( String indent, Set visited ) { + if( visited.contains(this) ) { + // already been here + System.out.println(indent + this + " ** circular."); + return; + } + System.out.println(indent + this); + visited.add(this); + for( GraphNode n : links ) { + n.dump(indent + " ", visited); + } + visited.remove(this); + } + + public GraphNode addLink( String name ) { + GraphNode node = new GraphNode(name); + links.add(node); + return node; + } + + public GraphNode addLink( GraphNode node ) { + links.add(node); + return node; + } + + public List getLinks() { + return links; + } + + public GraphNode jmeClone() { + try { + return (GraphNode)super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException(e); + } + } + + public void cloneFields( Cloner cloner, Object original ) { + this.links = cloner.clone(links); + } + + public String toString() { + return getClass().getSimpleName() + "@" + System.identityHashCode(this) + + "[name=" + name + "]"; + } + } + + public static class ArrayHolder implements JmeCloneable { + + private int[] intArray; + private int[][] intArray2D; + private Object[] objects; + private RegularObject[] regularObjects; + private String[] strings; + + public ArrayHolder( int... values ) { + this.intArray = values; + this.intArray2D = new int[values.length][2]; + for( int i = 0; i < values.length; i++ ) { + intArray2D[i][0] = values[i] + 1; + intArray2D[i][1] = values[i] * 2; + } + this.objects = new Object[values.length]; + this.regularObjects = new RegularObject[values.length]; + this.strings = new String[values.length]; + for( int i = 0; i < values.length; i++ ) { + objects[i] = values[i]; + regularObjects[i] = new RegularObject(values[i]); + strings[i] = String.valueOf(values[i]); + } + } + + public ArrayHolder jmeClone() { + try { + return (ArrayHolder)super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException(e); + } + } + + public void cloneFields( Cloner cloner, Object original ) { + intArray = cloner.clone(intArray); + intArray2D = cloner.clone(intArray2D); + + // Boxed types are not cloneable so this will fail + //objects = cloner.clone(objects); + + regularObjects = cloner.clone(regularObjects); + + // Strings are also not cloneable + //strings = cloner.clone(strings); + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("intArray=" + intArray); + for( int i = 0; i < intArray.length; i++ ) { + if( i == 0 ) { + sb.append("["); + } else { + sb.append(", "); + } + sb.append(intArray[i]); + } + sb.append("], "); + + sb.append("intArray2D=" + intArray2D); + for( int i = 0; i < intArray2D.length; i++ ) { + if( i == 0 ) { + sb.append("["); + } else { + sb.append(", "); + } + sb.append("intArray2D[" + i + "]=" + intArray2D[i]); + for( int j = 0; j < 2; j++ ) { + if( j == 0 ) { + sb.append("["); + } else { + sb.append(", "); + } + sb.append(intArray2D[i][j]); + } + sb.append("], "); + } + sb.append("], "); + + sb.append("objectArray=" + objects); + for( int i = 0; i < objects.length; i++ ) { + if( i == 0 ) { + sb.append("["); + } else { + sb.append(", "); + } + sb.append(objects[i]); + } + sb.append("], "); + + sb.append("objectArray=" + regularObjects); + for( int i = 0; i < regularObjects.length; i++ ) { + if( i == 0 ) { + sb.append("["); + } else { + sb.append(", "); + } + sb.append(regularObjects[i]); + } + sb.append("], "); + + sb.append("stringArray=" + strings); + for( int i = 0; i < strings.length; i++ ) { + if( i == 0 ) { + sb.append("["); + } else { + sb.append(", "); + } + sb.append(strings[i]); + } + sb.append("]"); + + return getClass().getSimpleName() + "@" + System.identityHashCode(this) + + "[" + sb + "]"; + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/app/TestContextRestart.java b/jme3-examples/src/main/java/jme3test/app/TestContextRestart.java index b7e6634e1..0baaef3d6 100644 --- a/jme3-examples/src/main/java/jme3test/app/TestContextRestart.java +++ b/jme3-examples/src/main/java/jme3test/app/TestContextRestart.java @@ -32,7 +32,7 @@ package jme3test.app; -import com.jme3.app.Application; +import com.jme3.app.LegacyApplication; import com.jme3.system.AppSettings; public class TestContextRestart { @@ -40,7 +40,7 @@ public class TestContextRestart { public static void main(String[] args) throws InterruptedException{ AppSettings settings = new AppSettings(true); - final Application app = new Application(); + final LegacyApplication app = new LegacyApplication(); app.setSettings(settings); app.start(); diff --git a/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java b/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java index 1ae7a2ada..a19e2add8 100644 --- a/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java +++ b/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java @@ -32,13 +32,13 @@ package jme3test.app.state; -import com.jme3.app.Application; +import com.jme3.app.LegacyApplication; import com.jme3.niftygui.NiftyJmeDisplay; import com.jme3.scene.Spatial; import com.jme3.system.AppSettings; import com.jme3.system.JmeContext; -public class TestAppStates extends Application { +public class TestAppStates extends LegacyApplication { public static void main(String[] args){ TestAppStates app = new TestAppStates(); @@ -50,7 +50,7 @@ public class TestAppStates extends Application { AppSettings settings = new AppSettings(true); settings.setResolution(1024, 768); setSettings(settings); - + super.start(contextType); } diff --git a/jme3-examples/src/main/java/jme3test/awt/AppHarness.java b/jme3-examples/src/main/java/jme3test/awt/AppHarness.java index 862f0ab74..77e8d7092 100644 --- a/jme3-examples/src/main/java/jme3test/awt/AppHarness.java +++ b/jme3-examples/src/main/java/jme3test/awt/AppHarness.java @@ -32,7 +32,7 @@ package jme3test.awt; -import com.jme3.app.Application; +import com.jme3.app.LegacyApplication; import com.jme3.system.AppSettings; import com.jme3.system.JmeCanvasContext; import com.jme3.system.JmeSystem; @@ -53,7 +53,7 @@ public class AppHarness extends Applet { private JmeCanvasContext context; private Canvas canvas; - private Application app; + private LegacyApplication app; private String appClass; private URL appCfg = null; @@ -79,7 +79,7 @@ public class AppHarness extends Applet { JmeSystem.setLowPermissions(true); try{ - Class clazz = (Class) Class.forName(appClass); + Class clazz = (Class) Class.forName(appClass); app = clazz.newInstance(); }catch (ClassNotFoundException ex){ ex.printStackTrace(); @@ -91,11 +91,11 @@ public class AppHarness extends Applet { app.setSettings(settings); app.createCanvas(); - + context = (JmeCanvasContext) app.getContext(); canvas = context.getCanvas(); canvas.setSize(getWidth(), getHeight()); - + add(canvas); app.startCanvas(); } @@ -110,14 +110,14 @@ public class AppHarness extends Applet { appClass = getParameter("AppClass"); if (appClass == null) throw new RuntimeException("The required parameter AppClass isn't specified!"); - + try { appCfg = new URL(getParameter("AppSettingsURL")); } catch (MalformedURLException ex) { ex.printStackTrace(); appCfg = null; } - + createCanvas(); System.out.println("applet:init"); } diff --git a/jme3-examples/src/main/java/jme3test/awt/TestApplet.java b/jme3-examples/src/main/java/jme3test/awt/TestApplet.java index 1d5019d8b..8ac4b7f0a 100644 --- a/jme3-examples/src/main/java/jme3test/awt/TestApplet.java +++ b/jme3-examples/src/main/java/jme3test/awt/TestApplet.java @@ -32,7 +32,7 @@ package jme3test.awt; -import com.jme3.app.Application; +import com.jme3.app.LegacyApplication; import com.jme3.app.SimpleApplication; import com.jme3.system.AppSettings; import com.jme3.system.JmeCanvasContext; @@ -46,7 +46,7 @@ import javax.swing.SwingUtilities; public class TestApplet extends Applet { private static JmeCanvasContext context; - private static Application app; + private static LegacyApplication app; private static Canvas canvas; private static TestApplet applet; @@ -62,7 +62,7 @@ public class TestApplet extends Applet { JmeSystem.setLowPermissions(true); try{ - Class clazz = (Class) Class.forName(appClass); + Class clazz = (Class) Class.forName(appClass); app = clazz.newInstance(); }catch (ClassNotFoundException ex){ ex.printStackTrace(); @@ -74,7 +74,7 @@ public class TestApplet extends Applet { app.setSettings(settings); app.createCanvas(); - + context = (JmeCanvasContext) app.getContext(); canvas = context.getCanvas(); canvas.setSize(settings.getWidth(), settings.getHeight()); diff --git a/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java b/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java index 7e7b58a36..be4e5e8f6 100644 --- a/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java +++ b/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java @@ -32,7 +32,7 @@ package jme3test.awt; -import com.jme3.app.Application; +import com.jme3.app.LegacyApplication; import com.jme3.app.SimpleApplication; import com.jme3.system.AppSettings; import com.jme3.system.JmeCanvasContext; @@ -55,7 +55,7 @@ public class TestCanvas { private static JmeCanvasContext context; private static Canvas canvas; - private static Application app; + private static LegacyApplication app; private static JFrame frame; private static Container canvasPanel1, canvasPanel2; private static Container currentPanel; @@ -64,20 +64,20 @@ public class TestCanvas { private static void createTabs(){ tabbedPane = new JTabbedPane(); - + canvasPanel1 = new JPanel(); canvasPanel1.setLayout(new BorderLayout()); tabbedPane.addTab("jME3 Canvas 1", canvasPanel1); - + canvasPanel2 = new JPanel(); canvasPanel2.setLayout(new BorderLayout()); tabbedPane.addTab("jME3 Canvas 2", canvasPanel2); - + frame.getContentPane().add(tabbedPane); - + currentPanel = canvasPanel1; } - + private static void createMenu(){ JMenuBar menuBar = new JMenuBar(); frame.setJMenuBar(menuBar); @@ -95,12 +95,12 @@ public class TestCanvas { itemRemoveCanvas.setText("Add Canvas"); }else if (itemRemoveCanvas.getText().equals("Add Canvas")){ currentPanel.add(canvas, BorderLayout.CENTER); - + itemRemoveCanvas.setText("Remove Canvas"); } } }); - + final JMenuItem itemHideCanvas = new JMenuItem("Hide Canvas"); menuTortureMethods.add(itemHideCanvas); itemHideCanvas.addActionListener(new ActionListener() { @@ -114,7 +114,7 @@ public class TestCanvas { } } }); - + final JMenuItem itemSwitchTab = new JMenuItem("Switch to tab #2"); menuTortureMethods.add(itemSwitchTab); itemSwitchTab.addActionListener(new ActionListener(){ @@ -130,9 +130,9 @@ public class TestCanvas { currentPanel = canvasPanel1; itemSwitchTab.setText("Switch to tab #2"); } - } + } }); - + JMenuItem itemSwitchLaf = new JMenuItem("Switch Look and Feel"); menuTortureMethods.add(itemSwitchLaf); itemSwitchLaf.addActionListener(new ActionListener(){ @@ -146,7 +146,7 @@ public class TestCanvas { frame.pack(); } }); - + JMenuItem itemSmallSize = new JMenuItem("Set size to (0, 0)"); menuTortureMethods.add(itemSmallSize); itemSmallSize.addActionListener(new ActionListener(){ @@ -157,7 +157,7 @@ public class TestCanvas { frame.setPreferredSize(preferred); } }); - + JMenuItem itemKillCanvas = new JMenuItem("Stop/Start Canvas"); menuTortureMethods.add(itemKillCanvas); itemKillCanvas.addActionListener(new ActionListener() { @@ -181,7 +181,7 @@ public class TestCanvas { } }); } - + private static void createFrame(){ frame = new JFrame("Test"); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); @@ -202,7 +202,7 @@ public class TestCanvas { settings.setHeight(480); try{ - Class clazz = (Class) Class.forName(appClass); + Class clazz = (Class) Class.forName(appClass); app = clazz.newInstance(); }catch (ClassNotFoundException ex){ ex.printStackTrace(); @@ -233,7 +233,7 @@ public class TestCanvas { return null; } }); - + } public static void main(String[] args){ @@ -244,20 +244,20 @@ public class TestCanvas { Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]); Logger.getLogger("").addHandler(consoleHandler); - + createCanvas(appClass); - + try { Thread.sleep(500); } catch (InterruptedException ex) { } - + SwingUtilities.invokeLater(new Runnable(){ public void run(){ JPopupMenu.setDefaultLightWeightPopupEnabled(false); createFrame(); - + currentPanel.add(canvas, BorderLayout.CENTER); frame.pack(); startApp(); diff --git a/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java b/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java index 469fb03ae..a286501bb 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java +++ b/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java @@ -46,13 +46,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; /** * PhysicsHoverControl uses a RayCast Vehicle with "slippery wheels" to simulate a hovering tank * @author normenhansen */ -public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsControl, PhysicsTickListener { +public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsControl, PhysicsTickListener, JmeCloneable { protected Spatial spatial; protected boolean enabled = true; @@ -94,10 +96,21 @@ public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsContro createWheels(); } + @Override public Control cloneForSpatial(Spatial spatial) { throw new UnsupportedOperationException("Not supported yet."); } + @Override + public Object 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) { this.spatial = spatial; setUserObject(spatial); @@ -179,6 +192,7 @@ public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsContro } public void setPhysicsSpace(PhysicsSpace space) { + createVehicle(space); if (space == null) { if (this.space != null) { this.space.removeCollisionObject(this); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java b/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java index 5f058e68b..d1def4203 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java @@ -147,13 +147,13 @@ public class TestHoveringTank extends SimpleApplication implements AnalogListene spaceCraft.setLocalRotation(new Quaternion(new float[]{0, 0.01f, 0})); hoverControl = new PhysicsHoverControl(colShape, 500); - hoverControl.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02); spaceCraft.addControl(hoverControl); rootNode.attachChild(spaceCraft); getPhysicsSpace().add(hoverControl); + hoverControl.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02); ChaseCamera chaseCam = new ChaseCamera(cam, inputManager); spaceCraft.addControl(chaseCam); diff --git a/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java b/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java index 84d186e3a..f86f956b6 100644 --- a/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java +++ b/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java @@ -128,12 +128,12 @@ public class TestMousePick extends SimpleApplication { /** A red ball that marks the last spot that was "hit" by the "shot". */ protected void initMark() { Arrow arrow = new Arrow(Vector3f.UNIT_Z.mult(2f)); - arrow.setLineWidth(3); //Sphere sphere = new Sphere(30, 30, 0.2f); mark = new Geometry("BOOM!", arrow); //mark = new Geometry("BOOM!", sphere); Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mark_mat.getAdditionalRenderState().setLineWidth(3); mark_mat.setColor("Color", ColorRGBA.Red); mark.setMaterial(mark_mat); } diff --git a/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java b/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java index eaf516fae..14f220374 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java +++ b/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java @@ -40,12 +40,14 @@ import com.jme3.input.controls.KeyTrigger; import com.jme3.light.AmbientLight; import com.jme3.light.DirectionalLight; import com.jme3.material.Material; +import com.jme3.material.RenderState; import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; import com.jme3.math.Quaternion; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.post.FilterPostProcessor; +import com.jme3.post.ssao.SSAOFilter; import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; @@ -69,6 +71,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act private Geometry ground; private Material matGroundU; private Material matGroundL; + private AmbientLight al; public static void main(String[] args) { TestDirectionalLightShadow app = new TestDirectionalLightShadow(); @@ -99,7 +102,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act mat[0] = assetManager.loadMaterial("Common/Materials/RedColor.j3m"); mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); mat[1].setBoolean("UseMaterialColors", true); - mat[1].setColor("Ambient", ColorRGBA.White.mult(0.5f)); + mat[1].setColor("Ambient", ColorRGBA.White); mat[1].setColor("Diffuse", ColorRGBA.White.clone()); @@ -110,9 +113,14 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act TangentBinormalGenerator.generate(obj[1]); TangentBinormalGenerator.generate(obj[0]); + Spatial t = obj[0].clone(false); + t.setLocalScale(10f); + t.setMaterial(mat[1]); + rootNode.attachChild(t); + t.setLocalTranslation(0, 25, 0); for (int i = 0; i < 60; i++) { - Spatial t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false); + t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false); t.setLocalScale(FastMath.nextRandomFloat() * 10f); t.setMaterial(mat[FastMath.nextRandomInt(0, mat.length - 1)]); rootNode.attachChild(t); @@ -142,8 +150,8 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act rootNode.addLight(l); - AmbientLight al = new AmbientLight(); - al.setColor(ColorRGBA.White.mult(0.5f)); + al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(0.02f)); rootNode.addLight(al); Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false); @@ -156,8 +164,11 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act @Override public void simpleInitApp() { // put the camera in a bad position - cam.setLocation(new Vector3f(65.25412f, 44.38738f, 9.087874f)); - cam.setRotation(new Quaternion(0.078139365f, 0.050241485f, -0.003942559f, 0.9956679f)); +// cam.setLocation(new Vector3f(65.25412f, 44.38738f, 9.087874f)); +// cam.setRotation(new Quaternion(0.078139365f, 0.050241485f, -0.003942559f, 0.9956679f)); + + cam.setLocation(new Vector3f(3.3720117f, 42.838284f, -83.43792f)); + cam.setRotation(new Quaternion(0.13833192f, -0.08969371f, 0.012581267f, 0.9862358f)); flyCam.setMoveSpeed(100); @@ -166,7 +177,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 3); dlsr.setLight(l); dlsr.setLambda(0.55f); - dlsr.setShadowIntensity(0.6f); + dlsr.setShadowIntensity(0.8f); dlsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest); dlsr.displayDebug(); viewPort.addProcessor(dlsr); @@ -174,7 +185,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act dlsf = new DirectionalLightShadowFilter(assetManager, SHADOWMAP_SIZE, 3); dlsf.setLight(l); dlsf.setLambda(0.55f); - dlsf.setShadowIntensity(0.6f); + dlsf.setShadowIntensity(0.8f); dlsf.setEdgeFilteringMode(EdgeFilteringMode.Nearest); dlsf.setEnabled(false); @@ -205,10 +216,11 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act inputManager.addMapping("fwd", new KeyTrigger(KeyInput.KEY_PGUP)); inputManager.addMapping("back", new KeyTrigger(KeyInput.KEY_PGDN)); inputManager.addMapping("pp", new KeyTrigger(KeyInput.KEY_P)); + inputManager.addMapping("backShadows", new KeyTrigger(KeyInput.KEY_B)); inputManager.addListener(this, "lambdaUp", "lambdaDown", "ThicknessUp", "ThicknessDown", - "switchGroundMat", "debug", "up", "down", "right", "left", "fwd", "back", "pp", "stabilize", "distance"); + "switchGroundMat", "debug", "up", "down", "right", "left", "fwd", "back", "pp", "stabilize", "distance", "ShadowUp", "ShadowDown", "backShadows"); ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, dlsr, dlsf, guiNode, inputManager, viewPort); @@ -255,12 +267,19 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act dlsf.setLambda(dlsr.getLambda() - 0.01f); System.out.println("Lambda : " + dlsr.getLambda()); } - + if ((name.equals("ShadowUp") || name.equals("ShadowDown")) && keyPressed) { + al.setColor(ColorRGBA.White.mult((1 - dlsr.getShadowIntensity()) * 0.2f)); + } if (name.equals("debug") && keyPressed) { dlsr.displayFrustum(); } + if (name.equals("backShadows") && keyPressed) { + dlsr.setRenderBackFacesShadows(!dlsr.isRenderBackFacesShadows()); + dlsf.setRenderBackFacesShadows(!dlsf.isRenderBackFacesShadows()); + } + if (name.equals("stabilize") && keyPressed) { dlsr.setEnabledStabilization(!dlsr.isEnabledStabilization()); dlsf.setEnabledStabilization(!dlsf.isEnabledStabilization()); diff --git a/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java b/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java index a98ac03db..80079711d 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java +++ b/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java @@ -32,7 +32,10 @@ package jme3test.light; import com.jme3.app.SimpleApplication; +import com.jme3.input.controls.ActionListener; +import com.jme3.light.AmbientLight; import com.jme3.light.PointLight; +import com.jme3.math.ColorRGBA; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.post.FilterPostProcessor; @@ -45,7 +48,7 @@ import com.jme3.shadow.EdgeFilteringMode; import com.jme3.shadow.PointLightShadowFilter; import com.jme3.shadow.PointLightShadowRenderer; -public class TestPointLightShadows extends SimpleApplication { +public class TestPointLightShadows extends SimpleApplication implements ActionListener{ public static final int SHADOWMAP_SIZE = 512; public static void main(String[] args) { @@ -55,13 +58,19 @@ public class TestPointLightShadows extends SimpleApplication { Node lightNode; PointLightShadowRenderer plsr; PointLightShadowFilter plsf; + AmbientLight al; @Override - public void simpleInitApp() { + public void simpleInitApp () { flyCam.setMoveSpeed(10); cam.setLocation(new Vector3f(0.040581334f, 1.7745866f, 6.155161f)); cam.setRotation(new Quaternion(4.3868728E-5f, 0.9999293f, -0.011230096f, 0.0039059948f)); + al = new AmbientLight(ColorRGBA.White.mult(0.02f)); + rootNode.addLight(al); + + + Node scene = (Node) assetManager.loadModel("Models/Test/CornellBox.j3o"); scene.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); @@ -89,6 +98,7 @@ public class TestPointLightShadows extends SimpleApplication { plsr.setEdgeFilteringMode(EdgeFilteringMode.PCF4); plsr.setShadowZExtend(15); plsr.setShadowZFadeLength(5); + plsr.setShadowIntensity(0.9f); // plsr.setFlushQueues(false); //plsr.displayFrustum(); plsr.displayDebug(); @@ -99,18 +109,27 @@ public class TestPointLightShadows extends SimpleApplication { plsf.setLight((PointLight) scene.getLocalLightList().get(0)); plsf.setShadowZExtend(15); plsf.setShadowZFadeLength(5); + plsf.setShadowIntensity(0.8f); plsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4); plsf.setEnabled(false); FilterPostProcessor fpp = new FilterPostProcessor(assetManager); fpp.addFilter(plsf); viewPort.addProcessor(fpp); - + inputManager.addListener(this,"ShadowUp","ShadowDown"); ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, plsr, plsf, guiNode, inputManager, viewPort); + } @Override public void simpleUpdate(float tpf) { // lightNode.move(FastMath.cos(tpf) * 0.4f, 0, FastMath.sin(tpf) * 0.4f); } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if ((name.equals("ShadowUp") || name.equals("ShadowDown")) && isPressed) { + al.setColor(ColorRGBA.White.mult((1 - plsr.getShadowIntensity()) * 0.2f)); + } + } } \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java b/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java index 4c74b0664..55d5a3f87 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java +++ b/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java @@ -67,6 +67,8 @@ import com.jme3.texture.Texture; import com.jme3.texture.Texture.WrapMode; import com.jme3.util.SkyFactory; import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; public class TestPssmShadow extends SimpleApplication implements ActionListener { @@ -249,10 +251,20 @@ public class TestPssmShadow extends SimpleApplication implements ActionListener time = 0; } + @Override + public Object jmeClone() { + return null; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + } + @Override protected void controlRender(RenderManager rm, ViewPort vp) { } + @Override public Control cloneForSpatial(Spatial spatial) { return null; } diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java index 8d29ae498..7a6f35c37 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java @@ -58,7 +58,7 @@ public class TestSpotLight extends SimpleApplication { Geometry lightMdl; public void setupLighting(){ AmbientLight al=new AmbientLight(); - al.setColor(ColorRGBA.White.mult(0.8f)); + al.setColor(ColorRGBA.White.mult(0.02f)); rootNode.addLight(al); spot=new SpotLight(); diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java index fbf0e1b67..8df514fd3 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java @@ -65,7 +65,7 @@ public class TestSpotLightShadows extends SimpleApplication { public void setupLighting() { AmbientLight al = new AmbientLight(); - al.setColor(ColorRGBA.White.mult(0.3f)); + al.setColor(ColorRGBA.White.mult(0.02f)); rootNode.addLight(al); rootNode.setShadowMode(ShadowMode.CastAndReceive); @@ -132,11 +132,6 @@ public class TestSpotLightShadows extends SimpleApplication { }, "stop"); inputManager.addMapping("stop", new KeyTrigger(KeyInput.KEY_1)); - - MaterialDebugAppState s = new MaterialDebugAppState(); - s.registerBinding("Common/MatDefs/Shadow/PostShadow15.frag", rootNode); - s.registerBinding(new KeyTrigger(KeyInput.KEY_R), rootNode); - stateManager.attach(s); flyCam.setDragToRotate(true); } diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java index 4fb58602c..f656b1fd6 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java @@ -95,7 +95,7 @@ public class TestSpotLightTerrain extends SimpleApplication { rootNode.addLight(sl); AmbientLight ambLight = new AmbientLight(); - ambLight.setColor(new ColorRGBA(0.8f, 0.8f, 0.8f, 0.2f)); + ambLight.setColor(ColorRGBA.Black); rootNode.addLight(ambLight); cam.setLocation(new Vector3f(-41.219646f, -84.8363f, -171.67267f)); diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java index a786d767d..21817e7c8 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java @@ -72,7 +72,6 @@ public class TestTangentGenBadModels extends SimpleApplication { "debug tangents geom", TangentBinormalGenerator.genTbnLines(g.getMesh(), 0.2f) ); - debug.getMesh().setLineWidth(1); debug.setMaterial(debugMat); debug.setCullHint(Spatial.CullHint.Never); debug.setLocalTransform(g.getWorldTransform()); diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java b/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java new file mode 100644 index 000000000..9f8daf8e7 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java @@ -0,0 +1,100 @@ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.*; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; + +/** + * test + * + * @author normenhansen + */ +public class TestTangentSpace extends SimpleApplication { + + public static void main(String[] args) { + TestTangentSpace app = new TestTangentSpace(); + app.start(); + } + + private Node debugNode = new Node("debug"); + + @Override + public void simpleInitApp() { + renderManager.setSinglePassLightBatchSize(2); + renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass); + initView(); + + Spatial s = assetManager.loadModel("Models/Test/BasicCubeLow.obj"); + rootNode.attachChild(s); + + Material m = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + m.setTexture("NormalMap", assetManager.loadTexture("Models/Test/Normal_pixel.png")); + + Geometry g = (Geometry)s; + Geometry g2 = (Geometry) g.deepClone(); + g2.move(5, 0, 0); + g.getParent().attachChild(g2); + + g.setMaterial(m); + g2.setMaterial(m); + + //Regular tangent generation (left geom) + TangentBinormalGenerator.generate(g2.getMesh(), true); + + //MikkTSPace Tangent generation (right geom) + + MikktspaceTangentGenerator.generate(g); + + createDebugTangents(g2); + createDebugTangents(g); + + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("toggleDebug") && isPressed) { + if (debugNode.getParent() == null) { + rootNode.attachChild(debugNode); + } else { + debugNode.removeFromParent(); + } + } + } + }, "toggleDebug"); + + inputManager.addMapping("toggleDebug", new KeyTrigger(KeyInput.KEY_SPACE)); + + + DirectionalLight dl = new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal()); + rootNode.addLight(dl); + } + + private void initView() { + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + cam.setLocation(new Vector3f(8.569681f, 3.335546f, 5.4372444f)); + cam.setRotation(new Quaternion(-0.07608022f, 0.9086564f, -0.18992864f, -0.3639813f)); + flyCam.setMoveSpeed(10); + } + + private void createDebugTangents(Geometry geom) { + Geometry debug = new Geometry( + "Debug " + geom.getName(), + TangentBinormalGenerator.genTbnLines(geom.getMesh(), 0.8f) + ); + Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m"); + debug.setMaterial(debugMat); + debug.setCullHint(Spatial.CullHint.Never); + debug.getLocalTranslation().set(geom.getWorldTranslation()); + debugNode.attachChild(debug); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java b/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java index 6c4c45e2f..fc4731615 100644 --- a/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java +++ b/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java @@ -50,10 +50,11 @@ public class TestDebugShapes extends SimpleApplication { app.start(); } - public Geometry putShape(Mesh shape, ColorRGBA color){ + public Geometry putShape(Mesh shape, ColorRGBA color, float lineWidth){ Geometry g = new Geometry("shape", shape); Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); mat.getAdditionalRenderState().setWireframe(true); + mat.getAdditionalRenderState().setLineWidth(lineWidth); mat.setColor("Color", color); g.setMaterial(mat); rootNode.attachChild(g); @@ -62,20 +63,19 @@ public class TestDebugShapes extends SimpleApplication { public void putArrow(Vector3f pos, Vector3f dir, ColorRGBA color){ Arrow arrow = new Arrow(dir); - arrow.setLineWidth(4); // make arrow thicker - putShape(arrow, color).setLocalTranslation(pos); + putShape(arrow, color, 4).setLocalTranslation(pos); } public void putBox(Vector3f pos, float size, ColorRGBA color){ - putShape(new WireBox(size, size, size), color).setLocalTranslation(pos); + putShape(new WireBox(size, size, size), color, 1).setLocalTranslation(pos); } public void putGrid(Vector3f pos, ColorRGBA color){ - putShape(new Grid(6, 6, 0.2f), color).center().move(pos); + putShape(new Grid(6, 6, 0.2f), color, 1).center().move(pos); } public void putSphere(Vector3f pos, ColorRGBA color){ - putShape(new WireSphere(1), color).setLocalTranslation(pos); + putShape(new WireSphere(1), color, 1).setLocalTranslation(pos); } @Override diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java index a36853913..2e8ecbfaf 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java @@ -41,6 +41,8 @@ import java.awt.Component; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.*; import jme3test.network.TestChatServer.ChatMessage; @@ -115,6 +117,18 @@ public class TestChatClient extends JFrame { public static void main(String... args) throws Exception { + // Increate the logging level for networking... + System.out.println("Setting logging to max"); + Logger networkLog = Logger.getLogger("com.jme3.network"); + networkLog.setLevel(Level.FINEST); + + // And we have to tell JUL's handler also + // turn up logging in a very convoluted way + Logger rootLog = Logger.getLogger(""); + if( rootLog.getHandlers().length > 0 ) { + rootLog.getHandlers()[0].setLevel(Level.FINEST); + } + // Note: in JME 3.1 this is generally unnecessary as the server will // send a message with all server-registered classes. // TestChatServer.initializeClasses(); diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java index 4d8642881..8bdb740b9 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java @@ -31,6 +31,9 @@ */ package jme3test.network; +import java.util.logging.Level; +import java.util.logging.Logger; + import com.jme3.network.*; import com.jme3.network.serializing.Serializable; import com.jme3.network.serializing.Serializer; @@ -56,11 +59,15 @@ public class TestChatServer { private boolean isRunning; public TestChatServer() throws IOException { - initializeClasses(); // Use this to test the client/server name version check this.server = Network.createServer(NAME, VERSION, PORT, UDP_PORT); + // Initialize our own messages only after the server has been created. + // It registers some additional messages with the serializer by default + // that need to go before custom messages. + initializeClasses(); + ChatHandler handler = new ChatHandler(); server.addMessageListener(handler, ChatMessage.class); @@ -121,6 +128,18 @@ public class TestChatServer { } public static void main(String... args) throws Exception { + + // Increate the logging level for networking... + System.out.println("Setting logging to max"); + Logger networkLog = Logger.getLogger("com.jme3.network"); + networkLog.setLevel(Level.FINEST); + + // And we have to tell JUL's handler also + // turn up logging in a very convoluted way + Logger rootLog = Logger.getLogger(""); + if( rootLog.getHandlers().length > 0 ) { + rootLog.getHandlers()[0].setLevel(Level.FINEST); + } TestChatServer chatServer = new TestChatServer(); chatServer.start(); diff --git a/jme3-examples/src/main/java/jme3test/scene/TestLineWidthRenderState.java b/jme3-examples/src/main/java/jme3test/scene/TestLineWidthRenderState.java new file mode 100644 index 000000000..6bd629823 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/scene/TestLineWidthRenderState.java @@ -0,0 +1,87 @@ +/* + * 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 jme3test.scene; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; + +public class TestLineWidthRenderState extends SimpleApplication { + + private Material mat; + + public static void main(String[] args){ + TestLineWidthRenderState app = new TestLineWidthRenderState(); + app.start(); + } + + + + @Override + public void simpleInitApp() { + setDisplayFps(false); + setDisplayStatView(false); + cam.setLocation(new Vector3f(5.5826545f, 3.6192513f, 8.016988f)); + cam.setRotation(new Quaternion(-0.04787097f, 0.9463123f, -0.16569641f, -0.27339742f)); + + Box b = new Box(1, 1, 1); + Geometry geom = new Geometry("Box", b); + mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Blue); + mat.getAdditionalRenderState().setWireframe(true); + mat.getAdditionalRenderState().setLineWidth(2); + geom.setMaterial(mat); + rootNode.attachChild(geom); + + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if(name.equals("up") && isPressed){ + mat.getAdditionalRenderState().setLineWidth(mat.getAdditionalRenderState().getLineWidth() + 1); + } + if(name.equals("down") && isPressed){ + mat.getAdditionalRenderState().setLineWidth(Math.max(mat.getAdditionalRenderState().getLineWidth() - 1, 1)); + } + } + }, "up", "down"); + inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_U)); + inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_J)); + } +} \ No newline at end of file diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle index 0ff99d2d3..c900e9061 100644 --- a/jme3-plugins/build.gradle +++ b/jme3-plugins/build.gradle @@ -15,4 +15,5 @@ sourceSets { dependencies { compile project(':jme3-core') + testCompile project(':jme3-desktop') } diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java new file mode 100644 index 000000000..0535c4582 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java @@ -0,0 +1,78 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.material.plugin.export.material; + +import com.jme3.export.JmeExporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.material.Material; +import com.jme3.material.MaterialDef; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; + +/** + * Saves a Material to a j3m file with proper formatting. + * + * usage is : + *
+ *     J3MExporter exporter = new J3MExporter();
+ *     exporter.save(material, myFile);
+ *     //or
+ *     exporter.save(material, myOutputStream);
+ * 
+ * + * @author tsr + * @author nehon (documentation and safety check) + */ +public class J3MExporter implements JmeExporter { + + private final J3MRootOutputCapsule rootCapsule; + + /** + * Create a J3MExporter + */ + public J3MExporter() { + rootCapsule = new J3MRootOutputCapsule(this); + } + + @Override + public void save(Savable object, OutputStream f) throws IOException { + + if (!(object instanceof Material)) { + throw new IllegalArgumentException("J3MExporter can only save com.jme3.material.Material class"); + } + + OutputStreamWriter out = new OutputStreamWriter(f, Charset.forName("UTF-8")); + + rootCapsule.clear(); + object.write(this); + rootCapsule.writeToStream(out); + + out.flush(); + } + + @Override + public void save(Savable object, File f) throws IOException { + try (FileOutputStream fos = new FileOutputStream(f)) { + save(object, fos); + } + } + + @Override + public OutputCapsule getCapsule(Savable object) { + if ((object instanceof Material) || (object instanceof MaterialDef)) { + return rootCapsule; + } + + return rootCapsule.getCapsule(object); + } + +} diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java new file mode 100644 index 000000000..7dd6a2a59 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java @@ -0,0 +1,383 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.material.plugin.export.material; + +import com.jme3.asset.TextureKey; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.material.MatParam; +import com.jme3.material.MatParamTexture; +import com.jme3.math.*; +import com.jme3.shader.VarType; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.IntMap; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author tsr + */ +public class J3MOutputCapsule implements OutputCapsule { + + private final HashMap parameters; + protected final J3MExporter exporter; + + public J3MOutputCapsule(J3MExporter exporter) { + this.exporter = exporter; + parameters = new HashMap<>(); + } + + public void writeToStream(OutputStreamWriter out) throws IOException { + for (String key : parameters.keySet()) { + out.write(" "); + writeParameter(out, key, parameters.get(key)); + out.write("\n"); + } + } + + protected void writeParameter(OutputStreamWriter out, String name, String value) throws IOException { + out.write(name); + out.write(" : "); + out.write(value); + } + + public void clear() { + parameters.clear(); + } + + protected void putParameter(String name, String value) { + parameters.put(name, value); + } + + @Override + public void write(boolean value, String name, boolean defVal) throws IOException { + if (value == defVal) { + return; + } + + putParameter(name, ((value) ? "On" : "Off")); + } + + @Override + public void writeStringSavableMap(Map map, String name, Map defVal) throws IOException { + for (String key : map.keySet()) { + Savable value = map.get(key); + if (defVal == null || !defVal.containsKey(key) || !defVal.get(key).equals(value)) { + putParameter(key, format(value)); + } + } + } + + protected String format(Savable value) { + if (value instanceof MatParamTexture) { + return formatMatParamTexture((MatParamTexture) value); + } + if (value instanceof MatParam) { + return formatMatParam((MatParam) value); + } + + throw new UnsupportedOperationException(value.getClass() + ": Not supported yet."); + } + + private String formatMatParam(MatParam param){ + VarType type = param.getVarType(); + Object val = param.getValue(); + switch (type) { + case Boolean: + case Float: + case Int: + return val.toString(); + case Vector2: + Vector2f v2 = (Vector2f) val; + return v2.getX() + " " + v2.getY(); + case Vector3: + Vector3f v3 = (Vector3f) val; + return v3.getX() + " " + v3.getY() + " " + v3.getZ(); + case Vector4: + // can be either ColorRGBA, Vector4f or Quaternion + if (val instanceof Vector4f) { + Vector4f v4 = (Vector4f) val; + return v4.getX() + " " + v4.getY() + " " + + v4.getZ() + " " + v4.getW(); + } else if (val instanceof ColorRGBA) { + ColorRGBA color = (ColorRGBA) val; + return color.getRed() + " " + color.getGreen() + " " + + color.getBlue() + " " + color.getAlpha(); + } else if (val instanceof Quaternion) { + Quaternion quat = (Quaternion) val; + return quat.getX() + " " + quat.getY() + " " + + quat.getZ() + " " + quat.getW(); + } else { + throw new UnsupportedOperationException("Unexpected Vector4 type: " + val); + } + + default: + return null; // parameter type not supported in J3M + } + } + + protected static String formatMatParamTexture(MatParamTexture param) { + StringBuilder ret = new StringBuilder(); + Texture tex = (Texture) param.getValue(); + TextureKey key; + if (tex != null) { + key = (TextureKey) tex.getKey(); + + if (key != null && key.isFlipY()) { + ret.append("Flip "); + } + + ret.append(formatWrapMode(tex, Texture.WrapAxis.S)); + ret.append(formatWrapMode(tex, Texture.WrapAxis.T)); + ret.append(formatWrapMode(tex, Texture.WrapAxis.R)); + + //Min and Mag filter + Texture.MinFilter def = Texture.MinFilter.BilinearNoMipMaps; + if (tex.getImage().hasMipmaps() || (key != null && key.isGenerateMips())) { + def = Texture.MinFilter.Trilinear; + } + if (tex.getMinFilter() != def) { + ret.append("Min").append(tex.getMinFilter().name()).append(" "); + } + + if (tex.getMagFilter() != Texture.MagFilter.Bilinear) { + ret.append("Mag").append(tex.getMagFilter().name()).append(" "); + } + + ret.append("\"").append(key.getName()).append("\""); + } + + return ret.toString(); + } + + protected static String formatWrapMode(Texture texVal, Texture.WrapAxis axis) { + WrapMode mode; + 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 void write(Enum value, String name, Enum defVal) throws IOException { + if (value == defVal) { + return; + } + + putParameter(name, value.toString()); + } + + @Override + public void write(float value, String name, float defVal) throws IOException { + if (value == defVal) { + return; + } + + putParameter(name, Float.toString(value)); + } + + @Override + public void write(float[] value, String name, float[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(float[][] value, String name, float[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(double value, String name, double defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(double[] value, String name, double[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(double[][] value, String name, double[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(long value, String name, long defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(long[] value, String name, long[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(long[][] value, String name, long[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(short value, String name, short defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(short[] value, String name, short[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(short[][] value, String name, short[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(boolean[] value, String name, boolean[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(boolean[][] value, String name, boolean[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(String value, String name, String defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(String[] value, String name, String[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(String[][] value, String name, String[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(BitSet value, String name, BitSet defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(Savable object, String name, Savable defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(Savable[] objects, String name, Savable[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(Savable[][] objects, String name, Savable[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void writeSavableArrayList(ArrayList array, String name, ArrayList defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void writeSavableArrayListArray(ArrayList[] array, String name, ArrayList[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void writeSavableArrayListArray2D(ArrayList[][] array, String name, ArrayList[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void writeFloatBufferArrayList(ArrayList array, String name, ArrayList defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void writeByteBufferArrayList(ArrayList array, String name, ArrayList defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void writeSavableMap(Map map, String name, Map defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void writeIntSavableMap(IntMap map, String name, IntMap defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(FloatBuffer value, String name, FloatBuffer defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(IntBuffer value, String name, IntBuffer defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(ByteBuffer value, String name, ByteBuffer defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(ShortBuffer value, String name, ShortBuffer defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(byte value, String name, byte defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(byte[] value, String name, byte[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(byte[][] value, String name, byte[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(int value, String name, int defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(int[] value, String name, int[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(int[][] value, String name, int[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + +} diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java new file mode 100644 index 000000000..02c8df02c --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java @@ -0,0 +1,82 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.material.plugin.export.material; + +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.HashMap; + +/** + * + * @author tsr + */ +public class J3MRenderStateOutputCapsule extends J3MOutputCapsule { + protected final static HashMap NAME_MAP; + protected String offsetUnit; + + static { + NAME_MAP = new HashMap<>(); + NAME_MAP.put( "wireframe", "Wireframe"); + NAME_MAP.put( "cullMode", "FaceCull"); + NAME_MAP.put( "depthWrite", "DepthWrite"); + NAME_MAP.put( "depthTest", "DepthTest"); + NAME_MAP.put( "blendMode", "Blend"); + NAME_MAP.put( "alphaFallOff", "AlphaTestFalloff"); + NAME_MAP.put( "offsetFactor", "PolyOffset"); + NAME_MAP.put( "colorWrite", "ColorWrite"); + NAME_MAP.put( "pointSprite", "PointSprite"); + NAME_MAP.put( "depthFunc", "DepthFunc"); + NAME_MAP.put( "alphaFunc", "AlphaFunc"); + NAME_MAP.put( "lineWidth", "LineWidth"); + } + public J3MRenderStateOutputCapsule(J3MExporter exporter) { + super(exporter); + } + + public OutputCapsule getCapsule(Savable object) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void clear() { + super.clear(); + offsetUnit = ""; + } + + @Override + public void writeToStream(OutputStreamWriter out) throws IOException { + out.write(" AdditionalRenderState {\n"); + super.writeToStream(out); + out.write(" }\n"); + } + + @Override + protected void writeParameter(OutputStreamWriter out, String name, String value) throws IOException { + out.write(name); + out.write(" "); + out.write(value); + + if( "PolyOffset".equals(name) ) { + out.write(" "); + out.write(offsetUnit); + } + } + + @Override + protected void putParameter(String name, String value ) { + if( "offsetUnits".equals(name) ) { + offsetUnit = value; + return; + } + + if( !NAME_MAP.containsKey(name) ) + return; + + super.putParameter(NAME_MAP.get(name), value); + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java new file mode 100644 index 000000000..40130b705 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java @@ -0,0 +1,99 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.material.plugin.export.material; + +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.HashMap; + +/** + * @author tsr + */ +public class J3MRootOutputCapsule extends J3MOutputCapsule { + + private final HashMap outCapsules; + private String name; + private String materialDefinition; + private Boolean isTransparent; + + public J3MRootOutputCapsule(J3MExporter exporter) { + super(exporter); + outCapsules = new HashMap<>(); + } + + @Override + public void clear() { + super.clear(); + isTransparent = null; + name = ""; + materialDefinition = ""; + outCapsules.clear(); + + } + + public OutputCapsule getCapsule(Savable object) { + if (!outCapsules.containsKey(object)) { + outCapsules.put(object, new J3MRenderStateOutputCapsule(exporter)); + } + + return outCapsules.get(object); + } + + @Override + public void writeToStream(OutputStreamWriter out) throws IOException { + out.write("Material " + name + " : " + materialDefinition + " {\n\n"); + if (isTransparent != null) + out.write(" Transparent " + ((isTransparent) ? "On" : "Off") + "\n\n"); + + out.write(" MaterialParameters {\n"); + super.writeToStream(out); + out.write(" }\n\n"); + + for (J3MOutputCapsule c : outCapsules.values()) { + c.writeToStream(out); + } + out.write("}\n"); + } + + @Override + public void write(String value, String name, String defVal) throws IOException { + switch (name) { + case "material_def": + materialDefinition = value; + break; + case "name": + this.name = value; + break; + default: + throw new UnsupportedOperationException(name + " string material parameter not supported yet"); + } + } + + @Override + public void write(boolean value, String name, boolean defVal) throws IOException { + if( value == defVal) + return; + + switch (name) { + case "is_transparent": + isTransparent = value; + break; + default: + throw new UnsupportedOperationException(name + " boolean material parameter not supported yet"); + } + } + + @Override + public void write(Savable object, String name, Savable defVal) throws IOException { + if(object != null && !object.equals(defVal)) { + object.write(exporter); + } + } + +} diff --git a/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java b/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java new file mode 100644 index 000000000..be8fc3573 --- /dev/null +++ b/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2009-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.material.plugin; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.material.plugin.export.material.J3MExporter; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.math.ColorRGBA; +import com.jme3.system.JmeSystem; +import com.jme3.texture.Texture; +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.assertTrue; + +public class TestMaterialWrite { + + private AssetManager assetManager; + + @Before + public void init() { + assetManager = JmeSystem.newAssetManager( + TestMaterialWrite.class.getResource("/com/jme3/asset/Desktop.cfg")); + + + } + + + @Test + public void testWriteMat() throws Exception { + + Material mat = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md"); + + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Diffuse", ColorRGBA.White); + mat.setColor("Ambient", ColorRGBA.DarkGray); + mat.setFloat("AlphaDiscardThreshold", 0.5f); + + mat.setFloat("Shininess", 2.5f); + + Texture tex = assetManager.loadTexture("Common/Textures/MissingTexture.png"); + tex.setMagFilter(Texture.MagFilter.Nearest); + tex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); + tex.setWrap(Texture.WrapAxis.S, Texture.WrapMode.Repeat); + tex.setWrap(Texture.WrapAxis.T, Texture.WrapMode.MirroredRepeat); + + mat.setTexture("DiffuseMap", tex); + mat.getAdditionalRenderState().setDepthWrite(false); + mat.getAdditionalRenderState().setDepthTest(false); + mat.getAdditionalRenderState().setLineWidth(5); + mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); + + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + J3MExporter exporter = new J3MExporter(); + try { + exporter.save(mat, stream); + } catch (IOException e) { + e.printStackTrace(); + } + + System.err.println(stream.toString()); + + J3MLoader loader = new J3MLoader(); + AssetInfo info = new AssetInfo(assetManager, new AssetKey("test")) { + @Override + public InputStream openStream() { + return new ByteArrayInputStream(stream.toByteArray()); + } + }; + Material mat2 = (Material)loader.load(info); + + assertTrue(mat.contentEquals(mat2)); + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java index 4c41e739e..f6682dbe2 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java @@ -40,6 +40,8 @@ import com.jme3.renderer.ViewPort; import com.jme3.scene.Spatial; import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.Control; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; @@ -67,13 +69,33 @@ public class NormalRecalcControl extends AbstractControl { } + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public Object jmeClone() { + NormalRecalcControl control = (NormalRecalcControl)super.jmeClone(); + control.setEnabled(true); + return control; + } + + /** + * 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.terrain = cloner.clone(terrain); + } + + @Override public Control cloneForSpatial(Spatial spatial) { NormalRecalcControl control = new NormalRecalcControl(terrain); control.setSpatial(spatial); control.setEnabled(true); return control; } - + @Override public void setSpatial(Spatial spatial) { super.setSpatial(spatial); diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java index 8ad2425ad..d551ff4e5 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java @@ -46,6 +46,8 @@ import com.jme3.scene.control.Control; import com.jme3.terrain.Terrain; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -72,12 +74,12 @@ import java.util.logging.Logger; * This control serializes, but it does not save the Camera reference. * This camera reference has to be manually added in when you load the * terrain to the scene! - * + * * When the control or the terrain are removed from the scene, you should call * TerrainLodControl.detachAndCleanUpControl() to remove any threads it created * to handle the LOD processing. If you supply your own executor service, then * you have to handle its thread termination yourself. - * + * * @author Brent Owens */ public class TerrainLodControl extends AbstractControl { @@ -90,15 +92,15 @@ public class TerrainLodControl extends AbstractControl { private HashMap updatedPatches; private final Object updatePatchesLock = new Object(); - + protected List lastCameraLocations; // used for LOD calc private AtomicBoolean lodCalcRunning = new AtomicBoolean(false); private int lodOffCount = 0; - + protected ExecutorService executor; protected Future> indexer; private boolean forceUpdate = true; - + public TerrainLodControl() { } @@ -109,7 +111,7 @@ public class TerrainLodControl extends AbstractControl { this.cameras = cams; lodCalculator = new DistanceLodCalculator(65, 2.7f); // a default calculator } - + /** * Only uses the first camera right now. * @param terrain to act upon (must be a Spatial) @@ -132,7 +134,7 @@ public class TerrainLodControl extends AbstractControl { public void setExecutor(ExecutorService executor) { this.executor = executor; } - + protected ExecutorService createExecutorService() { return Executors.newSingleThreadExecutor(new ThreadFactory() { public Thread newThread(Runnable r) { @@ -143,14 +145,14 @@ public class TerrainLodControl extends AbstractControl { } }); } - + @Override protected void controlUpdate(float tpf) { //list of cameras for when terrain supports multiple cameras (ie split screen) if (lodCalculator == null) return; - + if (!enabled) { if (!hasResetLod) { // this will get run once @@ -158,7 +160,7 @@ public class TerrainLodControl extends AbstractControl { lodCalculator.turnOffLod(); } } - + if (cameras != null) { cameraLocations.clear(); for (Camera c : cameras) // populate them @@ -168,7 +170,7 @@ public class TerrainLodControl extends AbstractControl { updateLOD(cameraLocations, lodCalculator); } } - + /** * Call this when you remove the terrain or this control from the scene. * It will clear up any threads it had. @@ -184,7 +186,7 @@ public class TerrainLodControl extends AbstractControl { if(getSpatial() == null){ return; } - + // update any existing ones that need updating updateQuadLODs(); @@ -194,9 +196,9 @@ public class TerrainLodControl extends AbstractControl { return; else lodOffCount++; - } else + } else lodOffCount = 0; - + if (lastCameraLocations != null) { if (!forceUpdate && lastCameraLocationsTheSame(locations) && !lodCalculator.isLodOff()) return; // don't update if in same spot @@ -216,9 +218,9 @@ public class TerrainLodControl extends AbstractControl { if (executor == null) executor = createExecutorService(); - + prepareTerrain(); - + UpdateLOD updateLodThread = getLodThread(locations, lodCalculator); indexer = executor.submit(updateLodThread); } @@ -230,12 +232,12 @@ public class TerrainLodControl extends AbstractControl { public void forceUpdate() { this.forceUpdate = true; } - + protected void prepareTerrain() { TerrainQuad terrain = (TerrainQuad)getSpatial(); terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely } - + protected UpdateLOD getLodThread(List locations, LodCalculator lodCalculator) { return new UpdateLOD(locations, lodCalculator); } @@ -247,7 +249,7 @@ public class TerrainLodControl extends AbstractControl { if (indexer != null) { if (indexer.isDone()) { try { - + HashMap updated = indexer.get(); if (updated != null) { // do the actual geometry update here @@ -255,7 +257,7 @@ public class TerrainLodControl extends AbstractControl { utp.updateAll(); } } - + } catch (InterruptedException ex) { Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex); } catch (ExecutionException ex) { @@ -266,7 +268,7 @@ public class TerrainLodControl extends AbstractControl { } } } - + private boolean lastCameraLocationsTheSame(List locations) { boolean theSame = true; for (Vector3f l : locations) { @@ -279,7 +281,7 @@ public class TerrainLodControl extends AbstractControl { } return theSame; } - + protected synchronized boolean isLodCalcRunning() { return lodCalcRunning.get(); } @@ -295,12 +297,37 @@ public class TerrainLodControl extends AbstractControl { return cloned; } - - - - - - + + + + + @Override + public Object jmeClone() { + if (spatial instanceof Terrain) { + TerrainLodControl cloned = new TerrainLodControl((Terrain) spatial, cameras); + cloned.setLodCalculator(lodCalculator.clone()); + cloned.spatial = spatial; + return cloned; + } + return null; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + super.cloneFields(cloner, original); + + this.lodCalculator = cloner.clone(lodCalculator); + + try { + // Not deep clone of the cameras themselves + this.cameras = cloner.javaClone(cameras); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException("Error cloning", e); + } + } + + + @Override public Control cloneForSpatial(Spatial spatial) { if (spatial instanceof Terrain) { List cameraClone = new ArrayList(); @@ -321,7 +348,7 @@ public class TerrainLodControl extends AbstractControl { cams.add(camera); setCameras(cams); } - + public void setCameras(List cameras) { this.cameras = cameras; cameraLocations.clear(); @@ -349,7 +376,7 @@ public class TerrainLodControl extends AbstractControl { public void setLodCalculator(LodCalculator lodCalculator) { this.lodCalculator = lodCalculator; } - + @Override public void setEnabled(boolean enabled) { this.enabled = enabled; @@ -361,8 +388,8 @@ public class TerrainLodControl extends AbstractControl { lodCalculator.turnOnLod(); } } - - + + /** * Calculates the LOD of all child terrain patches. */ @@ -383,7 +410,7 @@ public class TerrainLodControl extends AbstractControl { setLodCalcRunning(true); TerrainQuad terrainQuad = (TerrainQuad)getSpatial(); - + // go through each patch and calculate its LOD based on camera distance HashMap updated = new HashMap(); boolean lodChanged = terrainQuad.calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here @@ -393,8 +420,8 @@ public class TerrainLodControl extends AbstractControl { setLodCalcRunning(false); return null; } - - + + // then calculate its neighbour LOD values for seaming in the shader terrainQuad.findNeighboursLod(updated); @@ -405,7 +432,7 @@ public class TerrainLodControl extends AbstractControl { //setUpdateQuadLODs(updated); // set back to main ogl thread setLodCalcRunning(false); - + return updated; } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java index 4ac811e9f..4641f4de6 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java @@ -50,6 +50,7 @@ import com.jme3.scene.mesh.IndexBuffer; import com.jme3.terrain.geomipmap.TerrainQuad.LocationHeight; import com.jme3.terrain.geomipmap.lodcalc.util.EntropyComputeUtil; import com.jme3.util.BufferUtils; +import com.jme3.util.clone.Cloner; import java.io.IOException; import java.nio.Buffer; import java.nio.FloatBuffer; @@ -65,18 +66,18 @@ import java.util.List; * That uses a geo-mipmapping algorithm to change the index buffer of the mesh. * The mesh is a triangle strip. In wireframe mode you might notice some strange lines, these are degenerate * triangles generated by the geoMipMap algorithm and can be ignored. The video card removes them at almost no cost. - * + * * Each patch needs to know its neighbour's LOD so it can seam its edges with them, in case the neighbour has a different * LOD. If this doesn't happen, you will see gaps. - * + * * The LOD value is most detailed at zero. It gets less detailed the higher the LOD value until you reach maxLod, which * is a mathematical limit on the number of times the 'size' of the patch can be divided by two. However there is a -1 to that * for now until I add in a custom index buffer calculation for that max level, the current algorithm does not go that far. - * - * You can supply a LodThresholdCalculator for use in determining when the LOD should change. It's API will no doubt change - * in the near future. Right now it defaults to just changing LOD every two patch sizes. So if a patch has a size of 65, + * + * You can supply a LodThresholdCalculator for use in determining when the LOD should change. It's API will no doubt change + * in the near future. Right now it defaults to just changing LOD every two patch sizes. So if a patch has a size of 65, * then the LOD changes every 130 units away. - * + * * @author Brent Owens */ public class TerrainPatch extends Geometry { @@ -118,7 +119,7 @@ public class TerrainPatch extends Geometry { super("TerrainPatch"); setBatchHint(BatchHint.Never); } - + public TerrainPatch(String name) { super(name); setBatchHint(BatchHint.Never); @@ -221,7 +222,7 @@ public class TerrainPatch extends Geometry { public FloatBuffer getHeightmap() { return BufferUtils.createFloatBuffer(geomap.getHeightArray()); } - + public float[] getHeightMap() { return geomap.getHeightArray(); } @@ -256,7 +257,7 @@ public class TerrainPatch extends Geometry { idxB = geomap.writeIndexArrayLodVariable(pow, (int) Math.pow(2, utp.getRightLod()), (int) Math.pow(2, utp.getTopLod()), (int) Math.pow(2, utp.getLeftLod()), (int) Math.pow(2, utp.getBottomLod()), totalSize); else idxB = geomap.writeIndexArrayLodDiff(pow, right, top, left, bottom, totalSize); - + Buffer b; if (idxB.getBuffer() instanceof IntBuffer) b = (IntBuffer)idxB.getBuffer(); @@ -277,14 +278,14 @@ public class TerrainPatch extends Geometry { return store.set(getMesh().getFloatBuffer(Type.TexCoord).get(idx*2), getMesh().getFloatBuffer(Type.TexCoord).get(idx*2+1) ); } - + public float getHeightmapHeight(float x, float z) { if (x < 0 || z < 0 || x >= size || z >= size) return 0; int idx = (int) (z * size + x); return getMesh().getFloatBuffer(Type.Position).get(idx*3+1); // 3 floats per entry (x,y,z), the +1 is to get the Y } - + /** * Get the triangle of this geometry at the specified local coordinate. * @param x local to the terrain patch @@ -306,7 +307,7 @@ public class TerrainPatch extends Geometry { } protected void setHeight(List locationHeights, boolean overrideHeight) { - + for (LocationHeight lh : locationHeights) { if (lh.x < 0 || lh.z < 0 || lh.x >= size || lh.z >= size) continue; @@ -317,7 +318,7 @@ public class TerrainPatch extends Geometry { float h = getMesh().getFloatBuffer(Type.Position).get(idx*3+1); geomap.getHeightArray()[idx] = h+lh.h; } - + } FloatBuffer newVertexBuffer = geomap.writeVertexArray(null, stepScale, false); @@ -351,7 +352,7 @@ public class TerrainPatch extends Geometry { TB.setUpdateNeeded(); BB.setUpdateNeeded(); } - + /** * Matches the normals along the edge of the patch with the neighbours. * Computes the normals for the right, bottom, left, and top edges of the @@ -364,7 +365,7 @@ public class TerrainPatch extends Geometry { * *---x---* * | * * - * It works across the right side of the patch, from the top down to + * It works across the right side of the patch, from the top down to * the bottom. Then it works on the bottom side of the patch, from the * left to the right. */ @@ -388,9 +389,9 @@ public class TerrainPatch extends Geometry { Vector3f binormal = new Vector3f(); Vector3f normal = new Vector3f(); - + int s = this.getSize()-1; - + if (right != null) { // right side, works its way down for (int i=0; i= size || z >= size) return null; // out of range - + int index = (z*size+x)*3; FloatBuffer nb = (FloatBuffer)this.getMesh().getBuffer(Type.Normal).getData(); Vector3f normal = new Vector3f(); @@ -609,7 +610,7 @@ public class TerrainPatch extends Geometry { protected float getHeight(int x, int z, float xm, float zm) { return geomap.getHeight(x,z,xm,zm); } - + /** * Locks the mesh (sets it static) to improve performance. * But it it not editable then. Set unlock to make it editable. @@ -626,7 +627,7 @@ public class TerrainPatch extends Geometry { public void unlockMesh() { getMesh().setDynamic(); } - + /** * Returns the offset amount this terrain patch uses for textures. * @@ -797,7 +798,7 @@ public class TerrainPatch extends Geometry { protected void setLodBottom(int lodBottom) { this.lodBottom = lodBottom; } - + /*public void setLodCalculator(LodCalculatorFactory lodCalculatorFactory) { this.lodCalculatorFactory = lodCalculatorFactory; setLodCalculator(lodCalculatorFactory.createCalculator(this)); @@ -812,7 +813,7 @@ public class TerrainPatch extends Geometry { if (other instanceof BoundingVolume) if (!getWorldBound().intersects((BoundingVolume)other)) return 0; - + if(other instanceof Ray) return collideWithRay((Ray)other, results); else if (other instanceof BoundingVolume) @@ -853,7 +854,7 @@ public class TerrainPatch extends Geometry { * This most definitely is not optimized. */ private int collideWithBoundingBox(BoundingBox bbox, CollisionResults results) { - + // test the four corners, for cases where the bbox dimensions are less than the terrain grid size, which is probably most of the time Vector3f topLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x-bbox.getXExtent(), 0, bbox.getCenter().z-bbox.getZExtent())); Vector3f topRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x+bbox.getXExtent(), 0, bbox.getCenter().z-bbox.getZExtent())); @@ -872,11 +873,11 @@ public class TerrainPatch extends Geometry { t = getTriangle(bottomRight.x, bottomRight.z); if (t != null && bbox.collideWith(t, results) > 0) return 1; - + // box is larger than the points on the terrain, so test against the points for (float z=topLeft.z; z= size || z >= size) continue; t = getTriangle(x,z); @@ -895,7 +896,7 @@ public class TerrainPatch extends Geometry { // this reduces the save size to 10% by not saving the mesh Mesh temp = getMesh(); mesh = null; - + super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(size, "size", 16); @@ -908,7 +909,7 @@ public class TerrainPatch extends Geometry { //oc.write(lodCalculatorFactory, "lodCalculatorFactory", null); oc.write(lodEntropy, "lodEntropy", null); oc.write(geomap, "geomap", null); - + setMesh(temp); } @@ -927,7 +928,7 @@ public class TerrainPatch extends Geometry { //lodCalculatorFactory = (LodCalculatorFactory) ic.readSavable("lodCalculatorFactory", null); lodEntropy = ic.readFloatArray("lodEntropy", null); geomap = (LODGeomap) ic.readSavable("geomap", null); - + Mesh regen = geomap.createMesh(stepScale, new Vector2f(1,1), offset, offsetAmount, totalSize, false); setMesh(regen); //TangentBinormalGenerator.generate(this); // note that this will be removed @@ -955,6 +956,34 @@ public class TerrainPatch 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.stepScale = cloner.clone(stepScale); + this.offset = cloner.clone(offset); + + this.leftNeighbour = null; + this.topNeighbour = null; + this.rightNeighbour = null; + this.bottomNeighbour = null; + + // Don't feel like making geomap cloneable tonight + // so I'll copy the old logic. + this.geomap = new LODGeomap(size, geomap.getHeightArray()); + Mesh m = geomap.createMesh(stepScale, Vector2f.UNIT_XY, offset, offsetAmount, totalSize, false); + this.setMesh(m); + + // In this case, we always clone material even if the cloner is setup + // not to clone it. Terrain uses mutable textures and stuff so it's important + // to clone it. (At least that's my understanding and is evidenced by the old + // clone code specifically cloning material.) -pspeed + this.material = material.clone(); + } + protected void ensurePositiveVolumeBBox() { if (getModelBound() instanceof BoundingBox) { if (((BoundingBox)getModelBound()).getYExtent() < 0.001f) { diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java index 8cceb85bb..e21b89155 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java @@ -55,6 +55,7 @@ import com.jme3.terrain.geomipmap.picking.BresenhamTerrainPicker; import com.jme3.terrain.geomipmap.picking.TerrainPickData; import com.jme3.terrain.geomipmap.picking.TerrainPicker; import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.clone.Cloner; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -126,7 +127,7 @@ public class TerrainQuad extends Node implements Terrain { private Vector3f lastScale = Vector3f.UNIT_XYZ; protected NeighbourFinder neighbourFinder; - + public TerrainQuad() { super("Terrain"); } @@ -144,24 +145,24 @@ 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 (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