diff --git a/.gitignore b/.gitignore index 8baaf9fc4..be1334611 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,22 @@ +**/nbproject/private/ /.gradle/ -/.nb-gradle/private/ -/.nb-gradle/profiles/private/ +/.nb-gradle/ /.idea/ /dist/ /build/ +/bin/ /netbeans/ -/sdk/jdks/local/ -/jme3-core/build/ +/.classpath +/.project +/.settings +*.dll +*.so +*.jnilib +*.dylib +*.iml +.DS_Store /jme3-core/src/main/resources/com/jme3/system/version.properties -/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-*/build/ /jme3-bullet-native/bullet.zip /jme3-bullet-native/bullet-2.82-r2704/ /jme3-android-native/openal-soft/ @@ -38,112 +26,9 @@ /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/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-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 -/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 2cad48bf2..9a4aba272 100644 --- a/common.gradle +++ b/common.gradle @@ -51,6 +51,12 @@ javadoc { } } +test { + testLogging { + exceptionFormat = 'full' + } +} + task sourcesJar(type: Jar, dependsOn: classes, description: 'Creates a jar from the source files.') { classifier = 'sources' from sourceSets*.allSource 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-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java index 3b5a8eefb..c8dedcd40 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java @@ -1,6 +1,7 @@ package com.jme3.scene.plugins.blender.materials; import java.io.IOException; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -157,14 +158,14 @@ public final class MaterialContext implements Savable { } // applying textures + int textureIndex = 0; if (loadedTextures != null && loadedTextures.size() > 0) { - int textureIndex = 0; if (loadedTextures.size() > TextureHelper.TEXCOORD_TYPES.length) { LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different textures. JME supports only {0} UV mappings.", TextureHelper.TEXCOORD_TYPES.length); } for (CombinedTexture combinedTexture : loadedTextures) { if (textureIndex < TextureHelper.TEXCOORD_TYPES.length) { - combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext); + String usedUserUVSet = combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext); this.setTexture(material, combinedTexture.getMappingType(), combinedTexture.getResultTexture()); List uvs = combinedTexture.getResultUVS(); @@ -173,13 +174,19 @@ public final class MaterialContext implements Savable { uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()]))); geometry.getMesh().setBuffer(uvCoordsBuffer); }//uvs might be null if the user assigned non existing UV coordinates group name to the mesh (this should be fixed in blender file) + + if(usedUserUVSet != null) { + userDefinedUVCoordinates = new HashMap<>(userDefinedUVCoordinates); + userDefinedUVCoordinates.remove(usedUserUVSet); + } } else { LOGGER.log(Level.WARNING, "The texture could not be applied because JME only supports up to {0} different UV's.", TextureHelper.TEXCOORD_TYPES.length); } } - } else if (userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { - LOGGER.fine("No textures found for the mesh, but UV coordinates are applied."); - int textureIndex = 0; + } + + if (userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { + LOGGER.fine("Storing unused, user defined UV coordinates sets."); if (userDefinedUVCoordinates.size() > TextureHelper.TEXCOORD_TYPES.length) { LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different UV coordinates for the mesh. JME supports only {0} UV coordinates buffers.", TextureHelper.TEXCOORD_TYPES.length); } @@ -190,7 +197,9 @@ public final class MaterialContext implements Savable { uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()]))); geometry.getMesh().setBuffer(uvCoordsBuffer); } else { - LOGGER.log(Level.WARNING, "The texture could not be applied because JME only supports up to {0} different UV's.", TextureHelper.TEXCOORD_TYPES.length); + LOGGER.log(Level.WARNING, "The user's UV set named: '{0}' could not be stored because JME only supports up to {1} different UV's.", new Object[] { + entry.getKey(), TextureHelper.TEXCOORD_TYPES.length + }); } } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java index e406447eb..6c40542e2 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java @@ -119,22 +119,24 @@ public class CombinedTexture { } } - /** - * This method flattens the texture and creates a single result of Texture2D - * type. - * - * @param geometry - * the geometry the texture is created for - * @param geometriesOMA - * the old memory address of the geometries list that the given - * geometry belongs to (needed for bounding box creation) - * @param userDefinedUVCoordinates - * the UV's defined by user (null or zero length table if none - * were defined) - * @param blenderContext - * the blender context - */ - public void flatten(Geometry geometry, Long geometriesOMA, Map> userDefinedUVCoordinates, BlenderContext blenderContext) { + /** + * This method flattens the texture and creates a single result of Texture2D + * type. + * + * @param geometry + * the geometry the texture is created for + * @param geometriesOMA + * the old memory address of the geometries list that the given + * geometry belongs to (needed for bounding box creation) + * @param userDefinedUVCoordinates + * the UV's defined by user (null or zero length table if none + * were defined) + * @param blenderContext + * the blender context + * @return the name of the user UV coordinates used (null if the UV's were + * generated) + */ + public String flatten(Geometry geometry, Long geometriesOMA, Map> userDefinedUVCoordinates, BlenderContext blenderContext) { Mesh mesh = geometry.getMesh(); Texture previousTexture = null; UVCoordinatesType masterUVCoordinatesType = null; @@ -226,6 +228,7 @@ public class CombinedTexture { } resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS(); resultTexture = ((TriangulatedTexture) resultTexture).getResultTexture(); + masterUserUVSetName = null; } // setting additional data @@ -234,6 +237,8 @@ public class CombinedTexture { // otherwise ugly lines appear between the mesh faces resultTexture.setMagFilter(MagFilter.Nearest); resultTexture.setMinFilter(MinFilter.NearestNoMipMaps); + + return masterUserUVSetName; } /** diff --git a/jme3-bullet-native/src/native/cpp/jmeClasses.cpp b/jme3-bullet-native/src/native/cpp/jmeClasses.cpp index 6b7caa0e9..ae7784c82 100644 --- a/jme3-bullet-native/src/native/cpp/jmeClasses.cpp +++ b/jme3-bullet-native/src/native/cpp/jmeClasses.cpp @@ -40,6 +40,7 @@ jclass jmeClasses::PhysicsSpace; jmethodID jmeClasses::PhysicsSpace_preTick; jmethodID jmeClasses::PhysicsSpace_postTick; jmethodID jmeClasses::PhysicsSpace_addCollisionEvent; +jmethodID jmeClasses::PhysicsSpace_notifyCollisionGroupListeners; jclass jmeClasses::PhysicsGhostObject; jmethodID jmeClasses::PhysicsGhostObject_addOverlappingObject; @@ -137,6 +138,7 @@ void jmeClasses::initJavaClasses(JNIEnv* env) { PhysicsSpace_preTick = env->GetMethodID(PhysicsSpace, "preTick_native", "(F)V"); PhysicsSpace_postTick = env->GetMethodID(PhysicsSpace, "postTick_native", "(F)V"); PhysicsSpace_addCollisionEvent = env->GetMethodID(PhysicsSpace, "addCollisionEvent_native","(Lcom/jme3/bullet/collision/PhysicsCollisionObject;Lcom/jme3/bullet/collision/PhysicsCollisionObject;J)V"); + PhysicsSpace_notifyCollisionGroupListeners = env->GetMethodID(PhysicsSpace, "notifyCollisionGroupListeners_native","(Lcom/jme3/bullet/collision/PhysicsCollisionObject;Lcom/jme3/bullet/collision/PhysicsCollisionObject;)Z"); if (env->ExceptionCheck()) { env->Throw(env->ExceptionOccurred()); return; diff --git a/jme3-bullet-native/src/native/cpp/jmeClasses.h b/jme3-bullet-native/src/native/cpp/jmeClasses.h index bb1b0e99a..bdead6b70 100644 --- a/jme3-bullet-native/src/native/cpp/jmeClasses.h +++ b/jme3-bullet-native/src/native/cpp/jmeClasses.h @@ -46,6 +46,7 @@ public: static jmethodID PhysicsSpace_addCollisionEvent; static jclass PhysicsGhostObject; static jmethodID PhysicsGhostObject_addOverlappingObject; + static jmethodID PhysicsSpace_notifyCollisionGroupListeners; static jclass Vector3f; static jmethodID Vector3f_set; diff --git a/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp b/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp index 34c77c407..8cb80bc87 100644 --- a/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp +++ b/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp @@ -187,8 +187,28 @@ void jmePhysicsSpace::createPhysicsSpace(jfloat minX, jfloat minY, jfloat minZ, jmeUserPointer *up0 = (jmeUserPointer*) co0 -> getUserPointer(); jmeUserPointer *up1 = (jmeUserPointer*) co1 -> getUserPointer(); if (up0 != NULL && up1 != NULL) { - collides = (up0->group & up1->groups) != 0; - collides = collides && (up1->group & up0->groups); + collides = (up0->group & up1->groups) != 0 || (up1->group & up0->groups) != 0; + + if(collides){ + jmePhysicsSpace *dynamicsWorld = (jmePhysicsSpace *)up0->space; + JNIEnv* env = dynamicsWorld->getEnv(); + jobject javaPhysicsSpace = env->NewLocalRef(dynamicsWorld->getJavaPhysicsSpace()); + jobject javaCollisionObject0 = env->NewLocalRef(up0->javaCollisionObject); + jobject javaCollisionObject1 = env->NewLocalRef(up1->javaCollisionObject); + + jboolean notifyResult = env->CallBooleanMethod(javaPhysicsSpace, jmeClasses::PhysicsSpace_notifyCollisionGroupListeners, javaCollisionObject0, javaCollisionObject1); + + env->DeleteLocalRef(javaPhysicsSpace); + env->DeleteLocalRef(javaCollisionObject0); + env->DeleteLocalRef(javaCollisionObject1); + + if (env->ExceptionCheck()) { + env->Throw(env->ExceptionOccurred()); + return collides; + } + + collides = (bool) notifyResult; + } //add some additional logic here that modified 'collides' return collides; diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java index edea485ef..f3575bcdf 100644 --- a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java +++ b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java @@ -335,6 +335,21 @@ public class PhysicsSpace { // System.out.println("addCollisionEvent:"+node.getObjectId()+" "+ node1.getObjectId()); collisionEvents.add(eventFactory.getEvent(PhysicsCollisionEvent.TYPE_PROCESSED, node, node1, manifoldPointObjectId)); } + + private boolean notifyCollisionGroupListeners_native(PhysicsCollisionObject node, PhysicsCollisionObject node1){ + PhysicsCollisionGroupListener listener = collisionGroupListeners.get(node.getCollisionGroup()); + PhysicsCollisionGroupListener listener1 = collisionGroupListeners.get(node1.getCollisionGroup()); + boolean result = true; + + if(listener != null){ + result = listener.collide(node, node1); + } + if(listener1 != null && node.getCollisionGroup() != node1.getCollisionGroup()){ + result = listener1.collide(node, node1) && result; + } + + return result; + } /** * updates the physics space 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 82af6e135..89500c1cd 100644 --- a/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java @@ -118,22 +118,17 @@ public class EffectTrack implements ClonableTrack { } } - @Override + @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. + //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 - public void cloneFields( Cloner cloner, Object original ) { - this.spatial = cloner.clone(spatial); } - + @Override protected void controlRender(RenderManager rm, ViewPort vp) { } @@ -143,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; @@ -261,7 +256,7 @@ public class EffectTrack implements ClonableTrack { public float[] getKeyFrameTimes() { return new float[] { startOffset }; } - + /** * Clone this track * @@ -302,21 +297,21 @@ public class EffectTrack implements ClonableTrack { return effectTrack; } - @Override + @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 ) { + @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/SkeletonControl.java b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java index 2e0b17c3b..b753ad2cb 100644 --- a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java +++ b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java @@ -111,7 +111,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl * Material references used for hardware skinning */ private Set materials = new HashSet(); - + /** * Serialization only. Do not use. */ @@ -204,6 +204,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl * @param skeleton the skeleton */ public SkeletonControl(Skeleton skeleton) { + if (skeleton == null) { + throw new IllegalArgumentException("skeleton cannot be null"); + } this.skeleton = skeleton; } @@ -406,7 +409,23 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl // Not automatic set cloning yet Set newMaterials = new HashSet(); for( Material m : this.materials ) { - newMaterials.add(cloner.clone(m)); + Material mClone = cloner.clone(m); + newMaterials.add(mClone); + if( mClone != m ) { + // Material was really cloned so clear the bone matrices in case + // this is hardware skinned. This allows a local version to be + // used and will be reset on the material. Really this just avoids + // the 'safety' check in controlRenderHardware(). Right now material + // doesn't clone itself with the cloner (and doesn't clone its parameters) + // else this would be unnecessary. + MatParam boneMatrices = mClone.getParam("BoneMatrices"); + + // ...because for some strange reason you can't clear a non-existant + // parameter. + if( boneMatrices != null ) { + mClone.clearParam("BoneMatrices"); + } + } } this.materials = newMaterials; } 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 9688a5d1e..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,321 +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(); - } - - 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(); - } + public void setTimer(Timer timer); - 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); - } - - /** - * 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. @@ -533,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 @@ -546,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 @@ -650,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. @@ -666,109 +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(); - } + 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..d796ba309 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java @@ -0,0 +1,793 @@ +/* + * 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.AppState; +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() { + this((AppState[])null); + } + + /** + * Create a new instance of LegacyApplication, preinitialized + * with the specified set of app states. + */ + public LegacyApplication( AppState... initialStates ) { + initStateManager(); + + if (initialStates != null) { + for (AppState a : initialStates) { + if (a != null) { + stateManager.attach(a); + } + } + } + } + + /** + * 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){ + if (renderManager != null) { + 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..2ab008c5c 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) { @@ -100,15 +100,7 @@ public abstract class SimpleApplication extends Application { } public SimpleApplication( AppState... initialStates ) { - super(); - - if (initialStates != null) { - for (AppState a : initialStates) { - if (a != null) { - stateManager.attach(a); - } - } - } + super(initialStates); } @Override @@ -193,7 +185,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 +193,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 +202,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 +222,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/asset/AssetManager.java b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java index a077512ef..4cd284340 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetManager.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java @@ -41,9 +41,7 @@ import com.jme3.post.FilterPostProcessor; import com.jme3.renderer.Caps; import com.jme3.scene.Spatial; import com.jme3.scene.plugins.OBJLoader; -import com.jme3.shader.Shader; import com.jme3.shader.ShaderGenerator; -import com.jme3.shader.ShaderKey; import com.jme3.texture.Texture; import com.jme3.texture.plugins.TGALoader; import java.io.IOException; @@ -320,13 +318,6 @@ public interface AssetManager { */ public Material loadMaterial(String name); - /** - * Loads shader file(s), shouldn't be used by end-user in most cases. - * - * @see AssetManager#loadAsset(com.jme3.asset.AssetKey) - */ - public Shader loadShader(ShaderKey key); - /** * Load a font file. Font files are in AngelCode text format, * and are with the extension "fnt". diff --git a/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java index 66a6fbbdf..94ee68ceb 100644 --- a/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java +++ b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java @@ -32,7 +32,6 @@ package com.jme3.asset; import com.jme3.asset.cache.AssetCache; -import com.jme3.asset.cache.SimpleAssetCache; import com.jme3.audio.AudioData; import com.jme3.audio.AudioKey; import com.jme3.font.BitmapFont; @@ -42,9 +41,7 @@ import com.jme3.renderer.Caps; import com.jme3.scene.Spatial; import com.jme3.shader.Glsl100ShaderGenerator; import com.jme3.shader.Glsl150ShaderGenerator; -import com.jme3.shader.Shader; import com.jme3.shader.ShaderGenerator; -import com.jme3.shader.ShaderKey; import com.jme3.system.JmeSystem; import com.jme3.texture.Texture; import java.io.IOException; @@ -431,36 +428,6 @@ public class DesktopAssetManager implements AssetManager { return loadFilter(new FilterKey(name)); } - /** - * Load a vertex/fragment shader combo. - * - * @param key - * @return the loaded {@link Shader} - */ - public Shader loadShader(ShaderKey key){ - // cache abuse in method - // that doesn't use loaders/locators - AssetCache cache = handler.getCache(SimpleAssetCache.class); - Shader shader = (Shader) cache.getFromCache(key); - if (shader == null){ - if (key.isUsesShaderNodes()) { - if(shaderGenerator == null){ - throw new UnsupportedOperationException("ShaderGenerator was not initialized, make sure assetManager.getGenerator(caps) has been called"); - } - shader = shaderGenerator.generateShader(); - } else { - shader = new Shader(); - shader.initialize(); - for (Shader.ShaderType shaderType : key.getUsedShaderPrograms()) { - shader.addSource(shaderType,key.getShaderProgramName(shaderType),(String) loadAsset(new AssetKey(key.getShaderProgramName(shaderType))),key.getDefines().getCompiled(),key.getShaderProgramLanguage(shaderType)); - } - } - - cache.addToCache(key, shader); - } - return shader; - } - /** * {@inheritDoc} */ 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 47788275b..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 @@ -64,9 +64,9 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC 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; @@ -209,9 +209,9 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC 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); } @@ -220,9 +220,9 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC 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); } @@ -280,9 +280,9 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC 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; @@ -299,9 +299,9 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC 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; 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 d53273c4e..1af2bf1d1 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java @@ -65,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 { @@ -100,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; @@ -127,20 +127,20 @@ public class ParticleEmitter extends Geometry { // fixed automatically by ParticleEmitter.clone() method. } - @Override + @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 ) { + @Override + public void cloneFields( Cloner cloner, Object original ) { this.parentEmitter = cloner.clone(parentEmitter); } - + public void setSpatial(Spatial spatial) { } @@ -174,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(); @@ -211,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); @@ -225,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(); @@ -267,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) { @@ -278,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() { @@ -292,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; @@ -325,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); @@ -353,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() { @@ -365,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. */ @@ -387,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() { @@ -401,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)) { @@ -416,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 @@ -437,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; @@ -449,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. */ @@ -459,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; @@ -472,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. */ @@ -484,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; @@ -498,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) } @@ -506,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. */ @@ -516,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; @@ -528,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; @@ -540,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; @@ -551,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) { @@ -565,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; @@ -576,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) { @@ -590,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; @@ -601,7 +646,7 @@ public class ParticleEmitter extends Geometry { /** * This method sets the gravity vector. - * + * * @param gravity the gravity vector */ public void setGravity(Vector3f gravity) { @@ -610,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 @@ -623,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; @@ -634,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) { @@ -646,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; @@ -657,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) { @@ -671,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; @@ -682,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) { @@ -695,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; @@ -706,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) { @@ -719,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; @@ -732,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. */ @@ -740,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; @@ -754,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) { @@ -767,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; @@ -778,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) { @@ -805,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 @@ -818,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 */ @@ -833,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 @@ -923,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. @@ -935,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); @@ -995,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(); @@ -1028,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; @@ -1065,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) { @@ -1077,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; @@ -1088,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) { @@ -1098,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(); @@ -1236,7 +1281,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 58dc87550..82d27aa65 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/light/Light.java b/jme3-core/src/main/java/com/jme3/light/Light.java index 759eea14e..0a66fb729 100644 --- a/jme3-core/src/main/java/com/jme3/light/Light.java +++ b/jme3-core/src/main/java/com/jme3/light/Light.java @@ -191,6 +191,22 @@ public abstract class Light implements Savable, Cloneable { this.enabled = enabled; } + public boolean isFrustumCheckNeeded() { + return frustumCheckNeeded; + } + + public void setFrustumCheckNeeded(boolean frustumCheckNeeded) { + this.frustumCheckNeeded = frustumCheckNeeded; + } + + public boolean isIntersectsFrustum() { + return intersectsFrustum; + } + + public void setIntersectsFrustum(boolean intersectsFrustum) { + this.intersectsFrustum = intersectsFrustum; + } + /** * Determines if the light intersects with the given bounding box. *

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 677c17d55..8d965e363 100644 --- a/jme3-core/src/main/java/com/jme3/material/MatParam.java +++ b/jme3-core/src/main/java/com/jme3/material/MatParam.java @@ -34,7 +34,6 @@ package com.jme3.material; import com.jme3.asset.TextureKey; import com.jme3.export.*; import com.jme3.math.*; -import com.jme3.renderer.Renderer; import com.jme3.shader.VarType; import com.jme3.texture.Texture; import com.jme3.texture.Texture.WrapMode; @@ -129,9 +128,6 @@ public class MatParam implements Savable, Cloneable { this.value = value; } - void apply(Renderer r, Technique technique) { - technique.updateUniformParam(getPrefixedName(), getVarType(), getValue()); - } /** * Returns the material parameter value as it would appear in a J3M diff --git a/jme3-core/src/main/java/com/jme3/material/MatParamOverride.java b/jme3-core/src/main/java/com/jme3/material/MatParamOverride.java new file mode 100644 index 000000000..8a7355b87 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/MatParamOverride.java @@ -0,0 +1,151 @@ +/* + * 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; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.scene.Spatial; +import com.jme3.shader.VarType; +import java.io.IOException; + +/** + * MatParamOverride is a mechanism by which + * {@link MatParam material parameters} can be overridden on the scene graph. + *

+ * A scene branch which has a MatParamOverride applied to it will + * cause all material parameters with the same name and type to have their value + * replaced with the value set on the MatParamOverride. If those + * parameters are mapped to a define, then the define will be overridden as well + * using the same rules as the ones used for regular material parameters. + *

+ * MatParamOverrides are applied to a {@link Spatial} via the + * {@link Spatial#addMatParamOverride(com.jme3.material.MatParamOverride)} + * method. They are propagated to child Spatials via + * {@link Spatial#updateGeometricState()} similar to how lights are propagated. + *

+ * Example:
+ *

+ * {@code
+ *
+ * Geometry box = new Geometry("Box", new Box(1,1,1));
+ * Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ * mat.setColor("Color", ColorRGBA.Blue);
+ * box.setMaterial(mat);
+ * rootNode.attachChild(box);
+ *
+ * // ... later ...
+ * MatParamOverride override = new MatParamOverride(Type.Vector4, "Color", ColorRGBA.Red);
+ * rootNode.addMatParamOverride(override);
+ *
+ * // After adding the override to the root node, the box becomes red.
+ * }
+ * 
+ * + * @author Kirill Vainer + * @see Spatial#addMatParamOverride(com.jme3.material.MatParamOverride) + * @see Spatial#getWorldMatParamOverrides() + */ +public final class MatParamOverride extends MatParam { + + private boolean enabled = true; + + /** + * Serialization only. Do not use. + */ + public MatParamOverride() { + super(); + } + + /** + * Create a new MatParamOverride. + * + * Overrides are created enabled by default. + * + * @param type The type of parameter. + * @param name The name of the parameter. + * @param value The value to set the material parameter to. + */ + public MatParamOverride(VarType type, String name, Object value) { + super(type, name, value); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj) && this.enabled == ((MatParamOverride) obj).enabled; + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 59 * hash + (enabled ? 1 : 0); + return hash; + } + + /** + * Determine if the MatParamOverride is enabled or disabled. + * + * @return true if enabled, false if disabled. + * @see #setEnabled(boolean) + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Enable or disable this MatParamOverride. + * + * When disabled, the override will continue to propagate through the scene + * graph like before, but it will have no effect on materials. Overrides are + * enabled by default. + * + * @param enabled Whether to enable or disable this override. + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(enabled, "enabled", true); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + enabled = ic.readBoolean("enabled", true); + } +} diff --git a/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java b/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java index a3db63a1c..78c8ef33c 100644 --- a/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java +++ b/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java @@ -100,12 +100,6 @@ public class MatParamTexture extends MatParam { return unit; } - @Override - public void apply(Renderer r, Technique technique) { - TechniqueDef techDef = technique.getDef(); - r.setTexture(getUnit(), getTextureValue()); - technique.updateUniformParam(getPrefixedName(), getVarType(), getUnit()); - } @Override public void write(JmeExporter ex) throws IOException { diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index 5f4a4010b..0c5030732 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -34,7 +34,6 @@ package com.jme3.material; import com.jme3.asset.AssetKey; import com.jme3.asset.AssetManager; import com.jme3.asset.CloneableSmartAsset; -import com.jme3.bounding.BoundingSphere; import com.jme3.export.*; import com.jme3.light.*; import com.jme3.material.RenderState.BlendMode; @@ -45,18 +44,16 @@ import com.jme3.math.*; import com.jme3.renderer.Caps; import com.jme3.renderer.RenderManager; import com.jme3.renderer.Renderer; -import com.jme3.renderer.RendererException; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.instancing.InstancedGeometry; import com.jme3.shader.Shader; import com.jme3.shader.Uniform; +import com.jme3.shader.UniformBindingManager; import com.jme3.shader.VarType; +import com.jme3.texture.Image; import com.jme3.texture.Texture; import com.jme3.texture.image.ColorSpace; import com.jme3.util.ListMap; -import com.jme3.util.TempVars; import java.io.IOException; import java.util.*; import java.util.logging.Level; @@ -80,7 +77,6 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { private static final Logger logger = Logger.getLogger(Material.class.getName()); private static final RenderState additiveLight = new RenderState(); private static final RenderState depthOnly = new RenderState(); - private static final Quaternion nullDirLight = new Quaternion(0, -1, 0, -1); static { depthOnly.setDepthTest(true); @@ -105,10 +101,6 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { private int sortingId = -1; private transient ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1); - //Env textures units - int irrUnit = -1; - int pemUnit = -1; - public Material(MaterialDef def) { if (def == null) { throw new NullPointerException("Material definition cannot be null"); @@ -180,22 +172,29 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { * @return The sorting ID used for sorting geometries for rendering. */ public int getSortId() { - Technique t = getActiveTechnique(); - if (sortingId == -1 && t != null && t.getShader() != null) { - int texId = -1; + if (sortingId == -1 && technique != null) { + sortingId = technique.getSortId() << 16; + int texturesSortId = 17; for (int i = 0; i < paramValues.size(); i++) { MatParam param = paramValues.getValue(i); - if (param instanceof MatParamTexture) { - MatParamTexture tex = (MatParamTexture) param; - if (tex.getTextureValue() != null && tex.getTextureValue().getImage() != null) { - if (texId == -1) { - texId = 0; - } - texId += tex.getTextureValue().getImage().getId() % 0xff; - } + if (!param.getVarType().isTextureType()) { + continue; + } + Texture texture = (Texture) param.getValue(); + if (texture == null) { + continue; + } + Image image = texture.getImage(); + if (image == null) { + continue; + } + int textureId = image.getId(); + if (textureId == -1) { + textureId = 0; } + texturesSortId = texturesSortId * 23 + textureId; } - sortingId = texId + t.getShader().getId() * 1000; + sortingId |= texturesSortId & 0xFFFF; } return sortingId; } @@ -220,6 +219,8 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { mat.paramValues.put(entry.getKey(), entry.getValue().clone()); } + mat.sortingId = -1; + return mat; } catch (CloneNotSupportedException ex) { throw new AssertionError(ex); @@ -449,7 +450,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { * * @see #setParam(java.lang.String, com.jme3.shader.VarType, java.lang.Object) */ - public ListMap getParamsMap() { + public ListMap getParamsMap() { return paramValues; } @@ -510,26 +511,22 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { paramValues.remove(name); if (matParam instanceof MatParamTexture) { int texUnit = ((MatParamTexture) matParam).getUnit(); - removeTexUnit(texUnit); + nextTexUnit--; + for (MatParam param : paramValues.values()) { + if (param instanceof MatParamTexture) { + MatParamTexture texParam = (MatParamTexture) param; + if (texParam.getUnit() > texUnit) { + texParam.setUnit(texParam.getUnit() - 1); + } + } + } + sortingId = -1; } if (technique != null) { technique.notifyParamChanged(name, null, null); } } - protected void removeTexUnit(int texUnit) { - nextTexUnit--; - for (MatParam param : paramValues.values()) { - if (param instanceof MatParamTexture) { - MatParamTexture texParam = (MatParamTexture) param; - if (texParam.getUnit() > texUnit) { - texParam.setUnit(texParam.getUnit() - 1); - } - } - } - sortingId = -1; - } - /** * Set a texture parameter. * @@ -704,304 +701,6 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { setParam(name, VarType.Vector4, value); } - private LightProbe extractIndirectLights(LightList lightList, boolean removeLights) { - ambientLightColor.set(0, 0, 0, 1); - LightProbe probe = null; - for (int j = 0; j < lightList.size(); j++) { - Light l = lightList.get(j); - if (l instanceof AmbientLight) { - ambientLightColor.addLocal(l.getColor()); - if(removeLights){ - lightList.remove(l); - j--; - } - } - if (l instanceof LightProbe) { - probe = (LightProbe)l; - if(removeLights){ - lightList.remove(l); - j--; - } - } - } - ambientLightColor.a = 1.0f; - return probe; - } - - private static void renderMeshFromGeometry(Renderer renderer, Geometry geom) { - Mesh mesh = geom.getMesh(); - int lodLevel = geom.getLodLevel(); - if (geom instanceof InstancedGeometry) { - InstancedGeometry instGeom = (InstancedGeometry) geom; - int numInstances = instGeom.getActualNumInstances(); - if (numInstances == 0) { - return; - } - if (renderer.getCaps().contains(Caps.MeshInstancing)) { - renderer.renderMesh(mesh, lodLevel, numInstances, instGeom.getAllInstanceData()); - } else { - throw new RendererException("Mesh instancing is not supported by the video hardware"); - } - } else { - renderer.renderMesh(mesh, lodLevel, 1, null); - } - } - - /** - * Uploads the lights in the light list as two uniform arrays.

* - *

- * uniform vec4 g_LightColor[numLights];
// - * g_LightColor.rgb is the diffuse/specular color of the light.
// - * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point,
// - * 2 = Spot.

- * uniform vec4 g_LightPosition[numLights];
// - * g_LightPosition.xyz is the position of the light (for point lights)
- * // or the direction of the light (for directional lights).
// - * g_LightPosition.w is the inverse radius (1/r) of the light (for - * attenuation)

- */ - protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex) { - if (numLights == 0) { // this shader does not do lighting, ignore. - return 0; - } - - Uniform lightData = shader.getUniform("g_LightData"); - lightData.setVector4Length(numLights * 3);//8 lights * max 3 - Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); - Uniform lightProbeData = shader.getUniform("g_LightProbeData"); - lightProbeData.setVector4Length(1); - Uniform lightProbeIrrMap = shader.getUniform("g_IrradianceMap"); - Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap"); - - LightProbe lightProbe = null; - if (startIndex != 0) { - // apply additive blending for 2nd and future passes - rm.getRenderer().applyRenderState(additiveLight); - ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); - }else{ - lightProbe = extractIndirectLights(lightList,true); - ambientColor.setValue(VarType.Vector4, ambientLightColor); - } - - //If there is a lightProbe in the list we force it's render on the first pass - if(lightProbe != null){ - BoundingSphere s = (BoundingSphere)lightProbe.getBounds(); - lightProbeData.setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f/s.getRadius(), 0); - //assigning new texture indexes if they have never been assigned. - if( irrUnit == -1 ){ - irrUnit = nextTexUnit++; - pemUnit = nextTexUnit++; - } - rm.getRenderer().setTexture(irrUnit, lightProbe.getIrradianceMap()); - lightProbeIrrMap.setValue(VarType.Int, irrUnit); - rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap()); - lightProbePemMap.setValue(VarType.Int, pemUnit); - } else { - //Disable IBL for this pass - lightProbeData.setVector4InArray(0,0,0,-1, 0); - } - - int lightDataIndex = 0; - TempVars vars = TempVars.get(); - Vector4f tmpVec = vars.vect4f1; - int curIndex; - int endIndex = numLights + startIndex; - boolean useIBL = false; - for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) { - - - Light l = lightList.get(curIndex); - if(l.getType() == Light.Type.Ambient){ - endIndex++; - continue; - } - ColorRGBA color = l.getColor(); - //Color - - if(l.getType() != Light.Type.Probe){ - lightData.setVector4InArray(color.getRed(), - color.getGreen(), - color.getBlue(), - l.getType().getId(), - lightDataIndex); - lightDataIndex++; - } - - switch (l.getType()) { - case Directional: - DirectionalLight dl = (DirectionalLight) l; - Vector3f dir = dl.getDirection(); - //Data directly sent in view space to avoid a matrix mult for each pixel - tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f); - transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpVec); - lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex); - lightDataIndex++; - //PADDING - lightData.setVector4InArray(0,0,0,0, lightDataIndex); - lightDataIndex++; - break; - case Point: - PointLight pl = (PointLight) l; - Vector3f pos = pl.getPosition(); - float invRadius = pl.getInvRadius(); - tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f); - transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpVec); - - lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex); - lightDataIndex++; - //PADDING - lightData.setVector4InArray(0,0,0,0, lightDataIndex); - lightDataIndex++; - break; - case Spot: - SpotLight sl = (SpotLight) l; - Vector3f pos2 = sl.getPosition(); - Vector3f dir2 = sl.getDirection(); - float invRange = sl.getInvSpotRange(); - float spotAngleCos = sl.getPackedAngleCos(); - tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(), 1.0f); - transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpVec); - - lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex); - lightDataIndex++; - - tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0.0f); - transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpVec); - lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex); - lightDataIndex++; - break; - default: - throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); - } - } - vars.release(); - - //Padding of unsued buffer space - while(lightDataIndex < numLights * 3) { - lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex); - lightDataIndex++; - } - return curIndex; - } - - protected void renderMultipassLighting(Shader shader, Geometry g, LightList lightList, RenderManager rm) { - - Renderer r = rm.getRenderer(); - Uniform lightDir = shader.getUniform("g_LightDirection"); - Uniform lightColor = shader.getUniform("g_LightColor"); - Uniform lightPos = shader.getUniform("g_LightPosition"); - Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); - boolean isFirstLight = true; - boolean isSecondLight = false; - - for (int i = 0; i < lightList.size(); i++) { - Light l = lightList.get(i); - if (l instanceof AmbientLight || l instanceof LightProbe ) { - continue; - } - - if (isFirstLight) { - // set ambient color for first light only - extractIndirectLights(lightList, false); - ambientColor.setValue(VarType.Vector4, ambientLightColor); - isFirstLight = false; - isSecondLight = true; - } else if (isSecondLight) { - ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); - // apply additive blending for 2nd and future lights - r.applyRenderState(additiveLight); - isSecondLight = false; - } - - TempVars vars = TempVars.get(); - Quaternion tmpLightDirection = vars.quat1; - Vector4f tmpLightPosition = vars.vect4f2; - ColorRGBA tmpLightColor = vars.color; - Vector4f tmpVec = vars.vect4f1; - - ColorRGBA color = l.getColor(); - tmpLightColor.set(color); - tmpLightColor.a = l.getType().getId(); - lightColor.setValue(VarType.Vector4, tmpLightColor); - - switch (l.getType()) { - case Directional: - DirectionalLight dl = (DirectionalLight) l; - Vector3f dir = dl.getDirection(); - //FIXME : there is an inconstency here due to backward - //compatibility of the lighting shader. - //The directional light direction is passed in the - //LightPosition uniform. The lighting shader needs to be - //reworked though in order to fix this. - tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1); - transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpLightPosition); - lightPos.setValue(VarType.Vector4, tmpLightPosition); - tmpLightDirection.set(0, 0, 0, 0); - lightDir.setValue(VarType.Vector4, tmpLightDirection); - break; - case Point: - PointLight pl = (PointLight) l; - Vector3f pos = pl.getPosition(); - float invRadius = pl.getInvRadius(); - - tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius); - transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpLightPosition); - lightPos.setValue(VarType.Vector4, tmpLightPosition); - tmpLightDirection.set(0, 0, 0, 0); - lightDir.setValue(VarType.Vector4, tmpLightDirection); - break; - case Spot: - SpotLight sl = (SpotLight) l; - Vector3f pos2 = sl.getPosition(); - Vector3f dir2 = sl.getDirection(); - float invRange = sl.getInvSpotRange(); - float spotAngleCos = sl.getPackedAngleCos(); - - tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange); - transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpLightPosition); - lightPos.setValue(VarType.Vector4, tmpLightPosition); - - tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0); - if (technique.getDef().getLightSpace() == TechniqueDef.LightSpace.Legacy) { - //Legacy kept for backward compatibility. - //We transform the spot direction in view space here to save 5 varying later in the lighting shader - //one vec4 less and a vec4 that becomes a vec3 - //the downside is that spotAngleCos decoding happens now in the frag shader. - transposeLightDataToSpace(TechniqueDef.LightSpace.View, rm, tmpVec); - } else { - transposeLightDataToSpace(technique.getDef().getLightSpace(), rm, tmpVec); - } - tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos); - - lightDir.setValue(VarType.Vector4, tmpLightDirection); - - break; - default: - throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); - } - vars.release(); - r.setShader(shader); - renderMeshFromGeometry(r, g); - } - - if (isFirstLight) { - // Either there are no lights at all, or only ambient lights. - // Render a dummy "normal light" so we can see the ambient color. - extractIndirectLights(lightList, false); - ambientColor.setValue(VarType.Vector4, ambientLightColor); - lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha); - lightPos.setValue(VarType.Vector4, nullDirLight); - r.setShader(shader); - renderMeshFromGeometry(r, g); - } - } - - private void transposeLightDataToSpace(TechniqueDef.LightSpace space, RenderManager rm, Vector4f store){ - if(space == TechniqueDef.LightSpace.View){ - rm.getCurrentCamera().getViewMatrix().mult(store, store); - } - }; - /** * Select the technique to use for rendering this material. *

@@ -1030,9 +729,8 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { Technique tech = techniques.get(name); // When choosing technique, we choose one that // supports all the caps. - EnumSet rendererCaps = renderManager.getRenderer().getCaps(); if (tech == null) { - + EnumSet rendererCaps = renderManager.getRenderer().getCaps(); if (name.equals("Default")) { List techDefs = def.getDefaultTechniques(); if (techDefs == null || techDefs.isEmpty()) { @@ -1081,20 +779,73 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { } technique = tech; - tech.makeCurrent(def.getAssetManager(), true, rendererCaps, renderManager); + tech.notifyTechniqueSwitched(); // shader was changed sortingId = -1; } - private void autoSelectTechnique(RenderManager rm) { - if (technique == null) { - selectTechnique("Default", rm); - } else { - technique.makeCurrent(def.getAssetManager(), false, rm.getRenderer().getCaps(), rm); + private int updateShaderMaterialParameters(Renderer renderer, Shader shader, List overrides) { + int unit = 0; + + if (overrides != null) { + for (MatParamOverride override : overrides) { + VarType type = override.getVarType(); + + MatParam paramDef = def.getMaterialParam(override.getName()); + if (paramDef == null || paramDef.getVarType() != type || !override.isEnabled()) { + continue; + } + + Uniform uniform = shader.getUniform(override.getPrefixedName()); + if (override.getValue() != null) { + if (type.isTextureType()) { + renderer.setTexture(unit, (Texture) override.getValue()); + uniform.setValue(VarType.Int, unit); + unit++; + } else { + uniform.setValue(type, override.getValue()); + } + } else { + uniform.clearValue(); + } + } } + + for (int i = 0; i < paramValues.size(); i++) { + MatParam param = paramValues.getValue(i); + VarType type = param.getVarType(); + Uniform uniform = shader.getUniform(param.getPrefixedName()); + + if (uniform.isSetByCurrentMaterial()) { + continue; + } + + if (type.isTextureType()) { + renderer.setTexture(unit, (Texture) param.getValue()); + uniform.setValue(VarType.Int, unit); + unit++; + } else { + uniform.setValue(type, param.getValue()); + } + } + + //TODO HACKY HACK remove this when texture unit is handled by the uniform. + return unit; } + private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) { + if (renderManager.getForcedRenderState() != null) { + renderer.applyRenderState(renderManager.getForcedRenderState()); + } else { + if (techniqueDef.getRenderState() != null) { + renderer.applyRenderState(techniqueDef.getRenderState().copyMergedTo(additionalState, mergedRenderState)); + } else { + renderer.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState)); + } + } + } + /** * Preloads this material for the given render manager. *

@@ -1102,20 +853,23 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { * used for rendering, there won't be any delay since the material has * been already been setup for rendering. * - * @param rm The render manager to preload for + * @param renderManager The render manager to preload for */ - public void preload(RenderManager rm) { - autoSelectTechnique(rm); - - Renderer r = rm.getRenderer(); - TechniqueDef techDef = technique.getDef(); + public void preload(RenderManager renderManager) { + if (technique == null) { + selectTechnique("Default", renderManager); + } + TechniqueDef techniqueDef = technique.getDef(); + Renderer renderer = renderManager.getRenderer(); + EnumSet rendererCaps = renderer.getCaps(); - Collection params = paramValues.values(); - for (MatParam param : params) { - param.apply(r, technique); + if (techniqueDef.isNoRender()) { + return; } - r.setShader(technique.getShader()); + Shader shader = technique.makeCurrent(renderManager, null, null, rendererCaps); + updateShaderMaterialParameters(renderer, shader, null); + renderManager.getRenderer().setShader(shader); } private void clearUniformsSetByCurrent(Shader shader) { @@ -1197,83 +951,47 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { * * * - * @param geom The geometry to render + * @param geometry The geometry to render * @param lights Presorted and filtered light list to use for rendering - * @param rm The render manager requesting the rendering + * @param renderManager The render manager requesting the rendering */ - public void render(Geometry geom, LightList lights, RenderManager rm) { - autoSelectTechnique(rm); - TechniqueDef techDef = technique.getDef(); - - if (techDef.isNoRender()) return; - - Renderer r = rm.getRenderer(); - - if (rm.getForcedRenderState() != null) { - r.applyRenderState(rm.getForcedRenderState()); - } else { - if (techDef.getRenderState() != null) { - r.applyRenderState(techDef.getRenderState().copyMergedTo(additionalState, mergedRenderState)); - } else { - r.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState)); - } + public void render(Geometry geometry, LightList lights, RenderManager renderManager) { + if (technique == null) { + selectTechnique("Default", renderManager); } - - - // update camera and world matrices - // NOTE: setWorldTransform should have been called already - - // reset unchanged uniform flag - clearUniformsSetByCurrent(technique.getShader()); - rm.updateUniformBindings(technique.getWorldBindUniforms()); - - - // setup textures and uniforms - for (int i = 0; i < paramValues.size(); i++) { - MatParam param = paramValues.getValue(i); - param.apply(r, technique); + + TechniqueDef techniqueDef = technique.getDef(); + Renderer renderer = renderManager.getRenderer(); + EnumSet rendererCaps = renderer.getCaps(); + + if (techniqueDef.isNoRender()) { + return; } - Shader shader = technique.getShader(); + // Apply render state + updateRenderState(renderManager, renderer, techniqueDef); - - // send lighting information, if needed - switch (techDef.getLightMode()) { - case Disable: - break; - case SinglePass: - assert technique.getDef().getLightSpace()!= null; - int nbRenderedLights = 0; - resetUniformsNotSetByCurrent(shader); - if (lights.size() == 0) { - updateLightListUniforms(shader, geom, lights, rm.getSinglePassLightBatchSize(), rm, 0); - r.setShader(shader); - renderMeshFromGeometry(r, geom); - } else { - while (nbRenderedLights < lights.size()) { - nbRenderedLights = updateLightListUniforms(shader, geom, lights, rm.getSinglePassLightBatchSize(), rm, nbRenderedLights); - r.setShader(shader); - renderMeshFromGeometry(r, geom); - } - } - return; - case FixedPipeline: - throw new IllegalArgumentException("OpenGL1 is not supported"); - case MultiPass: - assert technique.getDef().getLightSpace()!= null; - // NOTE: Special case! - resetUniformsNotSetByCurrent(shader); - renderMultipassLighting(shader, geom, lights, rm); - // very important, notice the return statement! - return; - } + // Get world overrides + List overrides = geometry.getWorldMatParamOverrides(); - // upload and bind shader - // any unset uniforms will be set to 0 + // Select shader to use + Shader shader = technique.makeCurrent(renderManager, overrides, lights, rendererCaps); + + // Begin tracking which uniforms were changed by material. + clearUniformsSetByCurrent(shader); + + // Set uniform bindings + renderManager.updateUniformBindings(shader); + + // Set material parameters + //TODO RRemove the unit when texture units are handled in the Uniform + int unit = updateShaderMaterialParameters(renderer, shader, geometry.getWorldMatParamOverrides()); + + // Clear any uniforms not changed by material. resetUniformsNotSetByCurrent(shader); - r.setShader(shader); - - renderMeshFromGeometry(r, geom); + + // Delegate rendering to the technique + technique.render(renderManager, shader, geometry, lights, unit); } /** @@ -1298,6 +1016,14 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { oc.write(name, "name", null); oc.writeStringSavableMap(paramValues, "parameters", null); } + + @Override + public String toString() { + return "Material[name=" + name + + ", def=" + def.getName() + + ", tech=" + technique.getDef().getName() + + "]"; + } public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); 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 94cae3f50..7ff4a992a 100644 --- a/jme3-core/src/main/java/com/jme3/material/RenderState.java +++ b/jme3-core/src/main/java/com/jme3/material/RenderState.java @@ -822,6 +822,9 @@ public class RenderState implements Cloneable, Savable { * @param lineWidth the line width. */ public void setLineWidth(float lineWidth) { + if (lineWidth < 1f) { + throw new IllegalArgumentException("lineWidth must be greater than or equal to 1.0"); + } this.lineWidth = lineWidth; this.applyLineWidth = true; cachedHashCode = -1; diff --git a/jme3-core/src/main/java/com/jme3/material/Technique.java b/jme3-core/src/main/java/com/jme3/material/Technique.java index 71fde5759..28ef9e87b 100644 --- a/jme3-core/src/main/java/com/jme3/material/Technique.java +++ b/jme3-core/src/main/java/com/jme3/material/Technique.java @@ -31,27 +31,30 @@ */ package com.jme3.material; +import com.jme3.material.logic.TechniqueDefLogic; import com.jme3.asset.AssetManager; +import com.jme3.light.LightList; +import com.jme3.material.TechniqueDef.LightMode; import com.jme3.renderer.Caps; import com.jme3.renderer.RenderManager; -import com.jme3.shader.*; +import com.jme3.scene.Geometry; +import com.jme3.shader.DefineList; +import com.jme3.shader.Shader; +import com.jme3.shader.VarType; +import com.jme3.util.ListMap; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; -import java.util.logging.Logger; /** * Represents a technique instance. */ -public class Technique /* implements Savable */ { +public final class Technique { - private static final Logger logger = Logger.getLogger(Technique.class.getName()); - private TechniqueDef def; - private Material owner; - private ArrayList worldBindUniforms; - private DefineList defines; - private Shader shader; - private boolean needReload = true; + private final TechniqueDef def; + private final Material owner; + private final DefineList paramDefines; + private final DefineList dynamicDefines; /** * Creates a new technique instance that implements the given @@ -63,14 +66,8 @@ public class Technique /* implements Savable */ { public Technique(Material owner, TechniqueDef def) { this.owner = owner; this.def = def; - this.worldBindUniforms = new ArrayList(); - this.defines = new DefineList(); - } - - /** - * Serialization only. Do not use. - */ - public Technique() { + this.paramDefines = def.createDefineList(); + this.dynamicDefines = def.createDefineList(); } /** @@ -85,157 +82,117 @@ public class Technique /* implements Savable */ { } /** - * Returns the shader currently used by this technique instance. - *

- * Shaders are typically loaded dynamically when the technique is first - * used, therefore, this variable will most likely be null most of the time. - * - * @return the shader currently used by this technique instance. + * Called by the material to tell the technique a parameter was modified. + * Specify null for value if the param is to be cleared. */ - public Shader getShader() { - return shader; - } + final void notifyParamChanged(String paramName, VarType type, Object value) { + Integer defineId = def.getShaderParamDefineId(paramName); + if (defineId == null) { + return; + } + + paramDefines.set(defineId, type, value); + } + /** - * Returns a list of uniforms that implements the world parameters - * that were requested by the material definition. - * - * @return a list of uniforms implementing the world parameters. + * Called by the material to tell the technique that it has been made + * current. + * The technique updates dynamic defines based on the + * currently set material parameters. */ - public List getWorldBindUniforms() { - return worldBindUniforms; + final void notifyTechniqueSwitched() { + ListMap paramMap = owner.getParamsMap(); + paramDefines.clear(); + for (int i = 0; i < paramMap.size(); i++) { + MatParam param = paramMap.getValue(i); + notifyParamChanged(param.getName(), param.getVarType(), param.getValue()); + } } /** - * Called by the material to tell the technique a parameter was modified. - * Specify null for value if the param is to be cleared. + * Called by the material to determine which shader to use for rendering. + * + * The {@link TechniqueDefLogic} is used to determine the shader to use + * based on the {@link LightMode}. + * + * @param renderManager The render manager for which the shader is to be selected. + * @param rendererCaps The renderer capabilities which the shader should support. + * @return A compatible shader. */ - void notifyParamChanged(String paramName, VarType type, Object value) { - // Check if there's a define binding associated with this - // parameter. - String defineName = def.getShaderParamDefine(paramName); - if (defineName != null) { - // There is a define. Change it on the define list. - // The "needReload" variable will determine - // if the shader will be reloaded when the material - // is rendered. - - if (value == null) { - // Clear the define. - needReload = defines.remove(defineName) || needReload; - } else { - // Set the define. - needReload = defines.set(defineName, type, value) || needReload; + Shader makeCurrent(RenderManager renderManager, List overrides, + LightList lights, EnumSet rendererCaps) { + TechniqueDefLogic logic = def.getLogic(); + AssetManager assetManager = owner.getMaterialDef().getAssetManager(); + + dynamicDefines.clear(); + dynamicDefines.setAll(paramDefines); + + if (overrides != null) { + for (MatParamOverride override : overrides) { + if (!override.isEnabled()) { + continue; + } + Integer defineId = def.getShaderParamDefineId(override.name); + if (defineId != null) { + if (def.getDefineIdType(defineId) == override.type) { + dynamicDefines.set(defineId, override.type, override.value); + } + } } } - } - void updateUniformParam(String paramName, VarType type, Object value) { - if (paramName == null) { - throw new IllegalArgumentException(); - } - - Uniform u = shader.getUniform(paramName); - switch (type) { - case TextureBuffer: - case Texture2D: // fall intentional - case Texture3D: - case TextureArray: - case TextureCubeMap: - case Int: - u.setValue(VarType.Int, value); - break; - default: - u.setValue(type, value); - break; - } + return logic.makeCurrent(assetManager, renderManager, rendererCaps, lights, dynamicDefines); } - + /** - * Returns true if the technique must be reloaded. - *

- * If a technique needs to reload, then the {@link Material} should - * call {@link #makeCurrent(com.jme3.asset.AssetManager) } on this - * technique. + * Render the technique according to its {@link TechniqueDefLogic}. * - * @return true if the technique must be reloaded. + * @param renderManager The render manager to perform the rendering against. + * @param shader The shader that was selected in + * {@link #makeCurrent(com.jme3.renderer.RenderManager, java.util.EnumSet)}. + * @param geometry The geometry to render + * @param lights Lights which influence the geometry. */ - public boolean isNeedReload() { - return needReload; + void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + TechniqueDefLogic logic = def.getLogic(); + logic.render(renderManager, shader, geometry, lights, lastTexUnit); } - + /** - * Prepares the technique for use by loading the shader and setting - * the proper defines based on material parameters. + * Get the {@link DefineList} for dynamic defines. * - * @param assetManager The asset manager to use for loading shaders. + * Dynamic defines are used to implement material parameter -> define + * bindings as well as {@link TechniqueDefLogic} specific functionality. + * + * @return all dynamic defines. */ - public void makeCurrent(AssetManager assetManager, boolean techniqueSwitched, EnumSet rendererCaps, RenderManager rm) { - if (techniqueSwitched) { - if (defines.update(owner.getParamsMap(), def)) { - needReload = true; - } - if (getDef().getLightMode() == TechniqueDef.LightMode.SinglePass) { - defines.set("SINGLE_PASS_LIGHTING", VarType.Boolean, true); - defines.set("NB_LIGHTS", VarType.Int, rm.getSinglePassLightBatchSize() * 3); - } else { - defines.set("SINGLE_PASS_LIGHTING", VarType.Boolean, null); - } - } - - if (needReload) { - loadShader(assetManager,rendererCaps); - } - } - - private void loadShader(AssetManager manager,EnumSet rendererCaps) { - - ShaderKey key = new ShaderKey(getAllDefines(),def.getShaderProgramLanguages(),def.getShaderProgramNames()); - - if (getDef().isUsingShaderNodes()) { - manager.getShaderGenerator(rendererCaps).initialize(this); - key.setUsesShaderNodes(true); - } - shader = manager.loadShader(key); - - // register the world bound uniforms - worldBindUniforms.clear(); - if (def.getWorldBindings() != null) { - for (UniformBinding binding : def.getWorldBindings()) { - Uniform uniform = shader.getUniform("g_" + binding.name()); - uniform.setBinding(binding); - worldBindUniforms.add(uniform); - } - } - needReload = false; + public DefineList getDynamicDefines() { + return dynamicDefines; } /** - * Computes the define list - * @return the complete define list + * @return nothing. + * + * @deprecated Preset defines are precompiled into + * {@link TechniqueDef#getShaderPrologue()}, whereas + * dynamic defines are available via {@link #getParamDefines()}. */ + @Deprecated public DefineList getAllDefines() { - DefineList allDefines = new DefineList(); - allDefines.addFrom(def.getShaderPresetDefines()); - allDefines.addFrom(defines); - return allDefines; - } - - /* - public void write(JmeExporter ex) throws IOException { - OutputCapsule oc = ex.getCapsule(this); - oc.write(def, "def", null); - oc.writeSavableArrayList(worldBindUniforms, "worldBindUniforms", null); - oc.write(defines, "defines", null); - oc.write(shader, "shader", null); + throw new UnsupportedOperationException(); } - - public void read(JmeImporter im) throws IOException { - InputCapsule ic = im.getCapsule(this); - def = (TechniqueDef) ic.readSavable("def", null); - worldBindUniforms = ic.readSavableArrayList("worldBindUniforms", null); - defines = (DefineList) ic.readSavable("defines", null); - shader = (Shader) ic.readSavable("shader", null); + + /** + * Compute the sort ID. Similar to {@link Object#hashCode()} but used + * for sorting geometries for rendering. + * + * @return the sort ID for this technique instance. + */ + public int getSortId() { + int hash = 17; + hash = hash * 23 + def.getSortId(); + hash = hash * 23 + paramDefines.hashCode(); + return hash; } - */ } diff --git a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java index 3a490f70b..a1cbc3abb 100644 --- a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java +++ b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java @@ -31,9 +31,12 @@ */ package com.jme3.material; +import com.jme3.material.logic.TechniqueDefLogic; +import com.jme3.asset.AssetManager; import com.jme3.export.*; import com.jme3.renderer.Caps; import com.jme3.shader.*; +import com.jme3.shader.Shader.ShaderType; import java.io.IOException; import java.util.*; @@ -78,6 +81,16 @@ public class TechniqueDef implements Savable { */ MultiPass, + /** + * Enable light rendering by using a single pass, and also uses Image based lighting for global lighting + * Usually used for PBR + *

+ * An array of light positions and light colors is passed to the shader + * containing the world light list for the geometry being rendered. + * Also Light probes are passed to the shader. + */ + SinglePassAndImageBased, + /** * @deprecated OpenGL1 is not supported anymore */ @@ -102,11 +115,17 @@ public class TechniqueDef implements Savable { private EnumSet requiredCaps = EnumSet.noneOf(Caps.class); private String name; - + private int sortId; + private EnumMap shaderLanguages; private EnumMap shaderNames; - private DefineList presetDefines; + private String shaderPrologue; + private ArrayList defineNames; + private ArrayList defineTypes; + private HashMap paramToDefineId; + private final HashMap definesToShaderMap; + private boolean usesNodes = false; private List shaderNodes; private ShaderGenerationInfo shaderGenerationInfo; @@ -115,10 +134,10 @@ public class TechniqueDef implements Savable { private RenderState renderState; private RenderState forcedRenderState; - private LightMode lightMode = LightMode.Disable; + private LightMode lightMode = LightMode.Disable; private ShadowMode shadowMode = ShadowMode.Disable; + private TechniqueDefLogic logic; - private HashMap defineParams; private ArrayList worldBinds; //The space in which the light should be transposed before sending to the shader. private LightSpace lightSpace; @@ -131,17 +150,30 @@ public class TechniqueDef implements Savable { * @param name The name of the technique, should be set to null * for default techniques. */ - public TechniqueDef(String name){ + public TechniqueDef(String name, int sortId){ this(); + this.sortId = sortId; this.name = name == null ? "Default" : name; } /** * Serialization only. Do not use. */ - public TechniqueDef(){ - shaderLanguages=new EnumMap(Shader.ShaderType.class); - shaderNames=new EnumMap(Shader.ShaderType.class); + public TechniqueDef() { + shaderLanguages = new EnumMap(Shader.ShaderType.class); + shaderNames = new EnumMap(Shader.ShaderType.class); + defineNames = new ArrayList(); + defineTypes = new ArrayList(); + paramToDefineId = new HashMap(); + definesToShaderMap = new HashMap(); + } + + /** + * @return A unique sort ID. + * No other technique definition can have the same ID. + */ + public int getSortId() { + return sortId; } /** @@ -181,7 +213,15 @@ public class TechniqueDef implements Savable { } } } + + public void setLogic(TechniqueDefLogic logic) { + this.logic = logic; + } + public TechniqueDefLogic getLogic() { + return logic; + } + /** * Returns the shadow mode. * @return the shadow mode. @@ -243,14 +283,6 @@ public class TechniqueDef implements Savable { return noRender; } - /** - * @deprecated jME3 always requires shaders now - */ - @Deprecated - public boolean isUsingShaders(){ - return true; - } - /** * Returns true if this technique uses Shader Nodes, false otherwise. * @@ -292,34 +324,24 @@ public class TechniqueDef implements Savable { requiredCaps.add(fragCap); } - /** - * Sets the shaders that this technique definition will use. - * - * @param shaderNames EnumMap containing all shader names for this stage - * @param shaderLanguages EnumMap containing all shader languages for this stage + * Set a string which is prepended to every shader used by this technique. + * + * Typically this is used for preset defines. + * + * @param shaderPrologue The prologue to append before the technique's shaders. */ - public void setShaderFile(EnumMap shaderNames, EnumMap shaderLanguages) { - requiredCaps.clear(); - - for (Shader.ShaderType shaderType : shaderNames.keySet()) { - String language = shaderLanguages.get(shaderType); - String shaderFile = shaderNames.get(shaderType); - - this.shaderLanguages.put(shaderType, language); - this.shaderNames.put(shaderType, shaderFile); - - Caps vertCap = Caps.valueOf(language); - requiredCaps.add(vertCap); - - if (shaderType.equals(Shader.ShaderType.Geometry)) { - requiredCaps.add(Caps.GeometryShader); - } else if (shaderType.equals(Shader.ShaderType.TessellationControl)) { - requiredCaps.add(Caps.TesselationShader); - } - } + public void setShaderPrologue(String shaderPrologue) { + this.shaderPrologue = shaderPrologue; } - + + /** + * @return the shader prologue which is prepended to every shader. + */ + public String getShaderPrologue() { + return shaderPrologue; + } + /** * Returns the define name which the given material parameter influences. * @@ -329,60 +351,186 @@ public class TechniqueDef implements Savable { * @see #addShaderParamDefine(java.lang.String, java.lang.String) */ public String getShaderParamDefine(String paramName){ - if (defineParams == null) { + Integer defineId = paramToDefineId.get(paramName); + if (defineId != null) { + return defineNames.get(defineId); + } else { return null; } - return defineParams.get(paramName); + } + + /** + * Get the define ID for a given material parameter. + * + * @param paramName The parameter name to look up + * @return The define ID, or null if not found. + */ + public Integer getShaderParamDefineId(String paramName) { + return paramToDefineId.get(paramName); } + /** + * Get the type of a particular define. + * + * @param defineId The define ID to lookup. + * @return The type of the define, or null if not found. + */ + public VarType getDefineIdType(int defineId) { + return defineId < defineTypes.size() ? defineTypes.get(defineId) : null; + } + /** * Adds a define linked to a material parameter. *

* Any time the material parameter on the parent material is altered, * the appropriate define on the technique will be modified as well. - * See the method - * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) } - * on the exact details of how the material parameter changes the define. + * When set, the material parameter will be mapped to an integer define, + * typically 1 if it is set, unless it is an integer or a float, + * in which case it will converted into an integer. * * @param paramName The name of the material parameter to link to. + * @param paramType The type of the material parameter to link to. * @param defineName The name of the define parameter, e.g. USE_LIGHTING */ - public void addShaderParamDefine(String paramName, String defineName){ - if (defineParams == null) { - defineParams = new HashMap(); + public void addShaderParamDefine(String paramName, VarType paramType, String defineName){ + int defineId = defineNames.size(); + + if (defineId >= DefineList.MAX_DEFINES) { + throw new IllegalStateException("Cannot have more than " + + DefineList.MAX_DEFINES + " defines on a technique."); + } + + paramToDefineId.put(paramName, defineId); + defineNames.add(defineName); + defineTypes.add(paramType); + } + + /** + * Add an unmapped define which can only be set by define ID. + * + * Unmapped defines are used by technique renderers to + * configure the shader internally before rendering. + * + * @param defineName The define name to create + * @return The define ID of the created define + */ + public int addShaderUnmappedDefine(String defineName, VarType defineType) { + int defineId = defineNames.size(); + + if (defineId >= DefineList.MAX_DEFINES) { + throw new IllegalStateException("Cannot have more than " + + DefineList.MAX_DEFINES + " defines on a technique."); } - defineParams.put(paramName, defineName); + + defineNames.add(defineName); + defineTypes.add(defineType); + return defineId; } /** - * Returns the {@link DefineList} for the preset defines. + * Get the names of all defines declared on this technique definition. * - * @return the {@link DefineList} for the preset defines. + * The defines are returned in order of declaration. * - * @see #addShaderPresetDefine(java.lang.String, com.jme3.shader.VarType, java.lang.Object) + * @return the names of all defines declared. */ - public DefineList getShaderPresetDefines() { - return presetDefines; + public String[] getDefineNames() { + return defineNames.toArray(new String[0]); } /** - * Adds a preset define. - *

- * Preset defines do not depend upon any parameters to be activated, - * they are always passed to the shader as long as this technique is used. + * Get the types of all defines declared on this technique definition. * - * @param defineName The name of the define parameter, e.g. USE_LIGHTING - * @param type The type of the define. See - * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) } - * to see why it matters. + * The types are returned in order of declaration. * - * @param value The value of the define + * @return the types of all defines declared. */ - public void addShaderPresetDefine(String defineName, VarType type, Object value){ - if (presetDefines == null) { - presetDefines = new DefineList(); + public VarType[] getDefineTypes() { + return defineTypes.toArray(new VarType[0]); + } + + /** + * Create a define list with the size matching the number + * of defines on this technique. + * + * @return a define list with the size matching the number + * of defines on this technique. + */ + public DefineList createDefineList() { + return new DefineList(defineNames.size()); + } + + private Shader loadShader(AssetManager assetManager, EnumSet rendererCaps, DefineList defines) { + StringBuilder sb = new StringBuilder(); + sb.append(shaderPrologue); + defines.generateSource(sb, defineNames, defineTypes); + String definesSourceCode = sb.toString(); + + Shader shader; + if (isUsingShaderNodes()) { + ShaderGenerator shaderGenerator = assetManager.getShaderGenerator(rendererCaps); + if (shaderGenerator == null) { + throw new UnsupportedOperationException("ShaderGenerator was not initialized, " + + "make sure assetManager.getGenerator(caps) has been called"); + } + shaderGenerator.initialize(this); + shader = shaderGenerator.generateShader(definesSourceCode); + } else { + shader = new Shader(); + for (ShaderType type : ShaderType.values()) { + String language = shaderLanguages.get(type); + String shaderSourceAssetName = shaderNames.get(type); + if (language == null || shaderSourceAssetName == null) { + continue; + } + String shaderSourceCode = (String) assetManager.loadAsset(shaderSourceAssetName); + shader.addSource(type, shaderSourceAssetName, shaderSourceCode, definesSourceCode, language); + } + } + + if (getWorldBindings() != null) { + for (UniformBinding binding : getWorldBindings()) { + shader.addUniformBinding(binding); + } + } + + return shader; + } + + public Shader getShader(AssetManager assetManager, EnumSet rendererCaps, DefineList defines) { + Shader shader = definesToShaderMap.get(defines); + if (shader == null) { + shader = loadShader(assetManager, rendererCaps, defines); + definesToShaderMap.put(defines.deepClone(), shader); + } + return shader; + } + + /** + * Sets the shaders that this technique definition will use. + * + * @param shaderNames EnumMap containing all shader names for this stage + * @param shaderLanguages EnumMap containing all shader languages for this stage + */ + public void setShaderFile(EnumMap shaderNames, EnumMap shaderLanguages) { + requiredCaps.clear(); + + for (Shader.ShaderType shaderType : shaderNames.keySet()) { + String language = shaderLanguages.get(shaderType); + String shaderFile = shaderNames.get(shaderType); + + this.shaderLanguages.put(shaderType, language); + this.shaderNames.put(shaderType, shaderFile); + + Caps vertCap = Caps.valueOf(language); + requiredCaps.add(vertCap); + + if (shaderType.equals(Shader.ShaderType.Geometry)) { + requiredCaps.add(Caps.GeometryShader); + } else if (shaderType.equals(Shader.ShaderType.TessellationControl)) { + requiredCaps.add(Caps.TesselationShader); + } } - presetDefines.set(defineName, type, value); } /** @@ -486,7 +634,7 @@ public class TechniqueDef implements Savable { oc.write(shaderLanguages.get(Shader.ShaderType.TessellationControl), "tsctrlLanguage", null); oc.write(shaderLanguages.get(Shader.ShaderType.TessellationEvaluation), "tsevalLanguage", null); - oc.write(presetDefines, "presetDefines", null); + oc.write(shaderPrologue, "shaderPrologue", null); oc.write(lightMode, "lightMode", LightMode.Disable); oc.write(shadowMode, "shadowMode", ShadowMode.Disable); oc.write(renderState, "renderState", null); @@ -509,7 +657,7 @@ public class TechniqueDef implements Savable { shaderNames.put(Shader.ShaderType.Geometry,ic.readString("geomName", null)); shaderNames.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlName", null)); shaderNames.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalName", null)); - presetDefines = (DefineList) ic.readSavable("presetDefines", null); + shaderPrologue = ic.readString("shaderPrologue", null); lightMode = ic.readEnum("lightMode", LightMode.class, LightMode.Disable); shadowMode = ic.readEnum("shadowMode", ShadowMode.class, ShadowMode.Disable); renderState = (RenderState) ic.readSavable("renderState", null); @@ -566,11 +714,16 @@ public class TechniqueDef implements Savable { this.shaderGenerationInfo = shaderGenerationInfo; } - //todo: make toString return something usefull @Override public String toString() { - return "TechniqueDef{" + "requiredCaps=" + requiredCaps + ", name=" + name /*+ ", vertName=" + vertName + ", fragName=" + fragName + ", vertLanguage=" + vertLanguage + ", fragLanguage=" + fragLanguage */+ ", presetDefines=" + presetDefines + ", usesNodes=" + usesNodes + ", shaderNodes=" + shaderNodes + ", shaderGenerationInfo=" + shaderGenerationInfo + ", renderState=" + renderState + ", forcedRenderState=" + forcedRenderState + ", lightMode=" + lightMode + ", shadowMode=" + shadowMode + ", defineParams=" + defineParams + ", worldBinds=" + worldBinds + '}'; - } + return "TechniqueDef[name=" + name + + ", requiredCaps=" + requiredCaps + + ", noRender=" + noRender + + ", lightMode=" + lightMode + + ", usesNodes=" + usesNodes + + ", renderState=" + renderState + + ", forcedRenderState=" + forcedRenderState + "]"; + } /** * Returns the space in which the light data should be passed to the shader. diff --git a/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java new file mode 100644 index 000000000..86ce66391 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2009-2015 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.logic; + +import com.jme3.asset.AssetManager; +import com.jme3.light.*; +import com.jme3.material.TechniqueDef; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.instancing.InstancedGeometry; +import com.jme3.shader.DefineList; +import com.jme3.shader.Shader; +import java.util.EnumSet; + +public class DefaultTechniqueDefLogic implements TechniqueDefLogic { + + protected final TechniqueDef techniqueDef; + + public DefaultTechniqueDefLogic(TechniqueDef techniqueDef) { + this.techniqueDef = techniqueDef; + } + + @Override + public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager, + EnumSet rendererCaps, LightList lights, DefineList defines) { + return techniqueDef.getShader(assetManager, rendererCaps, defines); + } + + public static void renderMeshFromGeometry(Renderer renderer, Geometry geom) { + Mesh mesh = geom.getMesh(); + int lodLevel = geom.getLodLevel(); + if (geom instanceof InstancedGeometry) { + InstancedGeometry instGeom = (InstancedGeometry) geom; + renderer.renderMesh(mesh, lodLevel, instGeom.getActualNumInstances(), + instGeom.getAllInstanceData()); + } else { + renderer.renderMesh(mesh, lodLevel, 1, null); + } + } + + protected static ColorRGBA getAmbientColor(LightList lightList, boolean removeLights, ColorRGBA ambientLightColor) { + ambientLightColor.set(0, 0, 0, 1); + for (int j = 0; j < lightList.size(); j++) { + Light l = lightList.get(j); + if (l instanceof AmbientLight) { + ambientLightColor.addLocal(l.getColor()); + if (removeLights) { + lightList.remove(l); + } + } + } + ambientLightColor.a = 1.0f; + return ambientLightColor; + } + + + + @Override + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + Renderer renderer = renderManager.getRenderer(); + renderer.setShader(shader); + renderMeshFromGeometry(renderer, geometry); + } +} diff --git a/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java new file mode 100644 index 000000000..67f5b6a67 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2009-2015 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.logic; + +import com.jme3.asset.AssetManager; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.material.RenderState; +import com.jme3.material.TechniqueDef; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.scene.Geometry; +import com.jme3.shader.DefineList; +import com.jme3.shader.Shader; +import com.jme3.shader.Uniform; +import com.jme3.shader.VarType; +import com.jme3.util.TempVars; +import java.util.EnumSet; + +public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic { + + private static final RenderState ADDITIVE_LIGHT = new RenderState(); + private static final Quaternion NULL_DIR_LIGHT = new Quaternion(0, -1, 0, -1); + + private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1); + + static { + ADDITIVE_LIGHT.setBlendMode(RenderState.BlendMode.AlphaAdditive); + ADDITIVE_LIGHT.setDepthWrite(false); + } + + public MultiPassLightingLogic(TechniqueDef techniqueDef) { + super(techniqueDef); + } + + @Override + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + Renderer r = renderManager.getRenderer(); + Uniform lightDir = shader.getUniform("g_LightDirection"); + Uniform lightColor = shader.getUniform("g_LightColor"); + Uniform lightPos = shader.getUniform("g_LightPosition"); + Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); + boolean isFirstLight = true; + boolean isSecondLight = false; + + getAmbientColor(lights, false, ambientLightColor); + + for (int i = 0; i < lights.size(); i++) { + Light l = lights.get(i); + if (l instanceof AmbientLight) { + continue; + } + + if (isFirstLight) { + // set ambient color for first light only + ambientColor.setValue(VarType.Vector4, ambientLightColor); + isFirstLight = false; + isSecondLight = true; + } else if (isSecondLight) { + ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); + // apply additive blending for 2nd and future lights + r.applyRenderState(ADDITIVE_LIGHT); + isSecondLight = false; + } + + TempVars vars = TempVars.get(); + Quaternion tmpLightDirection = vars.quat1; + Quaternion tmpLightPosition = vars.quat2; + ColorRGBA tmpLightColor = vars.color; + Vector4f tmpVec = vars.vect4f1; + + ColorRGBA color = l.getColor(); + tmpLightColor.set(color); + tmpLightColor.a = l.getType().getId(); + lightColor.setValue(VarType.Vector4, tmpLightColor); + + switch (l.getType()) { + case Directional: + DirectionalLight dl = (DirectionalLight) l; + Vector3f dir = dl.getDirection(); + //FIXME : there is an inconstency here due to backward + //compatibility of the lighting shader. + //The directional light direction is passed in the + //LightPosition uniform. The lighting shader needs to be + //reworked though in order to fix this. + tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1); + lightPos.setValue(VarType.Vector4, tmpLightPosition); + tmpLightDirection.set(0, 0, 0, 0); + lightDir.setValue(VarType.Vector4, tmpLightDirection); + break; + case Point: + PointLight pl = (PointLight) l; + Vector3f pos = pl.getPosition(); + float invRadius = pl.getInvRadius(); + + tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius); + lightPos.setValue(VarType.Vector4, tmpLightPosition); + tmpLightDirection.set(0, 0, 0, 0); + lightDir.setValue(VarType.Vector4, tmpLightDirection); + break; + case Spot: + SpotLight sl = (SpotLight) l; + Vector3f pos2 = sl.getPosition(); + Vector3f dir2 = sl.getDirection(); + float invRange = sl.getInvSpotRange(); + float spotAngleCos = sl.getPackedAngleCos(); + + tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange); + lightPos.setValue(VarType.Vector4, tmpLightPosition); + + //We transform the spot direction in view space here to save 5 varying later in the lighting shader + //one vec4 less and a vec4 that becomes a vec3 + //the downside is that spotAngleCos decoding happens now in the frag shader. + tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0); + renderManager.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos); + + lightDir.setValue(VarType.Vector4, tmpLightDirection); + + break; + default: + throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); + } + vars.release(); + r.setShader(shader); + renderMeshFromGeometry(r, geometry); + } + + if (isFirstLight) { + // Either there are no lights at all, or only ambient lights. + // Render a dummy "normal light" so we can see the ambient color. + ambientColor.setValue(VarType.Vector4, getAmbientColor(lights, false, ambientLightColor)); + lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha); + lightPos.setValue(VarType.Vector4, NULL_DIR_LIGHT); + r.setShader(shader); + renderMeshFromGeometry(r, geometry); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java new file mode 100644 index 000000000..d276e82a9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2009-2015 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.logic; + +import com.jme3.asset.AssetManager; +import com.jme3.bounding.BoundingSphere; +import com.jme3.light.*; +import com.jme3.material.*; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.math.*; +import com.jme3.renderer.*; +import com.jme3.scene.Geometry; +import com.jme3.shader.*; +import com.jme3.util.TempVars; + +import java.util.EnumSet; + +public final class SinglePassAndImageBasedLightingLogic extends DefaultTechniqueDefLogic { + + private static final String DEFINE_SINGLE_PASS_LIGHTING = "SINGLE_PASS_LIGHTING"; + private static final String DEFINE_NB_LIGHTS = "NB_LIGHTS"; + private static final RenderState ADDITIVE_LIGHT = new RenderState(); + + private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1); + //Env textures units + int irrUnit = -1; + int pemUnit = -1; + + static { + ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive); + ADDITIVE_LIGHT.setDepthWrite(false); + } + + private final int singlePassLightingDefineId; + private final int nbLightsDefineId; + + public SinglePassAndImageBasedLightingLogic(TechniqueDef techniqueDef) { + super(techniqueDef); + singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean); + nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int); + } + + @Override + public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager, + EnumSet rendererCaps, LightList lights, DefineList defines) { + defines.set(nbLightsDefineId, renderManager.getSinglePassLightBatchSize() * 3); + defines.set(singlePassLightingDefineId, true); + return super.makeCurrent(assetManager, renderManager, rendererCaps, lights, defines); + } + + /** + * Uploads the lights in the light list as two uniform arrays.

* + *

+ * uniform vec4 g_LightColor[numLights];
// + * g_LightColor.rgb is the diffuse/specular color of the light.
// + * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point,
// + * 2 = Spot.

+ * uniform vec4 g_LightPosition[numLights];
// + * g_LightPosition.xyz is the position of the light (for point lights)
+ * // or the direction of the light (for directional lights).
// + * g_LightPosition.w is the inverse radius (1/r) of the light (for + * attenuation)

+ */ + protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex, int lastTexUnit) { + if (numLights == 0) { // this shader does not do lighting, ignore. + return 0; + } + + Uniform lightData = shader.getUniform("g_LightData"); + lightData.setVector4Length(numLights * 3);//8 lights * max 3 + Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); + Uniform lightProbeData = shader.getUniform("g_LightProbeData"); + lightProbeData.setVector4Length(1); + Uniform lightProbeIrrMap = shader.getUniform("g_IrradianceMap"); + Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap"); + + LightProbe lightProbe = null; + if (startIndex != 0) { + // apply additive blending for 2nd and future passes + rm.getRenderer().applyRenderState(ADDITIVE_LIGHT); + ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); + }else{ + lightProbe = extractIndirectLights(lightList,true); + ambientColor.setValue(VarType.Vector4, ambientLightColor); + } + + //If there is a lightProbe in the list we force it's render on the first pass + if(lightProbe != null){ + BoundingSphere s = (BoundingSphere)lightProbe.getBounds(); + lightProbeData.setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f/s.getRadius(), 0); + //assigning new texture indexes if they have never been assigned. + if( irrUnit == -1 ){ + irrUnit = lastTexUnit++; + pemUnit = lastTexUnit++; + } + rm.getRenderer().setTexture(irrUnit, lightProbe.getIrradianceMap()); + lightProbeIrrMap.setValue(VarType.Int, irrUnit); + rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap()); + lightProbePemMap.setValue(VarType.Int, pemUnit); + } else { + //Disable IBL for this pass + lightProbeData.setVector4InArray(0,0,0,-1, 0); + } + + int lightDataIndex = 0; + TempVars vars = TempVars.get(); + Vector4f tmpVec = vars.vect4f1; + int curIndex; + int endIndex = numLights + startIndex; + boolean useIBL = false; + for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) { + + + Light l = lightList.get(curIndex); + if(l.getType() == Light.Type.Ambient){ + endIndex++; + continue; + } + ColorRGBA color = l.getColor(); + //Color + + if(l.getType() != Light.Type.Probe){ + lightData.setVector4InArray(color.getRed(), + color.getGreen(), + color.getBlue(), + l.getType().getId(), + lightDataIndex); + lightDataIndex++; + } + + switch (l.getType()) { + case Directional: + DirectionalLight dl = (DirectionalLight) l; + Vector3f dir = dl.getDirection(); + //Data directly sent in view space to avoid a matrix mult for each pixel + tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f); + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex); + lightDataIndex++; + //PADDING + lightData.setVector4InArray(0,0,0,0, lightDataIndex); + lightDataIndex++; + break; + case Point: + PointLight pl = (PointLight) l; + Vector3f pos = pl.getPosition(); + float invRadius = pl.getInvRadius(); + tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f); + + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex); + lightDataIndex++; + //PADDING + lightData.setVector4InArray(0,0,0,0, lightDataIndex); + lightDataIndex++; + break; + case Spot: + SpotLight sl = (SpotLight) l; + Vector3f pos2 = sl.getPosition(); + Vector3f dir2 = sl.getDirection(); + float invRange = sl.getInvSpotRange(); + float spotAngleCos = sl.getPackedAngleCos(); + tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(), 1.0f); + + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex); + lightDataIndex++; + + tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0.0f); + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex); + lightDataIndex++; + break; + default: + throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); + } + } + vars.release(); + + //Padding of unsued buffer space + while(lightDataIndex < numLights * 3) { + lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex); + lightDataIndex++; + } + return curIndex; + } + + @Override + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + int nbRenderedLights = 0; + Renderer renderer = renderManager.getRenderer(); + int batchSize = renderManager.getSinglePassLightBatchSize(); + if (lights.size() == 0) { + updateLightListUniforms(shader, geometry, lights,batchSize, renderManager, 0, lastTexUnit); + renderer.setShader(shader); + renderMeshFromGeometry(renderer, geometry); + } else { + while (nbRenderedLights < lights.size()) { + nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights, lastTexUnit); + renderer.setShader(shader); + renderMeshFromGeometry(renderer, geometry); + } + } + return; + } + + protected LightProbe extractIndirectLights(LightList lightList, boolean removeLights) { + ambientLightColor.set(0, 0, 0, 1); + LightProbe probe = null; + for (int j = 0; j < lightList.size(); j++) { + Light l = lightList.get(j); + if (l instanceof AmbientLight) { + ambientLightColor.addLocal(l.getColor()); + if(removeLights){ + lightList.remove(l); + j--; + } + } + if (l instanceof LightProbe) { + probe = (LightProbe)l; + if(removeLights){ + lightList.remove(l); + j--; + } + } + } + ambientLightColor.a = 1.0f; + return probe; + } +} diff --git a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java new file mode 100644 index 000000000..a260eaf64 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2009-2015 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.logic; + +import com.jme3.asset.AssetManager; +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.LightList; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.material.RenderState; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.material.TechniqueDef; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.scene.Geometry; +import com.jme3.shader.DefineList; +import com.jme3.shader.Shader; +import com.jme3.shader.Uniform; +import com.jme3.shader.VarType; +import com.jme3.util.TempVars; +import java.util.EnumSet; + +public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic { + + private static final String DEFINE_SINGLE_PASS_LIGHTING = "SINGLE_PASS_LIGHTING"; + private static final String DEFINE_NB_LIGHTS = "NB_LIGHTS"; + private static final RenderState ADDITIVE_LIGHT = new RenderState(); + + private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1); + + static { + ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive); + ADDITIVE_LIGHT.setDepthWrite(false); + } + + private final int singlePassLightingDefineId; + private final int nbLightsDefineId; + + public SinglePassLightingLogic(TechniqueDef techniqueDef) { + super(techniqueDef); + singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean); + nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int); + } + + @Override + public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager, + EnumSet rendererCaps, LightList lights, DefineList defines) { + defines.set(nbLightsDefineId, renderManager.getSinglePassLightBatchSize() * 3); + defines.set(singlePassLightingDefineId, true); + return super.makeCurrent(assetManager, renderManager, rendererCaps, lights, defines); + } + + /** + * Uploads the lights in the light list as two uniform arrays.

* + *

+ * uniform vec4 g_LightColor[numLights];
// + * g_LightColor.rgb is the diffuse/specular color of the light.
// + * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point,
// + * 2 = Spot.

+ * uniform vec4 g_LightPosition[numLights];
// + * g_LightPosition.xyz is the position of the light (for point lights)
+ * // or the direction of the light (for directional lights).
// + * g_LightPosition.w is the inverse radius (1/r) of the light (for + * attenuation)

+ */ + protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex) { + if (numLights == 0) { // this shader does not do lighting, ignore. + return 0; + } + + Uniform lightData = shader.getUniform("g_LightData"); + lightData.setVector4Length(numLights * 3);//8 lights * max 3 + Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); + + + if (startIndex != 0) { + // apply additive blending for 2nd and future passes + rm.getRenderer().applyRenderState(ADDITIVE_LIGHT); + ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); + } else { + ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList, true, ambientLightColor)); + } + + int lightDataIndex = 0; + TempVars vars = TempVars.get(); + Vector4f tmpVec = vars.vect4f1; + int curIndex; + int endIndex = numLights + startIndex; + for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) { + + Light l = lightList.get(curIndex); + if (l.getType() == Light.Type.Ambient) { + endIndex++; + continue; + } + ColorRGBA color = l.getColor(); + //Color + lightData.setVector4InArray(color.getRed(), + color.getGreen(), + color.getBlue(), + l.getType().getId(), + lightDataIndex); + lightDataIndex++; + + switch (l.getType()) { + case Directional: + DirectionalLight dl = (DirectionalLight) l; + Vector3f dir = dl.getDirection(); + //Data directly sent in view space to avoid a matrix mult for each pixel + tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f); + rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); +// tmpVec.divideLocal(tmpVec.w); +// tmpVec.normalizeLocal(); + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex); + lightDataIndex++; + //PADDING + lightData.setVector4InArray(0, 0, 0, 0, lightDataIndex); + lightDataIndex++; + break; + case Point: + PointLight pl = (PointLight) l; + Vector3f pos = pl.getPosition(); + float invRadius = pl.getInvRadius(); + tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f); + rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + //tmpVec.divideLocal(tmpVec.w); + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex); + lightDataIndex++; + //PADDING + lightData.setVector4InArray(0, 0, 0, 0, lightDataIndex); + lightDataIndex++; + break; + case Spot: + SpotLight sl = (SpotLight) l; + Vector3f pos2 = sl.getPosition(); + Vector3f dir2 = sl.getDirection(); + float invRange = sl.getInvSpotRange(); + float spotAngleCos = sl.getPackedAngleCos(); + tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(), 1.0f); + rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + // tmpVec.divideLocal(tmpVec.w); + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex); + lightDataIndex++; + + //We transform the spot direction in view space here to save 5 varying later in the lighting shader + //one vec4 less and a vec4 that becomes a vec3 + //the downside is that spotAngleCos decoding happens now in the frag shader. + tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0.0f); + rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); + tmpVec.normalizeLocal(); + lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex); + lightDataIndex++; + break; + default: + throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); + } + } + vars.release(); + //Padding of unsued buffer space + while(lightDataIndex < numLights * 3) { + lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex); + lightDataIndex++; + } + return curIndex; + } + + @Override + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + int nbRenderedLights = 0; + Renderer renderer = renderManager.getRenderer(); + int batchSize = renderManager.getSinglePassLightBatchSize(); + if (lights.size() == 0) { + updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, 0); + renderer.setShader(shader); + renderMeshFromGeometry(renderer, geometry); + } else { + while (nbRenderedLights < lights.size()) { + nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights); + renderer.setShader(shader); + renderMeshFromGeometry(renderer, geometry); + } + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java new file mode 100644 index 000000000..49a15d3a4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2009-2015 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.logic; + +import com.jme3.asset.AssetManager; +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.Light.Type; +import com.jme3.light.LightList; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.material.TechniqueDef; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.scene.Geometry; +import com.jme3.shader.DefineList; +import com.jme3.shader.Shader; +import com.jme3.shader.Uniform; +import com.jme3.shader.VarType; +import java.util.ArrayList; +import java.util.EnumSet; + +/** + * Rendering logic for static pass. + * + * @author Kirill Vainer + */ +public final class StaticPassLightingLogic extends DefaultTechniqueDefLogic { + + private static final String DEFINE_NUM_DIR_LIGHTS = "NUM_DIR_LIGHTS"; + private static final String DEFINE_NUM_POINT_LIGHTS = "NUM_POINT_LIGHTS"; + private static final String DEFINE_NUM_SPOT_LIGHTS = "NUM_SPOT_LIGHTS"; + + private final int numDirLightsDefineId; + private final int numPointLightsDefineId; + private final int numSpotLightsDefineId; + + private final ArrayList tempDirLights = new ArrayList(); + private final ArrayList tempPointLights = new ArrayList(); + private final ArrayList tempSpotLights = new ArrayList(); + + private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1); + + public StaticPassLightingLogic(TechniqueDef techniqueDef) { + super(techniqueDef); + + numDirLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NUM_DIR_LIGHTS, VarType.Int); + numPointLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NUM_POINT_LIGHTS, VarType.Int); + numSpotLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NUM_SPOT_LIGHTS, VarType.Int); + } + + @Override + public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager, + EnumSet rendererCaps, LightList lights, DefineList defines) { + + // TODO: if it ever changes that render isn't called + // right away with the same geometry after makeCurrent, it would be + // a problem. + // Do a radix sort. + tempDirLights.clear(); + tempPointLights.clear(); + tempSpotLights.clear(); + for (Light light : lights) { + switch (light.getType()) { + case Directional: + tempDirLights.add((DirectionalLight) light); + break; + case Point: + tempPointLights.add((PointLight) light); + break; + case Spot: + tempSpotLights.add((SpotLight) light); + break; + } + } + + defines.set(numDirLightsDefineId, tempDirLights.size()); + defines.set(numPointLightsDefineId, tempPointLights.size()); + defines.set(numSpotLightsDefineId, tempSpotLights.size()); + + return techniqueDef.getShader(assetManager, rendererCaps, defines); + } + + private void updateLightListUniforms(Shader shader, LightList lights) { + Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); + ambientColor.setValue(VarType.Vector4, getAmbientColor(lights, true, ambientLightColor)); + + Uniform lightData = shader.getUniform("g_LightData"); + + int index = 0; + for (DirectionalLight light : tempDirLights) { + ColorRGBA color = light.getColor(); + Vector3f dir = light.getDirection(); + lightData.setVector4InArray(color.r, color.g, color.b, 1f, index++); + lightData.setVector4InArray(dir.x, dir.y, dir.z, 1f, index++); + } + + for (PointLight light : tempPointLights) { + ColorRGBA color = light.getColor(); + Vector3f pos = light.getPosition(); + lightData.setVector4InArray(color.r, color.g, color.b, 1f, index++); + lightData.setVector4InArray(pos.x, pos.y, pos.z, 1f, index++); + } + + for (SpotLight light : tempSpotLights) { + ColorRGBA color = light.getColor(); + Vector3f pos = light.getPosition(); + Vector3f dir = light.getDirection(); + float invRange = light.getInvSpotRange(); + float spotAngleCos = light.getPackedAngleCos(); + lightData.setVector4InArray(color.r, color.g, color.b, 1f, index++); + lightData.setVector4InArray(pos.x, pos.y, pos.z, invRange, index++); + lightData.setVector4InArray(dir.x, dir.y, dir.z, spotAngleCos, index++); + } + } + + @Override + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + Renderer renderer = renderManager.getRenderer(); + updateLightListUniforms(shader, lights); + renderer.setShader(shader); + renderMeshFromGeometry(renderer, geometry); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java new file mode 100644 index 000000000..18a805186 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2009-2015 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.logic; + +import com.jme3.asset.AssetManager; +import com.jme3.light.LightList; +import com.jme3.material.TechniqueDef.LightMode; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Geometry; +import com.jme3.shader.DefineList; +import com.jme3.shader.Shader; +import com.jme3.shader.Uniform; +import com.jme3.shader.UniformBinding; +import com.jme3.texture.Texture; +import java.util.EnumSet; + +/** + * TechniqueDefLogic is used to customize how + * a material should be rendered. + * + * Typically used to implement {@link LightMode lighting modes}. + * Implementations can register + * {@link TechniqueDef#addShaderUnmappedDefine(java.lang.String) unmapped defines} + * in their constructor and then later set them based on the geometry + * or light environment being rendered. + * + * @author Kirill Vainer + */ +public interface TechniqueDefLogic { + + /** + * Determine the shader to use for the given geometry / material combination. + * + * @param assetManager The asset manager to use for loading shader source code, + * shader nodes, and and lookup textures. + * @param renderManager The render manager for which rendering is to be performed. + * @param rendererCaps Renderer capabilities. The returned shader must + * support these capabilities. + * @param lights The lights with which the geometry shall be rendered. This + * list must not include culled lights. + * @param defines The define list used by the technique, any + * {@link TechniqueDef#addShaderUnmappedDefine(java.lang.String) unmapped defines} + * should be set here to change shader behavior. + * + * @return The shader to use for rendering. + */ + public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager, + EnumSet rendererCaps, LightList lights, DefineList defines); + + /** + * Requests that the TechniqueDefLogic renders the given geometry. + * + * Fixed material functionality such as {@link RenderState}, + * {@link MatParam material parameters}, and + * {@link UniformBinding uniform bindings} + * have already been applied by the material, however, + * {@link RenderState}, {@link Uniform uniforms}, {@link Texture textures}, + * can still be overriden. + * + * @param renderManager The render manager to perform the rendering against. + * * @param shader The shader that was selected by this logic in + * {@link #makeCurrent(com.jme3.asset.AssetManager, com.jme3.renderer.RenderManager, java.util.EnumSet, com.jme3.shader.DefineList)}. + * @param geometry The geometry to render + * @param lights Lights which influence the geometry. + */ + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit); +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java index e1361b26c..1a11d7339 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -34,8 +34,12 @@ package com.jme3.renderer; import com.jme3.light.DefaultLightFilter; import com.jme3.light.LightFilter; import com.jme3.light.LightList; -import com.jme3.material.*; -import com.jme3.math.Matrix4f; +import com.jme3.material.Material; +import com.jme3.material.MaterialDef; +import com.jme3.material.RenderState; +import com.jme3.material.Technique; +import com.jme3.material.TechniqueDef; +import com.jme3.math.*; import com.jme3.post.SceneProcessor; import com.jme3.profile.AppProfiler; import com.jme3.profile.AppStep; @@ -45,13 +49,12 @@ import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.*; -import com.jme3.shader.Uniform; +import com.jme3.shader.Shader; import com.jme3.shader.UniformBinding; import com.jme3.shader.UniformBindingManager; import com.jme3.system.NullRenderer; import com.jme3.system.Timer; import com.jme3.util.SafeArrayList; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -480,8 +483,8 @@ public class RenderManager { * Updates the given list of uniforms with {@link UniformBinding uniform bindings} * based on the current world state. */ - public void updateUniformBindings(List params) { - uniformBindingManager.updateUniformBindings(params); + public void updateUniformBindings(Shader shader) { + uniformBindingManager.updateUniformBindings(shader); } /** @@ -530,6 +533,7 @@ public class RenderManager { lightFilter.filterLights(g, filteredLightList); lightList = filteredLightList; } + //if forcedTechnique we try to force it for render, //if it does not exists in the mat def, we check for forcedMaterial and render the geom if not null @@ -612,7 +616,9 @@ public class RenderManager { gm.getMaterial().preload(this); Mesh mesh = gm.getMesh(); - if (mesh != null) { + if (mesh != null + && mesh.getVertexCount() != 0 + && mesh.getTriangleCount() != 0) { for (VertexBuffer vb : mesh.getBufferList().getArray()) { if (vb.getData() != null && vb.getUsage() != VertexBuffer.Usage.CpuOnly) { renderer.updateBufferData(vb); @@ -637,8 +643,10 @@ public class RenderManager { *

* In addition to enqueuing the visible geometries, this method * also scenes which cast or receive shadows, by putting them into the - * RenderQueue's {@link RenderQueue#renderShadowQueue(GeometryList, RenderManager, Camera, boolean) shadow queue}. - * Each Spatial which has its {@link Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) shadow mode} + * RenderQueue's + * {@link RenderQueue#addToShadowQueue(com.jme3.scene.Geometry, com.jme3.renderer.queue.RenderQueue.ShadowMode) + * shadow queue}. Each Spatial which has its + * {@link Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) shadow mode} * set to not off, will be put into the appropriate shadow queue, note that * this process does not check for frustum culling on any * {@link ShadowMode#Cast shadow casters}, as they don't have to be @@ -994,7 +1002,8 @@ public class RenderManager { * (see {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) }) *

  • If any objects remained in the render queue, they are removed * from the queue. This is generally objects added to the - * {@link RenderQueue#renderShadowQueue(GeometryList, RenderManager, Camera, boolean) shadow queue} + * {@link RenderQueue#renderShadowQueue(com.jme3.renderer.queue.RenderQueue.ShadowMode, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera, boolean) + * shadow queue} * which were not rendered because of a missing shadow renderer.
  • * * diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java index 2a7c38bb9..1fbad5c2d 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL3.java @@ -58,6 +58,7 @@ public interface GL3 extends GL2 { public void glBindFragDataLocation(int param1, int param2, String param3); /// GL3+ public void glBindVertexArray(int param1); /// GL3+ public void glDeleteVertexArrays(IntBuffer arrays); /// GL3+ + public void glFramebufferTextureLayer(int param1, int param2, int param3, int param4, int param5); /// GL3+ public void glGenVertexArrays(IntBuffer param1); /// GL3+ public String glGetString(int param1, int param2); /// GL3+ } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java index a0511f6ad..fd18cc7ff 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java @@ -94,4 +94,10 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 { gl4.glPatchParameter(count); checkError(); } + + @Override + public void glFramebufferTextureLayer(int param1, int param2, int param3, int param4, int param5) { + gl3.glFramebufferTextureLayer(param1, param2, param3, param4, param5); + checkError(); + } } 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 1ad42eac9..7ef77a2b6 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 @@ -474,6 +474,17 @@ public final class GLRenderer implements Renderer { { sb.append("\t").append(cap.toString()).append("\n"); } + + sb.append("\nHardware limits: \n"); + for (Limits limit : Limits.values()) { + Integer value = limits.get(limit); + if (value == null) { + value = 0; + } + sb.append("\t").append(limit.name()).append(" = ") + .append(value).append("\n"); + } + logger.log(Level.FINE, sb.toString()); } @@ -964,12 +975,12 @@ public final class GLRenderer implements Renderer { gl.glUniform1i(loc, b.booleanValue() ? GL.GL_TRUE : GL.GL_FALSE); break; case Matrix3: - fb = (FloatBuffer) uniform.getValue(); + fb = uniform.getMultiData(); assert fb.remaining() == 9; gl.glUniformMatrix3(loc, false, fb); break; case Matrix4: - fb = (FloatBuffer) uniform.getValue(); + fb = uniform.getMultiData(); assert fb.remaining() == 16; gl.glUniformMatrix4(loc, false, fb); break; @@ -978,23 +989,23 @@ public final class GLRenderer implements Renderer { gl.glUniform1(loc, ib); break; case FloatArray: - fb = (FloatBuffer) uniform.getValue(); + fb = uniform.getMultiData(); gl.glUniform1(loc, fb); break; case Vector2Array: - fb = (FloatBuffer) uniform.getValue(); + fb = uniform.getMultiData(); gl.glUniform2(loc, fb); break; case Vector3Array: - fb = (FloatBuffer) uniform.getValue(); + fb = uniform.getMultiData(); gl.glUniform3(loc, fb); break; case Vector4Array: - fb = (FloatBuffer) uniform.getValue(); + fb = uniform.getMultiData(); gl.glUniform4(loc, fb); break; case Matrix4Array: - fb = (FloatBuffer) uniform.getValue(); + fb = uniform.getMultiData(); gl.glUniformMatrix4(loc, false, fb); break; case Int: @@ -1442,11 +1453,19 @@ public final class GLRenderer implements Renderer { setupTextureParams(0, tex); } - glfbo.glFramebufferTexture2DEXT(GLFbo.GL_FRAMEBUFFER_EXT, - convertAttachmentSlot(rb.getSlot()), - convertTextureType(tex.getType(), image.getMultiSamples(), rb.getFace()), - image.getId(), - 0); + if (rb.getLayer() < 0){ + glfbo.glFramebufferTexture2DEXT(GLFbo.GL_FRAMEBUFFER_EXT, + convertAttachmentSlot(rb.getSlot()), + convertTextureType(tex.getType(), image.getMultiSamples(), rb.getFace()), + image.getId(), + 0); + } else { + gl3.glFramebufferTextureLayer(GLFbo.GL_FRAMEBUFFER_EXT, + convertAttachmentSlot(rb.getSlot()), + image.getId(), + 0, + rb.getLayer()); + } } public void updateFrameBufferAttachment(FrameBuffer fb, RenderBuffer rb) { @@ -2681,12 +2700,15 @@ public final class GLRenderer implements Renderer { } public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { - if (mesh.getVertexCount() == 0) { + if (mesh.getVertexCount() == 0 || mesh.getTriangleCount() == 0 || count == 0) { return; } - //this is kept for backward compatibility. - if (mesh.getLineWidth() != -1 && context.lineWidth != mesh.getLineWidth()) { + if (count > 1 && !caps.contains(Caps.MeshInstancing)) { + throw new RendererException("Mesh instancing is not supported by the video hardware"); + } + + if (mesh.getLineWidth() != 1f && context.lineWidth != mesh.getLineWidth()) { gl.glLineWidth(mesh.getLineWidth()); context.lineWidth = mesh.getLineWidth(); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java index 5529ac84e..09b43b9e8 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java @@ -99,6 +99,16 @@ public class GeometryList implements Iterable{ return size; } + /** + * Sets the element at the given index. + * + * @param index The index to set + * @param value The value + */ + public void set(int index, Geometry value) { + geometries[index] = value; + } + /** * Returns the element at the given index. * diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java b/jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java index 6ffff5702..4b1305c60 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java @@ -69,11 +69,12 @@ public class OpaqueComparator implements GeometryComparator { return spat.queueDistance; } + @Override public int compare(Geometry o1, Geometry o2) { Material m1 = o1.getMaterial(); Material m2 = o2.getMaterial(); - - int compareResult = m2.getSortId() - m1.getSortId(); + + int compareResult = Integer.compare(m1.getSortId(), m2.getSortId()); if (compareResult == 0){ // use the same shader. // sort front-to-back then. 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 fd6900a97..d31997f5b 100644 --- a/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java @@ -76,6 +76,8 @@ public class AssetLinkNode extends Node { */ @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 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 1338b50ef..1b0d5e051 100644 --- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java @@ -727,6 +727,8 @@ public class BatchNode extends GeometryGroupNode { */ @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); 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 11de0c3c0..44fed8208 100644 --- a/jme3-core/src/main/java/com/jme3/scene/CameraNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/CameraNode.java @@ -100,6 +100,8 @@ public class CameraNode extends Node { */ @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 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 827b1603b..c3f2da539 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Geometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java @@ -44,6 +44,7 @@ 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; @@ -499,6 +500,13 @@ 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, @@ -542,6 +550,10 @@ 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; @@ -552,9 +564,42 @@ public class Geometry extends Spatial { */ @Override public void cloneFields( Cloner cloner, Object original ) { - this.mesh = cloner.clone(mesh); + 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); - this.groupNode = cloner.clone(groupNode); } @Override 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 1a87d11b4..a64250c50 100644 --- a/jme3-core/src/main/java/com/jme3/scene/LightNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/LightNode.java @@ -100,6 +100,8 @@ public class LightNode extends Node { */ @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 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 f9b66bc45..15c6a5e1d 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Mesh.java +++ b/jme3-core/src/main/java/com/jme3/scene/Mesh.java @@ -175,7 +175,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { 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; @@ -307,7 +307,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { @Override public Mesh jmeClone() { try { - return (Mesh)super.clone(); + Mesh clone = (Mesh)super.clone(); + clone.vertexArrayID = -1; + return clone; } catch (CloneNotSupportedException ex) { throw new AssertionError(); } @@ -632,6 +634,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable { */ @Deprecated public void setLineWidth(float lineWidth) { + if (lineWidth < 1f) { + throw new IllegalArgumentException("lineWidth must be greater than or equal to 1.0"); + } this.lineWidth = lineWidth; } 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 59f0beb85..6089eda49 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Node.java +++ b/jme3-core/src/main/java/com/jme3/scene/Node.java @@ -75,7 +75,6 @@ public class Node extends Spatial { * 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. @@ -100,7 +99,6 @@ public class Node extends Spatial { */ 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 @@ -141,10 +139,21 @@ public class Node extends Spatial { } } + @Override + protected void setMatParamOverrideRefresh() { + super.setMatParamOverrideRefresh(); + for (Spatial child : children.getArray()) { + if ((child.refreshFlags & RF_MATPARAM_OVERRIDE) != 0) { + continue; + } + + child.setMatParamOverrideRefresh(); + } + } + @Override protected void updateWorldBound(){ super.updateWorldBound(); - // for a node, the world bound is a combination of all it's children // bounds BoundingVolume resultBound = null; @@ -239,19 +248,19 @@ public class Node extends Spatial { // This branch has no geometric state that requires updates. return; } - if ((refreshFlags & RF_LIGHTLIST) != 0){ updateWorldLightList(); } - if ((refreshFlags & RF_TRANSFORM) != 0){ // combine with parent transforms- same for all spatial // subclasses. updateWorldTransforms(); } + if ((refreshFlags & RF_MATPARAM_OVERRIDE) != 0) { + updateMatParamOverrides(); + } 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 @@ -287,7 +296,6 @@ public class Node extends Spatial { return count; } - /** * getVertexCount returns the number of vertices contained * in all sub-branches of this node that contain geometry. @@ -321,7 +329,6 @@ 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 @@ -345,20 +352,18 @@ 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(); } @@ -433,7 +438,8 @@ public class Node extends Spatial { child.setTransformRefresh(); // lights are also inherited from parent child.setLightListRefresh(); - + child.setMatParamOverrideRefresh(); + invalidateUpdateList(); } return child; @@ -519,7 +525,6 @@ public class Node extends Spatial { } return null; } - /** * determines if the provided Spatial is contained in the children list of * this node. @@ -567,39 +572,32 @@ 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. // 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 ;)) - 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(); @@ -692,12 +690,21 @@ 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){ @@ -713,6 +720,8 @@ public class Node extends Spatial { */ @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 @@ -720,7 +729,6 @@ public class Node extends Spatial { // cloning this list is fine. this.updateList = cloner.clone(updateList); } - @Override public void write(JmeExporter e) throws IOException { super.write(e); @@ -732,7 +740,6 @@ 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, e.getCapsule(this).readSavableArrayList("children", null) ); @@ -742,7 +749,6 @@ public class Node extends Spatial { child.parent = this; } } - super.read(e); } @@ -763,7 +769,6 @@ public class Node extends Spatial { } } } - @Override public void depthFirstTraversal(SceneGraphVisitor visitor) { for (Spatial child : children.getArray()) { @@ -771,7 +776,6 @@ 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 f833b6758..7870d5849 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -38,6 +38,7 @@ import com.jme3.collision.Collidable; import com.jme3.export.*; import com.jme3.light.Light; import com.jme3.light.LightList; +import com.jme3.material.MatParamOverride; import com.jme3.material.Material; import com.jme3.math.*; import com.jme3.renderer.Camera; @@ -48,6 +49,7 @@ 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; @@ -120,9 +122,10 @@ 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_CHILD_LIGHTLIST = 0x08; // some child need geometry update - + 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; /** @@ -134,7 +137,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab */ protected LightList localLights; protected transient LightList worldLights; - /** + + protected ArrayList localOverrides; + protected ArrayList worldOverrides; + + /** * This spatial's name. */ protected String name; @@ -194,13 +201,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab */ protected Spatial(String name) { this.name = name; - localTransform = new Transform(); worldTransform = new Transform(); localLights = new LightList(this); worldLights = new LightList(this); + localOverrides = new ArrayList<>(); + worldOverrides = new ArrayList<>(); refreshFlags |= RF_BOUND; } @@ -221,7 +229,6 @@ 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 * updateLogicalState() to be called even if they contain no controls. @@ -271,35 +278,33 @@ 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) { - //if (p.refreshFlags != 0) { - // any refresh flag is sufficient, - // as each propagates to the root Node - - // 2015/2/8: - // This is not true, because using e.g. getWorldBound() - // or getWorldTransform() activates a "partial refresh" - // which does not update the lights but does clear - // the refresh flags on the ancestors! - - // return; - //} - 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; } } + protected void setMatParamOverrideRefresh() { + refreshFlags |= RF_MATPARAM_OVERRIDE; + Spatial p = parent; + while (p != null) { + if ((p.refreshFlags & RF_MATPARAM_OVERRIDE) != 0) { + return; + } + + p.refreshFlags |= RF_MATPARAM_OVERRIDE; + p = p.parent; + } + } + /** * Indicate that the bounding of this spatial has changed and that * a refresh is required. @@ -317,7 +322,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab p = p.parent; } } - /** * (Internal use only) Forces a refresh of the given types of data. * @@ -423,6 +427,29 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab return worldLights; } + /** + * Get the local material parameter overrides. + * + * @return The list of local material parameter overrides. + */ + public List getLocalMatParamOverrides() { + return localOverrides; + } + + /** + * Get the world material parameter overrides. + * + * Note that this list is only updated on a call to + * {@link #updateGeometricState()}. After update, the world overrides list + * will contain the {@link #getParent() parent's} world overrides combined + * with this spatial's {@link #getLocalMatParamOverrides() local overrides}. + * + * @return The list of world material parameter overrides. + */ + public List getWorldMatParamOverrides() { + return worldOverrides; + } + /** * getWorldRotation retrieves the absolute rotation of the * Spatial. @@ -524,10 +551,8 @@ 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); - if ( getParent() != null ) { Quaternion rot=vars.quat1; rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation()); @@ -554,15 +579,63 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab worldLights.update(localLights, null); refreshFlags &= ~RF_LIGHTLIST; } else { - if ((parent.refreshFlags & RF_LIGHTLIST) == 0) { - worldLights.update(localLights, parent.worldLights); - refreshFlags &= ~RF_LIGHTLIST; - } else { - assert false; - } + assert (parent.refreshFlags & RF_LIGHTLIST) == 0; + worldLights.update(localLights, parent.worldLights); + refreshFlags &= ~RF_LIGHTLIST; + } + } + + protected void updateMatParamOverrides() { + refreshFlags &= ~RF_MATPARAM_OVERRIDE; + + worldOverrides.clear(); + if (parent == null) { + worldOverrides.addAll(localOverrides); + } else { + assert (parent.refreshFlags & RF_MATPARAM_OVERRIDE) == 0; + worldOverrides.addAll(localOverrides); + worldOverrides.addAll(parent.worldOverrides); } } + /** + * Adds a local material parameter override. + * + * @param override The override to add. + * @see MatParamOverride + */ + public void addMatParamOverride(MatParamOverride override) { + if (override == null) { + throw new IllegalArgumentException("override cannot be null"); + } + localOverrides.add(override); + setMatParamOverrideRefresh(); + } + + /** + * Remove a local material parameter override if it exists. + * + * @param override The override to remove. + * @see MatParamOverride + */ + public void removeMatParamOverride(MatParamOverride override) { + if (localOverrides.remove(override)) { + setMatParamOverrideRefresh(); + } + } + + /** + * Remove all local material parameter overrides. + * + * @see #addMatParamOverride(com.jme3.material.MatParamOverride) + */ + public void clearMatParamOverrides() { + if (!localOverrides.isEmpty()) { + setMatParamOverrideRefresh(); + } + localOverrides.clear(); + } + /** * Should only be called from updateGeometricState(). * In most cases should not be subclassed. @@ -695,7 +768,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab 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. @@ -719,7 +791,6 @@ 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. @@ -745,14 +816,12 @@ 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(); } - return result; } @@ -837,7 +906,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab if ((refreshFlags & RF_BOUND) != 0) { updateWorldBound(); } - + if ((refreshFlags & RF_MATPARAM_OVERRIDE) != 0) { + updateMatParamOverrides(); + } + assert refreshFlags == 0; } @@ -1263,12 +1335,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() */ - public Spatial clone(boolean cloneMaterial) { + 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 oldClone(boolean cloneMaterial) { try { Spatial clone = (Spatial) super.clone(); if (worldBound != null) { @@ -1281,6 +1384,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab clone.localLights.setOwner(clone); clone.worldLights.setOwner(clone); + clone.worldOverrides = new ArrayList(); + clone.localOverrides = new ArrayList(); + + for (MatParamOverride override : localOverrides) { + clone.localOverrides.add((MatParamOverride) override.clone()); + } + // No need to force cloned to update. // This node already has the refresh flags // set below so it will have to update anyway. @@ -1302,6 +1412,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab clone.setBoundRefresh(); clone.setTransformRefresh(); clone.setLightListRefresh(); + clone.setMatParamOverrideRefresh(); clone.controls = new SafeArrayList(Control.class); for (int i = 0; i < controls.size(); i++) { @@ -1344,7 +1455,23 @@ 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. @@ -1373,6 +1500,8 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab this.localLights = cloner.clone(localLights); this.worldTransform = cloner.clone(worldTransform); this.localTransform = cloner.clone(localTransform); + this.worldOverrides = cloner.clone(worldOverrides); + this.localOverrides = cloner.clone(localOverrides); this.controls = cloner.clone(controls); // Cloner doesn't handle maps on its own just yet. @@ -1381,13 +1510,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab // 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. - 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)); + 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)); + } } } } @@ -1467,6 +1598,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab capsule.write(shadowMode, "shadow_mode", ShadowMode.Inherit); capsule.write(localTransform, "transform", Transform.IDENTITY); capsule.write(localLights, "lights", null); + capsule.writeSavableArrayList(localOverrides, "overrides", null); // Shallow clone the controls array to convert its type. capsule.writeSavableArrayList(new ArrayList(controls), "controlsList", null); @@ -1490,6 +1622,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab localLights = (LightList) ic.readSavable("lights", null); localLights.setOwner(this); + localOverrides = ic.readSavableArrayList("overrides", null); + if (localOverrides == null) { + localOverrides = new ArrayList<>(); + } + worldOverrides = new ArrayList<>(); + //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. diff --git a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java index c67435b78..5652b9295 100644 --- a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java @@ -522,6 +522,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable { // throw new UnsupportedOperationException("Data has already been sent. Cannot set usage."); this.usage = usage; + this.setUpdateNeeded(); } /** 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 b57345664..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 @@ -349,6 +349,8 @@ public class InstancedGeometry extends Geometry { */ @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); 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 c3cdd21e3..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 @@ -350,6 +350,8 @@ public class InstancedNode extends GeometryGroupNode { */ @Override public void cloneFields( Cloner cloner, Object original ) { + super.cloneFields(cloner, original); + this.control = cloner.clone(control); this.lookUp = cloner.clone(lookUp); diff --git a/jme3-core/src/main/java/com/jme3/shader/DefineList.java b/jme3-core/src/main/java/com/jme3/shader/DefineList.java index dd605fc7e..d9b5782aa 100644 --- a/jme3-core/src/main/java/com/jme3/shader/DefineList.java +++ b/jme3-core/src/main/java/com/jme3/shader/DefineList.java @@ -1,286 +1,179 @@ -/* - * 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.shader; - -import com.jme3.export.*; -import com.jme3.material.MatParam; -import com.jme3.material.TechniqueDef; -import com.jme3.util.ListMap; - -import java.io.IOException; -import java.util.Map; -import java.util.TreeMap; - -public final class DefineList implements Savable, Cloneable { - - private static final String ONE = "1"; - - private TreeMap defines = new TreeMap(); - private String compiled = null; - private int cachedHashCode = 0; - - public void write(JmeExporter ex) throws IOException{ - OutputCapsule oc = ex.getCapsule(this); - - String[] keys = new String[defines.size()]; - String[] vals = new String[defines.size()]; - - int i = 0; - for (Map.Entry define : defines.entrySet()){ - keys[i] = define.getKey(); - vals[i] = define.getValue(); - i++; - } - - oc.write(keys, "keys", null); - oc.write(vals, "vals", null); - } - - public void read(JmeImporter im) throws IOException{ - InputCapsule ic = im.getCapsule(this); - - String[] keys = ic.readStringArray("keys", null); - String[] vals = ic.readStringArray("vals", null); - for (int i = 0; i < keys.length; i++){ - defines.put(keys[i], vals[i]); - } - } - - public void clear() { - defines.clear(); - compiled = ""; - cachedHashCode = 0; - } - - public String get(String key){ - return defines.get(key); - } - - @Override - public DefineList clone() { - try { - DefineList clone = (DefineList) super.clone(); - clone.cachedHashCode = 0; - clone.compiled = null; - clone.defines = (TreeMap) defines.clone(); - return clone; - } catch (CloneNotSupportedException ex) { - throw new AssertionError(); - } - } - - public boolean set(String key, VarType type, Object val){ - if (val == null){ - defines.remove(key); - compiled = null; - cachedHashCode = 0; - return true; - } - - switch (type){ - case Boolean: - if (((Boolean) val).booleanValue()) { - // same literal, != will work - if (defines.put(key, ONE) != ONE) { - compiled = null; - cachedHashCode = 0; - return true; - } - } else if (defines.containsKey(key)) { - defines.remove(key); - compiled = null; - cachedHashCode = 0; - return true; - } - - break; - case Float: - case Int: - String newValue = val.toString(); - String original = defines.put(key, newValue); - if (!val.equals(original)) { - compiled = null; - cachedHashCode = 0; - return true; - } - break; - default: - // same literal, != will work - if (defines.put(key, ONE) != ONE) { - compiled = null; - cachedHashCode = 0; - return true; - } - break; - } - - return false; - } - - public boolean remove(String key){ - if (defines.remove(key) != null) { - compiled = null; - cachedHashCode = 0; - return true; - } - return false; - } - - public void addFrom(DefineList other){ - if (other == null) { - return; - } - compiled = null; - cachedHashCode = 0; - defines.putAll(other.defines); - } - - public String getCompiled(){ - if (compiled == null){ - StringBuilder sb = new StringBuilder(); - for (Map.Entry entry : defines.entrySet()){ - sb.append("#define ").append(entry.getKey()).append(" "); - sb.append(entry.getValue()).append('\n'); - } - compiled = sb.toString(); - } - return compiled; - } - - @Override - public boolean equals(Object obj) { - final DefineList other = (DefineList) obj; - return defines.equals(other.defines); - } - - /** - * Update defines if the define list changed based on material parameters. - * @param params - * @param def - * @return true if defines was updated - */ - public boolean update(ListMap params, TechniqueDef def){ - if(equalsParams(params, def)){ - return false; - } - // Defines were changed, update define list - clear(); - for(int i=0;i entry : defines.entrySet()) { - sb.append(entry.getKey()).append("=").append(entry.getValue()); - if (i != defines.size() - 1) { - sb.append(", "); - } - i++; - } - return sb.toString(); - } - -} +/* + * Copyright (c) 2009-2015 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.shader; + +import java.util.Arrays; +import java.util.List; + +/** + * The new define list. + * + * @author Kirill Vainer + */ +public final class DefineList { + + public static final int MAX_DEFINES = 64; + + private long hash; + private final int[] vals; + + public DefineList(int numValues) { + if (numValues < 0 || numValues > MAX_DEFINES) { + throw new IllegalArgumentException("numValues must be between 0 and 64"); + } + vals = new int[numValues]; + } + + private DefineList(DefineList original) { + this.hash = original.hash; + this.vals = new int[original.vals.length]; + System.arraycopy(original.vals, 0, vals, 0, vals.length); + } + + public void set(int id, int val) { + assert 0 <= id && id < 64; + if (val != 0) { + hash |= (1L << id); + } else { + hash &= ~(1L << id); + } + vals[id] = val; + } + + public void set(int id, float val) { + set(id, Float.floatToIntBits(val)); + } + + public void set(int id, boolean val) { + set(id, val ? 1 : 0); + } + + public void set(int id, VarType type, Object value) { + if (value == null) { + set(id, 0); + return; + } + + switch (type) { + case Int: + set(id, (Integer) value); + break; + case Float: + set(id, (Float) value); + break; + case Boolean: + set(id, ((Boolean) value)); + break; + default: + set(id, 1); + break; + } + } + + public void setAll(DefineList other) { + for (int i = 0; i < other.vals.length; i++) { + if (other.vals[i] != 0) { + vals[i] = other.vals[i]; + } + } + } + + public void clear() { + hash = 0; + Arrays.fill(vals, 0); + } + + public boolean getBoolean(int id) { + return vals[id] != 0; + } + + public float getFloat(int id) { + return Float.intBitsToFloat(vals[id]); + } + + public int getInt(int id) { + return vals[id]; + } + + @Override + public int hashCode() { + return (int)((hash >> 32) ^ hash); + } + + @Override + public boolean equals(Object other) { + DefineList o = (DefineList) other; + if (hash == o.hash) { + for (int i = 0; i < vals.length; i++) { + if (vals[i] != o.vals[i]) return false; + } + return true; + } + return false; + } + + public DefineList deepClone() { + return new DefineList(this); + } + + public void generateSource(StringBuilder sb, List defineNames, List defineTypes) { + for (int i = 0; i < vals.length; i++) { + if (vals[i] != 0) { + String defineName = defineNames.get(i); + + sb.append("#define "); + sb.append(defineName); + sb.append(" "); + + if (defineTypes != null && defineTypes.get(i) == VarType.Float) { + float val = Float.intBitsToFloat(vals[i]); + if (Float.isInfinite(val) || Float.isNaN(val)) { + throw new IllegalArgumentException( + "GLSL does not support NaN " + + "or Infinite float literals"); + } + sb.append(val); + } else { + sb.append(vals[i]); + } + + sb.append("\n"); + } + } + } + + public String generateSource(List defineNames, List defineTypes) { + StringBuilder sb = new StringBuilder(); + generateSource(sb, defineNames, defineTypes); + return sb.toString(); + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java index a7a53b915..ffe96d0f0 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java @@ -258,7 +258,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator { } for (ShaderNodeVariable var : shaderNode.getDefinition().getOutputs()) { - ShaderNodeVariable v = new ShaderNodeVariable(var.getType(), shaderNode.getName(), var.getName()); + ShaderNodeVariable v = new ShaderNodeVariable(var.getType(), shaderNode.getName(), var.getName(), var.getMultiplicity()); if (!declaredInputs.contains(shaderNode.getName() + "_" + var.getName())) { if (!isVarying(info, v)) { declareVariable(source, v); @@ -397,6 +397,11 @@ public class Glsl100ShaderGenerator extends ShaderGenerator { source.append(mapping.getLeftVariable().getNameSpace()); source.append("_"); source.append(mapping.getLeftVariable().getName()); + if (mapping.getLeftVariable().getMultiplicity() != null){ + source.append("["); + source.append(mapping.getLeftVariable().getMultiplicity()); + source.append("]"); + } //left swizzle, the variable can't be declared and assigned on the same line. if (mapping.getLeftSwizzling().length() > 0) { diff --git a/jme3-core/src/main/java/com/jme3/shader/Shader.java b/jme3-core/src/main/java/com/jme3/shader/Shader.java index eb084f178..d6fc495f2 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Shader.java +++ b/jme3-core/src/main/java/com/jme3/shader/Shader.java @@ -45,44 +45,63 @@ public final class Shader extends NativeObject { /** * A list of all shader sources currently attached. */ - private ArrayList shaderSourceList; + private final ArrayList shaderSourceList; /** * Maps uniform name to the uniform variable. */ - private ListMap uniforms; + private final ListMap uniforms; + + /** + * Uniforms bound to {@link UniformBinding}s. + * + * Managed by the {@link UniformBindingManager}. + */ + private final ArrayList boundUniforms; /** * Maps attribute name to the location of the attribute in the shader. */ - private IntMap attribs; + private final IntMap attribs; /** * Type of shader. The shader will control the pipeline of it's type. */ public static enum ShaderType { + /** * Control fragment rasterization. (e.g color of pixel). */ - Fragment, - + Fragment("frag"), /** * Control vertex processing. (e.g transform of model to clip space) */ - Vertex, - + Vertex("vert"), /** - * Control geometry assembly. (e.g compile a triangle list from input data) + * Control geometry assembly. (e.g compile a triangle list from input + * data) */ - Geometry, + Geometry("geom"), /** - * Controls tesselation factor (e.g how often a input patch should be subdivided) + * Controls tesselation factor (e.g how often a input patch should be + * subdivided) */ - TessellationControl, + TessellationControl("tsctrl"), /** - * Controls tesselation transform (e.g similar to the vertex shader, but required to mix inputs manual) + * Controls tesselation transform (e.g similar to the vertex shader, but + * required to mix inputs manual) */ - TessellationEvaluation; + TessellationEvaluation("tseval"); + + private String extension; + + public String getExtension() { + return extension; + } + + private ShaderType(String extension) { + this.extension = extension; + } } /** @@ -195,22 +214,16 @@ public final class Shader extends NativeObject { } } - /** - * Initializes the shader for use, must be called after the - * constructor without arguments is used. - */ - public void initialize() { - shaderSourceList = new ArrayList(); - uniforms = new ListMap(); - attribs = new IntMap(); - } - /** * Creates a new shader, {@link #initialize() } must be called * after this constructor for the shader to be usable. */ public Shader(){ super(); + shaderSourceList = new ArrayList(); + uniforms = new ListMap(); + attribs = new IntMap(); + boundUniforms = new ArrayList(); } /** @@ -225,6 +238,10 @@ public final class Shader extends NativeObject { for (ShaderSource source : s.shaderSourceList){ shaderSourceList.add( (ShaderSource)source.createDestructableClone() ); } + + uniforms = null; + boundUniforms = null; + attribs = null; } /** @@ -248,6 +265,18 @@ public final class Shader extends NativeObject { setUpdateNeeded(); } + public void addUniformBinding(UniformBinding binding){ + String uniformName = "g_" + binding.name(); + Uniform uniform = uniforms.get(uniformName); + if (uniform == null) { + uniform = new Uniform(); + uniform.name = uniformName; + uniform.binding = binding; + uniforms.put(uniformName, uniform); + boundUniforms.add(uniform); + } + } + public Uniform getUniform(String name){ assert name.startsWith("m_") || name.startsWith("g_"); Uniform uniform = uniforms.get(name); @@ -277,6 +306,10 @@ public final class Shader extends NativeObject { public ListMap getUniformMap(){ return uniforms; } + + public ArrayList getBoundUniforms() { + return boundUniforms; + } public Collection getSources(){ return shaderSourceList; diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java index f5aeca2ed..d42dccf17 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java @@ -57,9 +57,9 @@ public abstract class ShaderGenerator { */ protected int indent; /** - * the technique to use for the shader generation + * the technique def to use for the shader generation */ - protected Technique technique = null; + protected TechniqueDef techniqueDef = null; /** * Build a shaderGenerator @@ -70,8 +70,8 @@ public abstract class ShaderGenerator { this.assetManager = assetManager; } - public void initialize(Technique technique){ - this.technique = technique; + public void initialize(TechniqueDef techniqueDef){ + this.techniqueDef = techniqueDef; } /** @@ -79,24 +79,29 @@ public abstract class ShaderGenerator { * * @return a Shader program */ - public Shader generateShader() { - if(technique == null){ - throw new UnsupportedOperationException("The shaderGenerator was not properly initialized, call initialize(Technique) before any generation"); + public Shader generateShader(String definesSourceCode) { + if (techniqueDef == null) { + throw new UnsupportedOperationException("The shaderGenerator was not " + + "properly initialized, call " + + "initialize(TechniqueDef) before any generation"); } - DefineList defines = technique.getAllDefines(); - TechniqueDef def = technique.getDef(); - ShaderGenerationInfo info = def.getShaderGenerationInfo(); - - String vertexSource = buildShader(def.getShaderNodes(), info, ShaderType.Vertex); - String fragmentSource = buildShader(def.getShaderNodes(), info, ShaderType.Fragment); + String techniqueName = techniqueDef.getName(); + ShaderGenerationInfo info = techniqueDef.getShaderGenerationInfo(); Shader shader = new Shader(); - shader.initialize(); - shader.addSource(Shader.ShaderType.Vertex, technique.getDef().getName() + ".vert", vertexSource, defines.getCompiled(), getLanguageAndVersion(ShaderType.Vertex)); - shader.addSource(Shader.ShaderType.Fragment, technique.getDef().getName() + ".frag", fragmentSource, defines.getCompiled(), getLanguageAndVersion(ShaderType.Fragment)); + for (ShaderType type : ShaderType.values()) { + String extension = type.getExtension(); + String language = getLanguageAndVersion(type); + String shaderSourceCode = buildShader(techniqueDef.getShaderNodes(), info, type); + + if (shaderSourceCode != null) { + String shaderSourceAssetName = techniqueName + "." + extension; + shader.addSource(type, shaderSourceAssetName, shaderSourceCode, definesSourceCode, language); + } + } - technique = null; + techniqueDef = null; return shader; } @@ -109,6 +114,14 @@ public abstract class ShaderGenerator { * @return the code of the generated vertex shader */ protected String buildShader(List shaderNodes, ShaderGenerationInfo info, ShaderType type) { + if (type == ShaderType.TessellationControl || + type == ShaderType.TessellationEvaluation || + type == ShaderType.Geometry) { + // TODO: Those are not supported. + // Too much code assumes that type is either Vertex or Fragment + return null; + } + indent = 0; StringBuilder sourceDeclaration = new StringBuilder(); diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderKey.java b/jme3-core/src/main/java/com/jme3/shader/ShaderKey.java deleted file mode 100644 index 23a187250..000000000 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderKey.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * 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.shader; - -import com.jme3.asset.AssetKey; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import java.io.IOException; -import java.util.EnumMap; -import java.util.Set; - -public class ShaderKey extends AssetKey { - - protected EnumMap shaderLanguage; - protected EnumMap shaderName; - protected DefineList defines; - protected int cachedHashedCode = 0; - protected boolean usesShaderNodes = false; - - public ShaderKey(){ - shaderLanguage=new EnumMap(Shader.ShaderType.class); - shaderName=new EnumMap(Shader.ShaderType.class); - } - - public ShaderKey(DefineList defines, EnumMap shaderLanguage,EnumMap shaderName){ - super(""); - this.name = reducePath(getShaderName(Shader.ShaderType.Vertex)); - this.shaderLanguage=new EnumMap(Shader.ShaderType.class); - this.shaderName=new EnumMap(Shader.ShaderType.class); - this.defines = defines; - for (Shader.ShaderType shaderType : shaderName.keySet()) { - this.shaderName.put(shaderType,shaderName.get(shaderType)); - this.shaderLanguage.put(shaderType,shaderLanguage.get(shaderType)); - } - } - - @Override - public ShaderKey clone() { - ShaderKey clone = (ShaderKey) super.clone(); - clone.cachedHashedCode = 0; - clone.defines = defines.clone(); - return clone; - } - - @Override - public String toString(){ - //todo: - return "V="+name+";"; - } - - private final String getShaderName(Shader.ShaderType type) { - if (shaderName == null) { - return ""; - } - String shName = shaderName.get(type); - return shName != null ? shName : ""; - } - - //todo: make equals and hashCode work - @Override - public boolean equals(Object obj) { - final ShaderKey other = (ShaderKey) obj; - if (name.equals(other.name) && getShaderName(Shader.ShaderType.Fragment).equals(other.getShaderName(Shader.ShaderType.Fragment))){ - if (defines != null && other.defines != null) { - return defines.equals(other.defines); - } else if (defines != null || other.defines != null) { - return false; - } else { - return true; - } - } - return false; - } - - @Override - public int hashCode() { - if (cachedHashedCode == 0) { - int hash = 7; - hash = 41 * hash + name.hashCode(); - hash = 41 * hash + getShaderName(Shader.ShaderType.Fragment).hashCode(); - hash = getShaderName(Shader.ShaderType.Geometry) == null ? hash : 41 * hash + getShaderName(Shader.ShaderType.Geometry).hashCode(); - hash = getShaderName(Shader.ShaderType.TessellationControl) == null ? hash : 41 * hash + getShaderName(Shader.ShaderType.TessellationControl).hashCode(); - hash = getShaderName(Shader.ShaderType.TessellationEvaluation) == null ? hash : 41 * hash + getShaderName(Shader.ShaderType.TessellationEvaluation).hashCode(); - hash = 41 * hash + (defines != null ? defines.hashCode() : 0); - cachedHashedCode = hash; - } - return cachedHashedCode; - } - - public DefineList getDefines() { - return defines; - } - - public String getVertName(){ - return getShaderName(Shader.ShaderType.Vertex); - } - - public String getFragName() { - return getShaderName(Shader.ShaderType.Fragment); - } - - /** - * @deprecated Use {@link #getVertexShaderLanguage() } instead. - */ - @Deprecated - public String getLanguage() { - return shaderLanguage.get(Shader.ShaderType.Vertex); - } - - public String getVertexShaderLanguage() { - return shaderLanguage.get(Shader.ShaderType.Vertex); - } - - public String getFragmentShaderLanguage() { - return shaderLanguage.get(Shader.ShaderType.Vertex); - } - - public boolean isUsesShaderNodes() { - return usesShaderNodes; - } - - public void setUsesShaderNodes(boolean usesShaderNodes) { - this.usesShaderNodes = usesShaderNodes; - } - - public Set getUsedShaderPrograms(){ - return shaderName.keySet(); - } - - public String getShaderProgramLanguage(Shader.ShaderType shaderType){ - return shaderLanguage.get(shaderType); - } - - public String getShaderProgramName(Shader.ShaderType shaderType){ - return getShaderName(shaderType); - } - - @Override - public void write(JmeExporter ex) throws IOException{ - super.write(ex); - OutputCapsule oc = ex.getCapsule(this); - oc.write(shaderName.get(Shader.ShaderType.Fragment), "fragment_name", null); - oc.write(shaderName.get(Shader.ShaderType.Geometry), "geometry_name", null); - oc.write(shaderName.get(Shader.ShaderType.TessellationControl), "tessControl_name", null); - oc.write(shaderName.get(Shader.ShaderType.TessellationEvaluation), "tessEval_name", null); - oc.write(shaderLanguage.get(Shader.ShaderType.Vertex), "language", null); - oc.write(shaderLanguage.get(Shader.ShaderType.Fragment), "frag_language", null); - oc.write(shaderLanguage.get(Shader.ShaderType.Geometry), "geom_language", null); - oc.write(shaderLanguage.get(Shader.ShaderType.TessellationControl), "tsctrl_language", null); - oc.write(shaderLanguage.get(Shader.ShaderType.TessellationEvaluation), "tseval_language", null); - - } - - @Override - public void read(JmeImporter im) throws IOException{ - super.read(im); - InputCapsule ic = im.getCapsule(this); - shaderName.put(Shader.ShaderType.Vertex,name); - shaderName.put(Shader.ShaderType.Fragment,ic.readString("fragment_name", null)); - shaderName.put(Shader.ShaderType.Geometry,ic.readString("geometry_name", null)); - shaderName.put(Shader.ShaderType.TessellationControl,ic.readString("tessControl_name", null)); - shaderName.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tessEval_name", null)); - shaderLanguage.put(Shader.ShaderType.Vertex,ic.readString("language", null)); - shaderLanguage.put(Shader.ShaderType.Fragment,ic.readString("frag_language", null)); - shaderLanguage.put(Shader.ShaderType.Geometry,ic.readString("geom_language", null)); - shaderLanguage.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrl_language", null)); - shaderLanguage.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tseval_language", null)); - } - -} diff --git a/jme3-core/src/main/java/com/jme3/shader/Uniform.java b/jme3-core/src/main/java/com/jme3/shader/Uniform.java index 5d5fd2fe3..7ed468528 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Uniform.java +++ b/jme3-core/src/main/java/com/jme3/shader/Uniform.java @@ -70,6 +70,30 @@ public class Uniform extends ShaderVariable { */ protected boolean setByCurrentMaterial = false; + @Override + public int hashCode() { + int hash = 5; + hash = 31 * hash + (this.value != null ? this.value.hashCode() : 0); + hash = 31 * hash + (this.varType != null ? this.varType.hashCode() : 0); + hash = 31 * hash + (this.binding != null ? this.binding.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + final Uniform other = (Uniform) obj; + if (this.value != other.value && (this.value == null || !this.value.equals(other.value))) { + return false; + } + return this.binding == other.binding && this.varType == other.varType; + } + @Override public String toString(){ StringBuilder sb = new StringBuilder(); @@ -102,6 +126,10 @@ public class Uniform extends ShaderVariable { public Object getValue(){ return value; } + + public FloatBuffer getMultiData() { + return multiData; + } public boolean isSetByCurrentMaterial() { return setByCurrentMaterial; @@ -111,21 +139,6 @@ public class Uniform extends ShaderVariable { setByCurrentMaterial = false; } - private static void setVector4(Vector4f vec, Object value) { - if (value instanceof ColorRGBA) { - ColorRGBA color = (ColorRGBA) value; - vec.set(color.r, color.g, color.b, color.a); - } else if (value instanceof Quaternion) { - Quaternion quat = (Quaternion) value; - vec.set(quat.getX(), quat.getY(), quat.getZ(), quat.getW()); - } else if (value instanceof Vector4f) { - Vector4f vec4 = (Vector4f) value; - vec.set(vec4); - } else{ - throw new IllegalArgumentException(); - } - } - public void clearValue(){ updateNeeded = true; @@ -182,27 +195,43 @@ public class Uniform extends ShaderVariable { } if (value == null) { - throw new NullPointerException(); + throw new IllegalArgumentException("for uniform " + name + ": value cannot be null"); } setByCurrentMaterial = true; switch (type){ case Matrix3: + if (value.equals(this.value)) { + return; + } Matrix3f m3 = (Matrix3f) value; if (multiData == null) { multiData = BufferUtils.createFloatBuffer(9); } m3.fillFloatBuffer(multiData, true); multiData.clear(); + if (this.value == null) { + this.value = new Matrix3f(m3); + } else { + ((Matrix3f)this.value).set(m3); + } break; case Matrix4: + if (value.equals(this.value)) { + return; + } Matrix4f m4 = (Matrix4f) value; if (multiData == null) { multiData = BufferUtils.createFloatBuffer(16); } m4.fillFloatBuffer(multiData, true); multiData.clear(); + if (this.value == null) { + this.value = new Matrix4f(m4); + } else { + ((Matrix4f)this.value).copy(m4); + } break; case IntArray: int[] ia = (int[]) value; @@ -283,11 +312,32 @@ public class Uniform extends ShaderVariable { } multiData.clear(); break; + case Vector4: + if (value.equals(this.value)) { + return; + } + if (value instanceof ColorRGBA) { + if (this.value == null) { + this.value = new ColorRGBA(); + } + ((ColorRGBA) this.value).set((ColorRGBA) value); + } else if (value instanceof Vector4f) { + if (this.value == null) { + this.value = new Vector4f(); + } + ((Vector4f) this.value).set((Vector4f) value); + } else { + if (this.value == null) { + this.value = new Quaternion(); + } + ((Quaternion) this.value).set((Quaternion) value); + } + break; // Only use check if equals optimization for primitive values case Int: case Float: case Boolean: - if (this.value != null && this.value.equals(value)) { + if (value.equals(this.value)) { return; } this.value = value; @@ -297,39 +347,38 @@ public class Uniform extends ShaderVariable { break; } - if (multiData != null) { - this.value = multiData; - } +// if (multiData != null) { +// this.value = multiData; +// } varType = type; updateNeeded = true; } public void setVector4Length(int length){ - if (location == -1) + if (location == -1) { return; - - FloatBuffer fb = (FloatBuffer) value; - if (fb == null || fb.capacity() < length * 4) { - value = BufferUtils.createFloatBuffer(length * 4); } - + + multiData = BufferUtils.ensureLargeEnough(multiData, length * 4); + value = multiData; varType = VarType.Vector4Array; updateNeeded = true; setByCurrentMaterial = true; } public void setVector4InArray(float x, float y, float z, float w, int index){ - if (location == -1) + if (location == -1) { return; + } - if (varType != null && varType != VarType.Vector4Array) - throw new IllegalArgumentException("Expected a "+varType.name()+" value!"); + if (varType != null && varType != VarType.Vector4Array) { + throw new IllegalArgumentException("Expected a " + varType.name() + " value!"); + } - FloatBuffer fb = (FloatBuffer) value; - fb.position(index * 4); - fb.put(x).put(y).put(z).put(w); - fb.rewind(); + multiData.position(index * 4); + multiData.put(x).put(y).put(z).put(w); + multiData.rewind(); updateNeeded = true; setByCurrentMaterial = true; } diff --git a/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java b/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java index af2df5a94..f9ead8979 100644 --- a/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java +++ b/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java @@ -36,7 +36,7 @@ import com.jme3.math.*; import com.jme3.renderer.Camera; import com.jme3.renderer.RenderManager; import com.jme3.system.Timer; -import java.util.List; +import java.util.ArrayList; /** * UniformBindingManager helps {@link RenderManager} to manage @@ -84,7 +84,8 @@ public class UniformBindingManager { * Updates the given list of uniforms with {@link UniformBinding uniform bindings} * based on the current world state. */ - public void updateUniformBindings(List params) { + public void updateUniformBindings(Shader shader) { + ArrayList params = shader.getBoundUniforms(); for (int i = 0; i < params.size(); i++) { Uniform u = params.get(i); switch (u.getBinding()) { diff --git a/jme3-core/src/main/java/com/jme3/shader/VarType.java b/jme3-core/src/main/java/com/jme3/shader/VarType.java index b494daf57..7300294d4 100644 --- a/jme3-core/src/main/java/com/jme3/shader/VarType.java +++ b/jme3-core/src/main/java/com/jme3/shader/VarType.java @@ -55,7 +55,7 @@ public enum VarType { TextureBuffer(false,true,"sampler1D|sampler1DShadow"), Texture2D(false,true,"sampler2D|sampler2DShadow"), Texture3D(false,true,"sampler3D"), - TextureArray(false,true,"sampler2DArray"), + TextureArray(false,true,"sampler2DArray|sampler2DArrayShadow"), TextureCubeMap(false,true,"samplerCube"), Int("int"); diff --git a/jme3-core/src/main/java/com/jme3/system/NullContext.java b/jme3-core/src/main/java/com/jme3/system/NullContext.java index 41204e6c9..54ee59c42 100644 --- a/jme3-core/src/main/java/com/jme3/system/NullContext.java +++ b/jme3-core/src/main/java/com/jme3/system/NullContext.java @@ -128,12 +128,13 @@ public class NullContext implements JmeContext, Runnable { public void run(){ initInThread(); - while (!needClose.get()){ + do { listener.update(); - if (frameRate > 0) + if (frameRate > 0) { sync(frameRate); - } + } + } while (!needClose.get()); deinitInThread(); diff --git a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java index f2e029af4..ae00386ee 100644 --- a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java +++ b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java @@ -51,7 +51,7 @@ import com.jme3.texture.Texture; public class NullRenderer implements Renderer { - private static final EnumSet caps = EnumSet.noneOf(Caps.class); + private static final EnumSet caps = EnumSet.allOf(Caps.class); private static final Statistics stats = new Statistics(); public void initialize() { diff --git a/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java index bc21fd82b..a3f825d26 100644 --- a/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java +++ b/jme3-core/src/main/java/com/jme3/texture/FrameBuffer.java @@ -97,6 +97,7 @@ public class FrameBuffer extends NativeObject { int id = -1; int slot = SLOT_UNDEF; int face = -1; + int layer = -1; /** * @return The image format of the render buffer. @@ -160,6 +161,10 @@ public class FrameBuffer extends NativeObject { return "BufferTarget[format=" + format + "]"; } } + + public int getLayer() { + return this.layer; + } } /** @@ -324,6 +329,19 @@ public class FrameBuffer extends NativeObject { addColorTexture(tex); } + /** + * Set the color texture array to use for this framebuffer. + * This automatically clears all existing textures added previously + * with {@link FrameBuffer#addColorTexture } and adds this texture as the + * only target. + * + * @param tex The color texture array to set. + */ + public void setColorTexture(TextureArray tex, int layer){ + clearColorTargets(); + addColorTexture(tex, layer); + } + /** * Set the color texture to use for this framebuffer. * This automatically clears all existing textures added previously @@ -369,6 +387,31 @@ public class FrameBuffer extends NativeObject { colorBufs.add(colorBuf); } + /** + * Add a color texture array to use for this framebuffer. + * If MRT is enabled, then each subsequently added texture can be + * rendered to through a shader that writes to the array gl_FragData. + * If MRT is not enabled, then the index set with {@link FrameBuffer#setTargetIndex(int) } + * is rendered to by the shader. + * + * @param tex The texture array to add. + */ + public void addColorTexture(TextureArray tex, int layer) { + if (id != -1) + throw new UnsupportedOperationException("FrameBuffer already initialized."); + + Image img = tex.getImage(); + checkSetTexture(tex, false); + + RenderBuffer colorBuf = new RenderBuffer(); + colorBuf.slot = colorBufs.size(); + colorBuf.tex = tex; + colorBuf.format = img.getFormat(); + colorBuf.layer = layer; + + colorBufs.add(colorBuf); + } + /** * Add a color texture to use for this framebuffer. * If MRT is enabled, then each subsequently added texture can be @@ -412,7 +455,20 @@ public class FrameBuffer extends NativeObject { depthBuf.tex = tex; depthBuf.format = img.getFormat(); } + public void setDepthTexture(TextureArray tex, int layer){ + if (id != -1) + throw new UnsupportedOperationException("FrameBuffer already initialized."); + Image img = tex.getImage(); + checkSetTexture(tex, true); + + depthBuf = new RenderBuffer(); + depthBuf.slot = img.getFormat().isDepthStencilFormat() ? SLOT_DEPTH_STENCIL : SLOT_DEPTH; + depthBuf.tex = tex; + depthBuf.format = img.getFormat(); + depthBuf.layer = layer; + } + /** * @return The number of color buffers attached to this texture. */ 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/Cloner.java b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java index cc046b28a..3cc2b5576 100644 --- a/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java +++ b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java @@ -37,6 +37,8 @@ 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; @@ -49,7 +51,7 @@ import java.util.concurrent.ConcurrentHashMap; *

    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 + * AND has no special handling defined then an IllegalArgumentException * will be thrown.

    * *

    Enhanced object cloning is done in a two step process. First, @@ -60,7 +62,7 @@ import java.util.concurrent.ConcurrentHashMap; * 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.

    + * 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 @@ -87,31 +89,33 @@ import java.util.concurrent.ConcurrentHashMap; * 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. @@ -121,41 +125,41 @@ public class Cloner { 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.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 + *
    • 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. + *
    • 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. @@ -167,20 +171,20 @@ public class Cloner { // 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 + *
    • 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 + *
    • 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. + *
    • Else an IllegalArgumentException is thrown. *
    * *

    The abililty to selectively use clone functions is useful when @@ -188,72 +192,97 @@ public class Cloner { * * 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; } - Class type = objectClass(object); - + + 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 ) { - return type.cast(clone); + if( clone != null || index.containsKey(object) ) { + if( log.isLoggable(Level.FINER) ) { + log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object) + + " as cached:" + (clone == null ? "null" : (clone.getClass() + "@" + System.identityHashCode(clone)))); + } + return type.cast(clone); } - + // See if there is a custom function... that trumps everything. - CloneFunction f = getCloneFunction(type); + 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); - + // are resolvable. + index.put(object, result); + // Now call the function again to deep clone the fields f.cloneFields(this, result, object); - - return result; + + 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 + // 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); - + 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); + 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 the exact Java type. Note: no inheritence - * checks are performed so a function must be registered for each specific type - * that it handles. By default ListCloneFunction is registered for - * ArrayList, LinkedList, CopyOnWriteArrayList, Vector, Stack, and JME's SafeArrayList. + * 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 ) { @@ -262,24 +291,59 @@ public class Cloner { 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 ) { - return (CloneFunction)functions.get(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 @@ -287,51 +351,54 @@ public class Cloner { * 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.

    - */ + * clone() and objects without necessarily knowing their real type.

    + */ public T javaClone( T object ) throws CloneNotSupportedException { + if( object == null ) { + return null; + } Method m = methodCache.get(object.getClass()); if( m == null ) { try { // Lookup the method and cache it m = object.getClass().getMethod("clone"); - } catch( NoSuchMethodException e ) { + } 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); + 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 type = objectClass(object); Class elementType = type.getComponentType(); - int size = Array.getLength(object); + int size = Array.getLength(object); Object clone = Array.newInstance(elementType, size); - + // Store the clone for later lookups - index.put(object, clone); - + index.put(object, clone); + if( elementType.isPrimitive() ) { // Then our job is a bit easier System.arraycopy(object, 0, clone, 0, size); @@ -341,8 +408,8 @@ public class Cloner { Object element = clone(Array.get(object, i)); Array.set(clone, i, element); } - } - + } + return type.cast(clone); } } 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 4c27dd90e..ad51979aa 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md @@ -114,10 +114,10 @@ MaterialDef Phong Lighting { //For instancing Boolean UseInstancing - Boolean BackfaceShadows: false + Boolean BackfaceShadows : false } - Technique { + Technique { LightMode SinglePass LightSpace View @@ -150,7 +150,7 @@ MaterialDef Phong Lighting { SEPARATE_TEXCOORD : SeparateTexCoord DISCARD_ALPHA : AlphaDiscardThreshold USE_REFLECTION : EnvMap - SPHERE_MAP : SphereMap + SPHERE_MAP : EnvMapAsSphereMap NUM_BONES : NumberOfBones INSTANCING : UseInstancing } @@ -189,7 +189,7 @@ MaterialDef Phong Lighting { SEPARATE_TEXCOORD : SeparateTexCoord DISCARD_ALPHA : AlphaDiscardThreshold USE_REFLECTION : EnvMap - SPHERE_MAP : SphereMap + SPHERE_MAP : EnvMapAsSphereMap NUM_BONES : NumberOfBones INSTANCING : UseInstancing } @@ -210,7 +210,6 @@ MaterialDef Phong Lighting { } Defines { - COLOR_MAP : ColorMap DISCARD_ALPHA : AlphaDiscardThreshold NUM_BONES : NumberOfBones INSTANCING : UseInstancing @@ -235,8 +234,7 @@ MaterialDef Phong Lighting { HARDWARE_SHADOWS : HardwareShadows FILTER_MODE : FilterMode PCFEDGE : PCFEdge - DISCARD_ALPHA : AlphaDiscardThreshold - COLOR_MAP : ColorMap + DISCARD_ALPHA : AlphaDiscardThreshold SHADOWMAP_SIZE : ShadowMapSize FADE : FadeInfo PSSM : Splits @@ -269,8 +267,7 @@ MaterialDef Phong Lighting { HARDWARE_SHADOWS : HardwareShadows FILTER_MODE : FilterMode PCFEDGE : PCFEdge - DISCARD_ALPHA : AlphaDiscardThreshold - COLOR_MAP : ColorMap + DISCARD_ALPHA : AlphaDiscardThreshold SHADOWMAP_SIZE : ShadowMapSize FADE : FadeInfo PSSM : Splits @@ -344,10 +341,6 @@ MaterialDef Phong Lighting { Defines { VERTEX_COLOR : UseVertexColor MATERIAL_COLORS : UseMaterialColors - V_TANGENT : VTangent - MINNAERT : Minnaert - WARDISO : WardIso - DIFFUSEMAP : DiffuseMap NORMALMAP : NormalMap SPECULARMAP : SpecularMap diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md index eb16cc3e5..6759f266e 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md @@ -107,7 +107,7 @@ MaterialDef PBR Lighting { } Technique { - LightMode SinglePass + LightMode SinglePassAndImageBased VertexShader GLSL100: Common/MatDefs/Light/PBRLighting.vert FragmentShader GLSL100: Common/MatDefs/Light/PBRLighting.frag 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 ae20006c0..000000000 --- a/jme3-core/src/main/resources/com/jme3/system/version.properties +++ /dev/null @@ -1,12 +0,0 @@ -# THIS IS AN AUTO-GENERATED FILE.. -# DO NOT MODIFY! -build.date=2016-03-25 -git.revision=0 -git.branch=unknown -git.hash= -git.hash.short= -git.tag= -name.full=jMonkeyEngine 3.1.0-UNKNOWN -version.full=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 70f918a6b..a0f0d2e56 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 @@ -31,6 +31,7 @@ */ package com.jme3.material.plugins; +import com.jme3.material.logic.*; import com.jme3.asset.*; import com.jme3.material.*; import com.jme3.material.RenderState.BlendMode; @@ -40,6 +41,7 @@ import com.jme3.material.TechniqueDef.ShadowMode; import com.jme3.math.ColorRGBA; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; +import com.jme3.shader.DefineList; import com.jme3.shader.Shader; import com.jme3.shader.VarType; import com.jme3.texture.Texture; @@ -73,6 +75,7 @@ public class J3MLoader implements AssetLoader { private Material material; private TechniqueDef technique; private RenderState renderState; + private ArrayList presetDefines = new ArrayList(); private EnumMap shaderLanguage; private EnumMap shaderName; @@ -114,7 +117,11 @@ public class J3MLoader implements AssetLoader { if (split.length != 2){ throw new IOException("LightMode statement syntax incorrect"); } - LightMode lm = LightMode.valueOf(split[1]); + + LightMode lm = LightMode.valueOf(split[1]); + if (lm == LightMode.FixedPipeline) { + throw new UnsupportedOperationException("OpenGL1 is not supported"); + } technique.setLightMode(lm); } @@ -506,10 +513,22 @@ public class J3MLoader implements AssetLoader { private void readDefine(String statement) throws IOException{ String[] split = statement.split(":"); if (split.length == 1){ - // add preset define - technique.addShaderPresetDefine(split[0].trim(), VarType.Boolean, true); + String defineName = split[0].trim(); + presetDefines.add(defineName); }else if (split.length == 2){ - technique.addShaderParamDefine(split[1].trim(), split[0].trim()); + String defineName = split[0].trim(); + String paramName = split[1].trim(); + MatParam param = materialDef.getMaterialParam(paramName); + if (param == null) { + logger.log(Level.WARNING, "In technique ''{0}'':\n" + + "Define ''{1}'' mapped to non-existent" + + " material parameter ''{2}'', ignoring.", + new Object[]{technique.getName(), defineName, paramName}); + return; + } + + VarType paramType = param.getVarType(); + technique.addShaderParamDefine(paramName, paramType, defineName); }else{ throw new IOException("Define syntax incorrect"); } @@ -573,15 +592,28 @@ public class J3MLoader implements AssetLoader { } material.setTransparent(parseBoolean(split[1])); } + + private static String createShaderPrologue(List presetDefines) { + DefineList dl = new DefineList(presetDefines.size()); + for (int i = 0; i < presetDefines.size(); i++) { + dl.set(i, 1); + } + StringBuilder sb = new StringBuilder(); + dl.generateSource(sb, presetDefines, null); + return sb.toString(); + } private void readTechnique(Statement techStat) throws IOException{ isUseNodes = false; String[] split = techStat.getLine().split(whitespacePattern); + if (split.length == 1) { - technique = new TechniqueDef(null); + String techniqueUniqueName = materialDef.getAssetName() + "@Default"; + technique = new TechniqueDef(null, techniqueUniqueName.hashCode()); } else if (split.length == 2) { String techName = split[1]; - technique = new TechniqueDef(techName); + String techniqueUniqueName = materialDef.getAssetName() + "@" + techName; + technique = new TechniqueDef(techName, techniqueUniqueName.hashCode()); } else { throw new IOException("Technique statement syntax incorrect"); } @@ -592,18 +624,43 @@ public class J3MLoader implements AssetLoader { if(isUseNodes){ nodesLoaderDelegate.computeConditions(); + //used for caching later, the shader here is not a file. + + // KIRILL 9/19/2015 + // Not sure if this is needed anymore, since shader caching + // is now done by TechniqueDef. technique.setShaderFile(technique.hashCode() + "", technique.hashCode() + "", "GLSL100", "GLSL100"); } if (shaderName.containsKey(Shader.ShaderType.Vertex) && shaderName.containsKey(Shader.ShaderType.Fragment)) { technique.setShaderFile(shaderName, shaderLanguage); } + + technique.setShaderPrologue(createShaderPrologue(presetDefines)); + + switch (technique.getLightMode()) { + case Disable: + technique.setLogic(new DefaultTechniqueDefLogic(technique)); + break; + case MultiPass: + technique.setLogic(new MultiPassLightingLogic(technique)); + break; + case SinglePass: + technique.setLogic(new SinglePassLightingLogic(technique)); + break; + case SinglePassAndImageBased: + technique.setLogic(new SinglePassAndImageBasedLightingLogic(technique)); + break; + default: + throw new UnsupportedOperationException(); + } materialDef.addTechniqueDef(technique); technique = null; shaderLanguage.clear(); shaderName.clear(); + presetDefines.clear(); } private void loadFromRoot(List roots) throws IOException{ diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java index 4437293f3..efdf28f4b 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java @@ -44,6 +44,7 @@ import com.jme3.shader.ShaderNodeDefinition; import com.jme3.shader.ShaderNodeVariable; import com.jme3.shader.ShaderUtils; import com.jme3.shader.UniformBinding; +import com.jme3.shader.VarType; import com.jme3.shader.VariableMapping; import com.jme3.util.blockparser.Statement; import java.io.IOException; @@ -583,7 +584,7 @@ public class ShaderNodeLoaderDelegate { //multiplicity is not an int attempting to find for a material parameter. MatParam mp = findMatParam(multiplicity); if (mp != null) { - addDefine(multiplicity); + addDefine(multiplicity, VarType.Int); multiplicity = multiplicity.toUpperCase(); } else { throw new MatParseException("Wrong multiplicity for variable" + mapping.getLeftVariable().getName() + ". " + multiplicity + " should be an int or a declared material parameter.", statement); @@ -625,9 +626,9 @@ public class ShaderNodeLoaderDelegate { * * @param paramName */ - public void addDefine(String paramName) { + public void addDefine(String paramName, VarType paramType) { if (techniqueDef.getShaderParamDefine(paramName) == null) { - techniqueDef.addShaderParamDefine(paramName, paramName.toUpperCase()); + techniqueDef.addShaderParamDefine(paramName, paramType, paramName.toUpperCase()); } } @@ -660,7 +661,7 @@ public class ShaderNodeLoaderDelegate { for (String string : defines) { MatParam param = findMatParam(string); if (param != null) { - addDefine(param.getName()); + addDefine(param.getName(), param.getVarType()); } else { throw new MatParseException("Invalid condition, condition must match a Material Parameter named " + cond, statement); } @@ -752,6 +753,7 @@ public class ShaderNodeLoaderDelegate { } right.setNameSpace(node.getName()); right.setType(var.getType()); + right.setMultiplicity(var.getMultiplicity()); mapping.setRightVariable(right); storeVaryings(node, mapping.getRightVariable()); diff --git a/jme3-core/src/test/java/com/jme3/asset/LoadShaderSourceTest.java b/jme3-core/src/test/java/com/jme3/asset/LoadShaderSourceTest.java new file mode 100644 index 000000000..4e9999b20 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/asset/LoadShaderSourceTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2015 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.asset; + +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.shader.plugins.GLSLLoader; +import com.jme3.system.JmeSystem; +import com.jme3.system.MockJmeSystemDelegate; +import org.junit.Test; + +public class LoadShaderSourceTest { + + @Test + public void testLoadShaderSource() { + JmeSystem.setSystemDelegate(new MockJmeSystemDelegate()); + AssetManager assetManager = new DesktopAssetManager(); + assetManager.registerLocator(null, ClasspathLocator.class); + assetManager.registerLoader(GLSLLoader.class, "frag"); + String showNormals = (String) assetManager.loadAsset("Common/MatDefs/Misc/ShowNormals.frag"); + System.out.println(showNormals); + } + +} diff --git a/jme3-core/src/test/java/com/jme3/material/MaterialMatParamOverrideTest.java b/jme3-core/src/test/java/com/jme3/material/MaterialMatParamOverrideTest.java new file mode 100644 index 000000000..846059e6e --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/material/MaterialMatParamOverrideTest.java @@ -0,0 +1,538 @@ +/* + * 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; + +import com.jme3.asset.AssetManager; +import com.jme3.light.LightList; +import com.jme3.math.Matrix4f; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.shader.Shader; +import com.jme3.shader.Uniform; +import com.jme3.shader.VarType; +import java.util.Arrays; +import java.util.HashSet; +import org.junit.Assert; +import org.junit.Test; + +import static com.jme3.scene.MPOTestUtils.*; +import com.jme3.shader.DefineList; +import com.jme3.system.NullRenderer; +import com.jme3.system.TestUtil; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import java.util.HashMap; +import java.util.Map; + +/** + * Validates how {@link MatParamOverride MPOs} work on the material level. + * + * @author Kirill Vainer + */ +public class MaterialMatParamOverrideTest { + + private static final HashSet IGNORED_UNIFORMS = new HashSet( + Arrays.asList(new String[]{"m_ParallaxHeight", "m_Shininess", "m_BackfaceShadows"})); + + @Test + public void testBoolMpoOnly() { + material("Common/MatDefs/Light/Lighting.j3md"); + inputMpo(mpoBool("UseMaterialColors", true)); + outDefines(def("MATERIAL_COLORS", VarType.Boolean, true)); + outUniforms(uniform("UseMaterialColors", VarType.Boolean, true)); + } + + @Test + public void testBoolMpOnly() { + material("Common/MatDefs/Light/Lighting.j3md"); + inputMp(mpoBool("UseMaterialColors", true)); + outDefines(def("MATERIAL_COLORS", VarType.Boolean, true)); + outUniforms(uniform("UseMaterialColors", VarType.Boolean, true)); + } + + @Test + public void testBoolOverrideFalse() { + material("Common/MatDefs/Light/Lighting.j3md"); + inputMp(mpoBool("UseMaterialColors", true)); + inputMpo(mpoBool("UseMaterialColors", false)); + outDefines(); + outUniforms(uniform("UseMaterialColors", VarType.Boolean, false)); + } + + @Test + public void testBoolOverrideTrue() { + material("Common/MatDefs/Light/Lighting.j3md"); + inputMp(mpoBool("UseMaterialColors", false)); + inputMpo(mpoBool("UseMaterialColors", true)); + outDefines(def("MATERIAL_COLORS", VarType.Boolean, true)); + outUniforms(uniform("UseMaterialColors", VarType.Boolean, true)); + } + + @Test + public void testFloatMpoOnly() { + material("Common/MatDefs/Light/Lighting.j3md"); + inputMpo(mpoFloat("AlphaDiscardThreshold", 3.12f)); + outDefines(def("DISCARD_ALPHA", VarType.Float, 3.12f)); + outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 3.12f)); + } + + @Test + public void testFloatMpOnly() { + material("Common/MatDefs/Light/Lighting.j3md"); + inputMp(mpoFloat("AlphaDiscardThreshold", 3.12f)); + outDefines(def("DISCARD_ALPHA", VarType.Float, 3.12f)); + outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 3.12f)); + } + + @Test + public void testFloatOverride() { + material("Common/MatDefs/Light/Lighting.j3md"); + inputMp(mpoFloat("AlphaDiscardThreshold", 3.12f)); + inputMpo(mpoFloat("AlphaDiscardThreshold", 2.79f)); + outDefines(def("DISCARD_ALPHA", VarType.Float, 2.79f)); + outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 2.79f)); + } + + @Test + public void testMpoDisable() { + material("Common/MatDefs/Light/Lighting.j3md"); + inputMp(mpoFloat("AlphaDiscardThreshold", 3.12f)); + + MatParamOverride override = mpoFloat("AlphaDiscardThreshold", 2.79f); + inputMpo(override); + outDefines(def("DISCARD_ALPHA", VarType.Float, 2.79f)); + outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 2.79f)); + + reset(); + override.setEnabled(false); + outDefines(def("DISCARD_ALPHA", VarType.Float, 3.12f)); + outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 3.12f)); + + reset(); + override.setEnabled(true); + outDefines(def("DISCARD_ALPHA", VarType.Float, 2.79f)); + outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 2.79f)); + } + + @Test + public void testIntMpoOnly() { + material("Common/MatDefs/Light/Lighting.j3md"); + inputMpo(mpoInt("NumberOfBones", 1234)); + outDefines(def("NUM_BONES", VarType.Int, 1234)); + outUniforms(uniform("NumberOfBones", VarType.Int, 1234)); + } + + @Test + public void testIntMpOnly() { + material("Common/MatDefs/Light/Lighting.j3md"); + inputMp(mpoInt("NumberOfBones", 1234)); + outDefines(def("NUM_BONES", VarType.Int, 1234)); + outUniforms(uniform("NumberOfBones", VarType.Int, 1234)); + } + + @Test + public void testIntOverride() { + material("Common/MatDefs/Light/Lighting.j3md"); + inputMp(mpoInt("NumberOfBones", 1234)); + inputMpo(mpoInt("NumberOfBones", 4321)); + outDefines(def("NUM_BONES", VarType.Int, 4321)); + outUniforms(uniform("NumberOfBones", VarType.Int, 4321)); + } + + @Test + public void testMatrixArray() { + Matrix4f[] matrices = new Matrix4f[]{ + new Matrix4f() + }; + + material("Common/MatDefs/Light/Lighting.j3md"); + inputMpo(mpoMatrix4Array("BoneMatrices", matrices)); + outDefines(); + outUniforms(uniform("BoneMatrices", VarType.Matrix4Array, matrices)); + } + + @Test + public void testNonExistentParameter() { + material("Common/MatDefs/Light/Lighting.j3md"); + inputMpo(mpoInt("NonExistent", 4321)); + outDefines(); + outUniforms(); + } + + @Test + public void testWrongType() { + material("Common/MatDefs/Light/Lighting.j3md"); + inputMpo(mpoInt("UseMaterialColors", 4321)); + outDefines(); + outUniforms(); + } + + @Test + public void testParamOnly() { + material("Common/MatDefs/Light/Lighting.j3md"); + inputMpo(mpoFloat("ShadowMapSize", 3.12f)); + outDefines(); + outUniforms(uniform("ShadowMapSize", VarType.Float, 3.12f)); + } + + @Test + public void testRemove() { + material("Common/MatDefs/Light/Lighting.j3md"); + + reset(); + inputMp(mpoInt("NumberOfBones", 1234)); + outDefines(def("NUM_BONES", VarType.Int, 1234)); + outUniforms(uniform("NumberOfBones", VarType.Int, 1234)); + + reset(); + inputMpo(mpoInt("NumberOfBones", 4321)); + outDefines(def("NUM_BONES", VarType.Int, 4321)); + outUniforms(uniform("NumberOfBones", VarType.Int, 4321)); + + reset(); + geometry.clearMatParamOverrides(); + geometry.updateGeometricState(); + outDefines(def("NUM_BONES", VarType.Int, 1234)); + outUniforms(uniform("NumberOfBones", VarType.Int, 1234)); + + reset(); + geometry.getMaterial().clearParam("NumberOfBones"); + outDefines(); + outUniforms(); + + reset(); + inputMpo(mpoInt("NumberOfBones", 4321)); + outDefines(def("NUM_BONES", VarType.Int, 4321)); + outUniforms(uniform("NumberOfBones", VarType.Int, 4321)); + + reset(); + inputMp(mpoInt("NumberOfBones", 1234)); + outDefines(def("NUM_BONES", VarType.Int, 4321)); + outUniforms(uniform("NumberOfBones", VarType.Int, 4321)); + } + + public void testRemoveOverride() { + material("Common/MatDefs/Light/Lighting.j3md"); + + reset(); + inputMp(mpoInt("NumberOfBones", 1234)); + outDefines(def("NUM_BONES", VarType.Int, 1234)); + outUniforms(uniform("NumberOfBones", VarType.Int, 1234)); + + reset(); + inputMpo(mpoInt("NumberOfBones", 4321)); + outDefines(def("NUM_BONES", VarType.Int, 4321)); + outUniforms(uniform("NumberOfBones", VarType.Int, 4321)); + + reset(); + geometry.clearMatParamOverrides(); + outDefines(def("NUM_BONES", VarType.Int, 1234)); + outUniforms(uniform("NumberOfBones", VarType.Int, 1234)); + } + + @Test + public void testRemoveMpoOnly() { + material("Common/MatDefs/Light/Lighting.j3md"); + + reset(); + inputMpo(mpoInt("NumberOfBones", 4321)); + outDefines(def("NUM_BONES", VarType.Int, 4321)); + outUniforms(uniform("NumberOfBones", VarType.Int, 4321)); + + reset(); + geometry.clearMatParamOverrides(); + geometry.updateGeometricState(); + outDefines(); + outUniforms(); + } + + @Test + public void testTextureMpoOnly() { + material("Common/MatDefs/Light/Lighting.j3md"); + Texture2D tex = new Texture2D(128, 128, Format.RGBA8); + + inputMpo(mpoTexture2D("DiffuseMap", tex)); + outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex)); + outUniforms(uniform("DiffuseMap", VarType.Int, 0)); + outTextures(tex); + } + + @Test + public void testTextureOverride() { + material("Common/MatDefs/Light/Lighting.j3md"); + Texture2D tex1 = new Texture2D(128, 128, Format.RGBA8); + Texture2D tex2 = new Texture2D(128, 128, Format.RGBA8); + + inputMp(mpoTexture2D("DiffuseMap", tex1)); + inputMpo(mpoTexture2D("DiffuseMap", tex2)); + + outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex2)); + outUniforms(uniform("DiffuseMap", VarType.Int, 0)); + outTextures(tex2); + } + + @Test + public void testRemoveTexture() { + material("Common/MatDefs/Light/Lighting.j3md"); + Texture2D tex = new Texture2D(128, 128, Format.RGBA8); + + reset(); + inputMpo(mpoTexture2D("DiffuseMap", tex)); + outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex)); + outUniforms(uniform("DiffuseMap", VarType.Int, 0)); + outTextures(tex); + + reset(); + geometry.clearMatParamOverrides(); + geometry.updateGeometricState(); + outDefines(); + outUniforms(); + outTextures(); + } + + @Test + public void testRemoveTextureOverride() { + material("Common/MatDefs/Light/Lighting.j3md"); + Texture2D tex1 = new Texture2D(128, 128, Format.RGBA8); + Texture2D tex2 = new Texture2D(128, 128, Format.RGBA8); + + reset(); + inputMp(mpoTexture2D("DiffuseMap", tex1)); + outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex1)); + outUniforms(uniform("DiffuseMap", VarType.Int, 0)); + outTextures(tex1); + + reset(); + inputMpo(mpoTexture2D("DiffuseMap", tex2)); + outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex2)); + outUniforms(uniform("DiffuseMap", VarType.Int, 0)); + outTextures(tex2); + + reset(); + geometry.clearMatParamOverrides(); + geometry.updateGeometricState(); + outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex1)); + outUniforms(uniform("DiffuseMap", VarType.Int, 0)); + outTextures(tex1); + } + + private static final class Define { + + public String name; + public VarType type; + public Object value; + + @Override + public int hashCode() { + int hash = 3; + hash = 89 * hash + this.name.hashCode(); + hash = 89 * hash + this.type.hashCode(); + hash = 89 * hash + this.value.hashCode(); + return hash; + } + + @Override + public boolean equals(Object obj) { + final Define other = (Define) obj; + return this.name.equals(other.name) && this.type.equals(other.type) && this.value.equals(other.value); + } + } + + private final Geometry geometry = new Geometry("geometry", new Box(1, 1, 1)); + private final LightList lightList = new LightList(geometry); + + private final NullRenderer renderer = new NullRenderer() { + @Override + public void setShader(Shader shader) { + MaterialMatParamOverrideTest.this.usedShader = shader; + evaluated = true; + } + + @Override + public void setTexture(int unit, Texture texture) { + MaterialMatParamOverrideTest.this.usedTextures[unit] = texture; + } + }; + private final RenderManager renderManager = new RenderManager(renderer); + + private boolean evaluated = false; + private Shader usedShader = null; + private final Texture[] usedTextures = new Texture[32]; + + private void inputMp(MatParam... params) { + if (evaluated) { + throw new IllegalStateException(); + } + Material mat = geometry.getMaterial(); + for (MatParam param : params) { + mat.setParam(param.getName(), param.getVarType(), param.getValue()); + } + } + + private void inputMpo(MatParamOverride... overrides) { + if (evaluated) { + throw new IllegalStateException(); + } + for (MatParamOverride override : overrides) { + geometry.addMatParamOverride(override); + } + geometry.updateGeometricState(); + } + + private void reset() { + evaluated = false; + usedShader = null; + Arrays.fill(usedTextures, null); + } + + private Define def(String name, VarType type, Object value) { + Define d = new Define(); + d.name = name; + d.type = type; + d.value = value; + return d; + } + + private Uniform uniform(String name, VarType type, Object value) { + Uniform u = new Uniform(); + u.setName("m_" + name); + u.setValue(type, value); + return u; + } + + private void material(String path) { + AssetManager assetManager = TestUtil.createAssetManager(); + geometry.setMaterial(new Material(assetManager, path)); + } + + private void evaluateTechniqueDef() { + Assert.assertFalse(evaluated); + Material mat = geometry.getMaterial(); + mat.render(geometry, lightList, renderManager); + Assert.assertTrue(evaluated); + } + + private void outTextures(Texture... textures) { + for (int i = 0; i < usedTextures.length; i++) { + if (i < textures.length) { + Assert.assertSame(textures[i], usedTextures[i]); + } else { + Assert.assertNull(usedTextures[i]); + } + } + } + + private void outDefines(Define... expectedDefinesArray) { + Map nameToDefineMap = new HashMap(); + for (Define define : expectedDefinesArray) { + nameToDefineMap.put(define.name, define); + } + + if (!evaluated) { + evaluateTechniqueDef(); + } + + Material mat = geometry.getMaterial(); + Technique tech = mat.getActiveTechnique(); + TechniqueDef def = tech.getDef(); + DefineList actualDefines = tech.getDynamicDefines(); + + String[] defineNames = def.getDefineNames(); + VarType[] defineTypes = def.getDefineTypes(); + + Assert.assertEquals(defineNames.length, defineTypes.length); + + for (int index = 0; index < defineNames.length; index++) { + String name = defineNames[index]; + VarType type = defineTypes[index]; + Define expectedDefine = nameToDefineMap.remove(name); + Object expectedValue = null; + + if (expectedDefine != null) { + Assert.assertEquals(expectedDefine.type, type); + expectedValue = expectedDefine.value; + } + + switch (type) { + case Boolean: + if (expectedValue != null) { + Assert.assertEquals((boolean) (Boolean) expectedValue, actualDefines.getBoolean(index)); + } else { + Assert.assertEquals(false, actualDefines.getBoolean(index)); + } + break; + case Int: + if (expectedValue != null) { + Assert.assertEquals((int) (Integer) expectedValue, actualDefines.getInt(index)); + } else { + Assert.assertEquals(0, actualDefines.getInt(index)); + } + break; + case Float: + if (expectedValue != null) { + Assert.assertEquals((float) (Float) expectedValue, actualDefines.getFloat(index), 0f); + } else { + Assert.assertEquals(0f, actualDefines.getFloat(index), 0f); + } + break; + default: + if (expectedValue != null) { + Assert.assertEquals(1, actualDefines.getInt(index)); + } else { + Assert.assertEquals(0, actualDefines.getInt(index)); + } + break; + } + } + + Assert.assertTrue(nameToDefineMap.isEmpty()); + } + + private void outUniforms(Uniform... uniforms) { + HashSet actualUniforms = new HashSet<>(); + + for (Uniform uniform : usedShader.getUniformMap().values()) { + if (uniform.getName().startsWith("m_") + && !IGNORED_UNIFORMS.contains(uniform.getName())) { + actualUniforms.add(uniform); + } + } + + HashSet expectedUniforms = new HashSet<>(Arrays.asList(uniforms)); + + if (!expectedUniforms.equals(actualUniforms)) { + Assert.fail("Uniform lists must match: " + expectedUniforms + " != " + actualUniforms); + } + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/FastMathTest.java b/jme3-core/src/test/java/com/jme3/math/FastMathTest.java index a74390d42..709f0829c 100644 --- a/jme3-core/src/test/java/com/jme3/math/FastMathTest.java +++ b/jme3-core/src/test/java/com/jme3/math/FastMathTest.java @@ -33,6 +33,9 @@ package com.jme3.math; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import org.junit.Ignore; + /** * Verifies that algorithms in {@link FastMath} are working correctly. * @@ -56,4 +59,39 @@ public class FastMathTest { assert nextPowerOf2 == nearestPowerOfTwoSlow(i); } } + + private static int fastCounterClockwise(Vector2f p0, Vector2f p1, Vector2f p2) { + float result = (p1.x - p0.x) * (p2.y - p1.y) - (p1.y - p0.y) * (p2.x - p1.x); + return (int) Math.signum(result); + } + + private static Vector2f randomVector() { + return new Vector2f(FastMath.nextRandomFloat(), + FastMath.nextRandomFloat()); + } + + @Ignore + @Test + public void testCounterClockwise() { + for (int i = 0; i < 100; i++) { + Vector2f p0 = randomVector(); + Vector2f p1 = randomVector(); + Vector2f p2 = randomVector(); + + int fastResult = fastCounterClockwise(p0, p1, p2); + int slowResult = FastMath.counterClockwise(p0, p1, p2); + + assert fastResult == slowResult; + } + + // duplicate test + Vector2f p0 = new Vector2f(0,0); + Vector2f p1 = new Vector2f(0,0); + Vector2f p2 = new Vector2f(0,1); + + int fastResult = fastCounterClockwise(p0, p1, p2); + int slowResult = FastMath.counterClockwise(p0, p1, p2); + + assertEquals(slowResult, fastResult); + } } diff --git a/jme3-core/src/test/java/com/jme3/renderer/OpaqueComparatorTest.java b/jme3-core/src/test/java/com/jme3/renderer/OpaqueComparatorTest.java new file mode 100644 index 000000000..84555c9f6 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/renderer/OpaqueComparatorTest.java @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2009-2015 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.renderer; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.queue.GeometryList; +import com.jme3.renderer.queue.OpaqueComparator; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.shape.Box; +import com.jme3.system.TestUtil; +import com.jme3.texture.Image; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.texture.image.ColorSpace; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.util.HashSet; +import java.util.Set; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +public class OpaqueComparatorTest { + + private final Mesh mesh = new Box(1,1,1); + private Camera cam = new Camera(1, 1); + private RenderManager renderManager; + private AssetManager assetManager; + private OpaqueComparator comparator = new OpaqueComparator(); + + @Before + public void setUp() { + assetManager = TestUtil.createAssetManager(); + renderManager = TestUtil.createRenderManager(); + comparator.setCamera(cam); + } + + /** + * Given a correctly sorted list of materials, check if the + * opaque comparator can sort a reversed list of them. + * + * Each material will be cloned so that none of them will be equal to each other + * in reference, forcing the comparator to compare the material sort ID. + * + * E.g. for a list of materials A, B, C, the following list will be generated: + *
    C, B, A, C, B, A, C, B, A
    , it should result in + *
    A, A, A, B, B, B, C, C, C
    . + * + * @param materials The pre-sorted list of materials to check sorting for. + */ + private void testSort(Material ... materials) { + GeometryList gl = new GeometryList(comparator); + for (int g = 0; g < 5; g++) { + for (int i = materials.length - 1; i >= 0; i--) { + Geometry geom = new Geometry("geom", mesh); + Material clonedMaterial = materials[i].clone(); + + if (materials[i].getActiveTechnique() != null) { + String techniqueName = materials[i].getActiveTechnique().getDef().getName(); + clonedMaterial.selectTechnique(techniqueName, renderManager); + } else { + clonedMaterial.selectTechnique("Default", renderManager); + } + + geom.setMaterial(clonedMaterial); + gl.add(geom); + } + } + gl.sort(); + + for (int i = 0; i < gl.size(); i++) { + Material mat = gl.get(i).getMaterial(); + String sortId = Integer.toHexString(mat.getSortId()).toUpperCase(); + System.out.print(sortId + "\t"); + System.out.println(mat); + } + + Set alreadySeen = new HashSet(); + Material current = null; + for (int i = 0; i < gl.size(); i++) { + Material mat = gl.get(i).getMaterial(); + if (current == null) { + current = mat; + } else if (!current.getName().equals(mat.getName())) { + assert !alreadySeen.contains(mat.getName()); + alreadySeen.add(current.getName()); + current = mat; + } + } + + for (int i = 0; i < materials.length; i++) { + for (int g = 0; g < 5; g++) { + int index = i * 5 + g; + Material mat1 = gl.get(index).getMaterial(); + Material mat2 = materials[i]; + assert mat1.getName().equals(mat2.getName()) : + mat1.getName() + " != " + mat2.getName() + " for index " + index; + } + } + } + + @Test + public void testSortByMaterialDef() { + Material lightingMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Material particleMat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + Material unshadedMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + Material skyMat = new Material(assetManager, "Common/MatDefs/Misc/Sky.j3md"); + + lightingMat.setName("MatLight"); + particleMat.setName("MatParticle"); + unshadedMat.setName("MatUnshaded"); + skyMat.setName("MatSky"); + testSort(skyMat, lightingMat, particleMat, unshadedMat); + } + + @Test + public void testSortByTechnique() { + Material lightingMatDefault = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Material lightingPreShadow = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Material lightingPostShadow = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Material lightingMatPreNormalPass = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Material lightingMatGBuf = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Material lightingMatGlow = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + + lightingMatDefault.setName("TechDefault"); + lightingMatDefault.selectTechnique("Default", renderManager); + + lightingPostShadow.setName("TechPostShad"); + lightingPostShadow.selectTechnique("PostShadow", renderManager); + + lightingPreShadow.setName("TechPreShad"); + lightingPreShadow.selectTechnique("PreShadow", renderManager); + + lightingMatPreNormalPass.setName("TechNorm"); + lightingMatPreNormalPass.selectTechnique("PreNormalPass", renderManager); + + lightingMatGBuf.setName("TechGBuf"); + lightingMatGBuf.selectTechnique("GBuf", renderManager); + + lightingMatGlow.setName("TechGlow"); + lightingMatGlow.selectTechnique("Glow", renderManager); + + testSort(lightingMatGlow, lightingPreShadow, lightingMatPreNormalPass, + lightingMatDefault, lightingPostShadow, lightingMatGBuf); + } + + @Test(expected = AssertionError.class) + public void testNoSortByParam() { + Material sameMat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + Material sameMat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + + sameMat1.setName("MatRed"); + sameMat1.setColor("Color", ColorRGBA.Red); + + sameMat2.setName("MatBlue"); + sameMat2.setColor("Color", ColorRGBA.Blue); + + testSort(sameMat1, sameMat2); + } + + private Texture createTexture(String name) { + ByteBuffer bb = BufferUtils.createByteBuffer(3); + Image image = new Image(Format.RGB8, 1, 1, bb, ColorSpace.sRGB); + Texture2D texture = new Texture2D(image); + texture.setName(name); + return texture; + } + + @Test + public void testSortByTexture() { + Material texture1Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + Material texture2Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + Material texture3Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + + Texture tex1 = createTexture("A"); + tex1.getImage().setId(1); + + Texture tex2 = createTexture("B"); + tex2.getImage().setId(2); + + Texture tex3 = createTexture("C"); + tex3.getImage().setId(3); + + texture1Mat.setName("TexA"); + texture1Mat.setTexture("ColorMap", tex1); + + texture2Mat.setName("TexB"); + texture2Mat.setTexture("ColorMap", tex2); + + texture3Mat.setName("TexC"); + texture3Mat.setTexture("ColorMap", tex3); + + testSort(texture1Mat, texture2Mat, texture3Mat); + } + + @Test + public void testSortByShaderDefines() { + Material lightingMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Material lightingMatVColor = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Material lightingMatVLight = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Material lightingMatTC = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Material lightingMatVColorLight = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Material lightingMatTCVColorLight = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + + lightingMat.setName("DefNone"); + + lightingMatVColor.setName("DefVC"); + lightingMatVColor.setBoolean("UseVertexColor", true); + + lightingMatVLight.setName("DefVL"); + lightingMatVLight.setBoolean("VertexLighting", true); + + lightingMatTC.setName("DefTC"); + lightingMatTC.setBoolean("SeparateTexCoord", true); + + lightingMatVColorLight.setName("DefVCVL"); + lightingMatVColorLight.setBoolean("UseVertexColor", true); + lightingMatVColorLight.setBoolean("VertexLighting", true); + + lightingMatTCVColorLight.setName("DefVCVLTC"); + lightingMatTCVColorLight.setBoolean("UseVertexColor", true); + lightingMatTCVColorLight.setBoolean("VertexLighting", true); + lightingMatTCVColorLight.setBoolean("SeparateTexCoord", true); + + testSort(lightingMat, lightingMatVColor, lightingMatVLight, + lightingMatVColorLight, lightingMatTC, lightingMatTCVColorLight); + } + + @Test + public void testSortByAll() { + Material matBase1 = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + Material matBase2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + + Texture texBase = createTexture("BASE"); + texBase.getImage().setId(1); + Texture tex1 = createTexture("1"); + tex1.getImage().setId(2); + Texture tex2 = createTexture("2"); + tex2.getImage().setId(3); + + matBase1.setName("BASE"); + matBase1.selectTechnique("Default", renderManager); + matBase1.setBoolean("UseVertexColor", true); + matBase1.setTexture("DiffuseMap", texBase); + + Material mat1100 = matBase1.clone(); + mat1100.setName("1100"); + mat1100.selectTechnique("PreShadow", renderManager); + + Material mat1101 = matBase1.clone(); + mat1101.setName("1101"); + mat1101.selectTechnique("PreShadow", renderManager); + mat1101.setTexture("DiffuseMap", tex1); + + Material mat1102 = matBase1.clone(); + mat1102.setName("1102"); + mat1102.selectTechnique("PreShadow", renderManager); + mat1102.setTexture("DiffuseMap", tex2); + + Material mat1110 = matBase1.clone(); + mat1110.setName("1110"); + mat1110.selectTechnique("PreShadow", renderManager); + mat1110.setFloat("AlphaDiscardThreshold", 2f); + + Material mat1120 = matBase1.clone(); + mat1120.setName("1120"); + mat1120.selectTechnique("PreShadow", renderManager); + mat1120.setBoolean("UseInstancing", true); + + Material mat1121 = matBase1.clone(); + mat1121.setName("1121"); + mat1121.selectTechnique("PreShadow", renderManager); + mat1121.setBoolean("UseInstancing", true); + mat1121.setTexture("DiffuseMap", tex1); + + Material mat1122 = matBase1.clone(); + mat1122.setName("1122"); + mat1122.selectTechnique("PreShadow", renderManager); + mat1122.setBoolean("UseInstancing", true); + mat1122.setTexture("DiffuseMap", tex2); + + Material mat1140 = matBase1.clone(); + mat1140.setName("1140"); + mat1140.selectTechnique("PreShadow", renderManager); + mat1140.setFloat("AlphaDiscardThreshold", 2f); + mat1140.setBoolean("UseInstancing", true); + + Material mat1200 = matBase1.clone(); + mat1200.setName("1200"); + mat1200.selectTechnique("PostShadow", renderManager); + + Material mat1210 = matBase1.clone(); + mat1210.setName("1210"); + mat1210.selectTechnique("PostShadow", renderManager); + mat1210.setFloat("AlphaDiscardThreshold", 2f); + + Material mat1220 = matBase1.clone(); + mat1220.setName("1220"); + mat1220.selectTechnique("PostShadow", renderManager); + mat1220.setBoolean("UseInstancing", true); + + Material mat2000 = matBase2.clone(); + mat2000.setName("2000"); + + testSort(mat1100, mat1101, mat1102, mat1110, + mat1120, mat1121, mat1122, mat1140, + mat1200, mat1210, mat1220, mat2000); + } +} diff --git a/jme3-core/src/test/java/com/jme3/scene/MPOTestUtils.java b/jme3-core/src/test/java/com/jme3/scene/MPOTestUtils.java new file mode 100644 index 000000000..183dece70 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/scene/MPOTestUtils.java @@ -0,0 +1,173 @@ +/* + * 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.scene; + +import com.jme3.material.MatParamOverride; +import com.jme3.math.Matrix4f; +import com.jme3.renderer.Camera; +import com.jme3.shader.VarType; +import com.jme3.texture.Texture2D; +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.Set; +import static org.junit.Assert.assertEquals; + +public class MPOTestUtils { + + private static final Camera DUMMY_CAM = new Camera(640, 480); + + private static final SceneGraphVisitor VISITOR = new SceneGraphVisitor() { + @Override + public void visit(Spatial spatial) { + validateSubScene(spatial); + } + }; + + private static void validateSubScene(Spatial scene) { + scene.checkCulling(DUMMY_CAM); + + Set actualOverrides = new HashSet(); + for (MatParamOverride override : scene.getWorldMatParamOverrides()) { + actualOverrides.add(override); + } + + Set expectedOverrides = new HashSet(); + Spatial current = scene; + while (current != null) { + for (MatParamOverride override : current.getLocalMatParamOverrides()) { + expectedOverrides.add(override); + } + current = current.getParent(); + } + + assertEquals("For " + scene, expectedOverrides, actualOverrides); + } + + public static void validateScene(Spatial scene) { + scene.updateGeometricState(); + scene.depthFirstTraversal(VISITOR); + } + + public static MatParamOverride mpoInt(String name, int value) { + return new MatParamOverride(VarType.Int, name, value); + } + + public static MatParamOverride mpoBool(String name, boolean value) { + return new MatParamOverride(VarType.Boolean, name, value); + } + + public static MatParamOverride mpoFloat(String name, float value) { + return new MatParamOverride(VarType.Float, name, value); + } + + public static MatParamOverride mpoMatrix4Array(String name, Matrix4f[] value) { + return new MatParamOverride(VarType.Matrix4Array, name, value); + } + + public static MatParamOverride mpoTexture2D(String name, Texture2D texture) { + return new MatParamOverride(VarType.Texture2D, name, texture); + } + + private static int getRefreshFlags(Spatial scene) { + try { + Field refreshFlagsField = Spatial.class.getDeclaredField("refreshFlags"); + refreshFlagsField.setAccessible(true); + return (Integer) refreshFlagsField.get(scene); + } catch (NoSuchFieldException ex) { + throw new AssertionError(ex); + } catch (SecurityException ex) { + throw new AssertionError(ex); + } catch (IllegalArgumentException ex) { + throw new AssertionError(ex); + } catch (IllegalAccessException ex) { + throw new AssertionError(ex); + } + } + + private static void dumpSceneRF(Spatial scene, String indent, boolean last, int refreshFlagsMask) { + StringBuilder sb = new StringBuilder(); + + sb.append(indent); + if (last) { + if (!indent.isEmpty()) { + sb.append("└─"); + } else { + sb.append(" "); + } + indent += " "; + } else { + sb.append("├─"); + indent += "│ "; + } + sb.append(scene.getName()); + int rf = getRefreshFlags(scene) & refreshFlagsMask; + if (rf != 0) { + sb.append("("); + if ((rf & 0x1) != 0) { + sb.append("T"); + } + if ((rf & 0x2) != 0) { + sb.append("B"); + } + if ((rf & 0x4) != 0) { + sb.append("L"); + } + if ((rf & 0x8) != 0) { + sb.append("l"); + } + if ((rf & 0x10) != 0) { + sb.append("O"); + } + sb.append(")"); + } + + if (!scene.getLocalMatParamOverrides().isEmpty()) { + sb.append(" [MPO]"); + } + + System.out.println(sb); + + if (scene instanceof Node) { + Node node = (Node) scene; + int childIndex = 0; + for (Spatial child : node.getChildren()) { + boolean childLast = childIndex == node.getQuantity() - 1; + dumpSceneRF(child, indent, childLast, refreshFlagsMask); + childIndex++; + } + } + } + + public static void dumpSceneRF(Spatial scene, int refreshFlagsMask) { + dumpSceneRF(scene, "", true, refreshFlagsMask); + } +} diff --git a/jme3-core/src/test/java/com/jme3/scene/SceneMatParamOverrideTest.java b/jme3-core/src/test/java/com/jme3/scene/SceneMatParamOverrideTest.java new file mode 100644 index 000000000..a615d5c92 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/scene/SceneMatParamOverrideTest.java @@ -0,0 +1,278 @@ +/* + * 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.scene; + +import com.jme3.asset.AssetManager; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.material.MatParamOverride; +import org.junit.Test; + +import static com.jme3.scene.MPOTestUtils.*; +import static org.junit.Assert.*; + +import com.jme3.system.TestUtil; +import java.util.ArrayList; +import java.util.List; + +/** + * Validates how {@link MatParamOverride MPOs} work on the scene level. + * + * @author Kirill Vainer + */ +public class SceneMatParamOverrideTest { + + private static Node createDummyScene() { + Node scene = new Node("Scene Node"); + + Node a = new Node("A"); + Node b = new Node("B"); + + Node c = new Node("C"); + Node d = new Node("D"); + + Node e = new Node("E"); + Node f = new Node("F"); + + Node g = new Node("G"); + Node h = new Node("H"); + Node j = new Node("J"); + + scene.attachChild(a); + scene.attachChild(b); + + a.attachChild(c); + a.attachChild(d); + + b.attachChild(e); + b.attachChild(f); + + c.attachChild(g); + c.attachChild(h); + c.attachChild(j); + + return scene; + } + + @Test + public void testOverrides_Empty() { + Node n = new Node("Node"); + assertTrue(n.getLocalMatParamOverrides().isEmpty()); + assertTrue(n.getWorldMatParamOverrides().isEmpty()); + + n.updateGeometricState(); + assertTrue(n.getLocalMatParamOverrides().isEmpty()); + assertTrue(n.getWorldMatParamOverrides().isEmpty()); + } + + @Test + public void testOverrides_AddRemove() { + MatParamOverride override = mpoBool("Test", true); + Node n = new Node("Node"); + + n.removeMatParamOverride(override); + assertTrue(n.getLocalMatParamOverrides().isEmpty()); + assertTrue(n.getWorldMatParamOverrides().isEmpty()); + + n.addMatParamOverride(override); + + assertSame(n.getLocalMatParamOverrides().get(0), override); + assertTrue(n.getWorldMatParamOverrides().isEmpty()); + n.updateGeometricState(); + + assertSame(n.getLocalMatParamOverrides().get(0), override); + assertSame(n.getWorldMatParamOverrides().get(0), override); + + n.removeMatParamOverride(override); + assertTrue(n.getLocalMatParamOverrides().isEmpty()); + assertSame(n.getWorldMatParamOverrides().get(0), override); + + n.updateGeometricState(); + assertTrue(n.getLocalMatParamOverrides().isEmpty()); + assertTrue(n.getWorldMatParamOverrides().isEmpty()); + } + + @Test + public void testOverrides_Clear() { + MatParamOverride override = mpoBool("Test", true); + Node n = new Node("Node"); + + n.clearMatParamOverrides(); + assertTrue(n.getLocalMatParamOverrides().isEmpty()); + assertTrue(n.getWorldMatParamOverrides().isEmpty()); + + n.addMatParamOverride(override); + n.clearMatParamOverrides(); + assertTrue(n.getLocalMatParamOverrides().isEmpty()); + assertTrue(n.getWorldMatParamOverrides().isEmpty()); + + n.addMatParamOverride(override); + n.updateGeometricState(); + n.clearMatParamOverrides(); + assertTrue(n.getLocalMatParamOverrides().isEmpty()); + assertSame(n.getWorldMatParamOverrides().get(0), override); + + n.updateGeometricState(); + assertTrue(n.getLocalMatParamOverrides().isEmpty()); + assertTrue(n.getWorldMatParamOverrides().isEmpty()); + + n.addMatParamOverride(override); + n.clearMatParamOverrides(); + n.updateGeometricState(); + assertTrue(n.getLocalMatParamOverrides().isEmpty()); + assertTrue(n.getWorldMatParamOverrides().isEmpty()); + } + + @Test + public void testOverrides_AddAfterAttach() { + Node scene = createDummyScene(); + scene.updateGeometricState(); + + Node root = new Node("Root Node"); + root.updateGeometricState(); + + root.attachChild(scene); + scene.getChild("A").addMatParamOverride(mpoInt("val", 5)); + + validateScene(root); + } + + @Test + public void testOverrides_AddBeforeAttach() { + Node scene = createDummyScene(); + scene.getChild("A").addMatParamOverride(mpoInt("val", 5)); + scene.updateGeometricState(); + + Node root = new Node("Root Node"); + root.updateGeometricState(); + + root.attachChild(scene); + + validateScene(root); + } + + @Test + public void testOverrides_RemoveBeforeAttach() { + Node scene = createDummyScene(); + scene.updateGeometricState(); + + Node root = new Node("Root Node"); + root.updateGeometricState(); + + scene.getChild("A").addMatParamOverride(mpoInt("val", 5)); + validateScene(scene); + + scene.getChild("A").clearMatParamOverrides(); + validateScene(scene); + + root.attachChild(scene); + validateScene(root); + } + + @Test + public void testOverrides_RemoveAfterAttach() { + Node scene = createDummyScene(); + scene.updateGeometricState(); + + Node root = new Node("Root Node"); + root.updateGeometricState(); + + scene.getChild("A").addMatParamOverride(mpoInt("val", 5)); + + root.attachChild(scene); + validateScene(root); + + scene.getChild("A").clearMatParamOverrides(); + validateScene(root); + } + + @Test + public void testOverrides_IdenticalNames() { + Node scene = createDummyScene(); + + scene.getChild("A").addMatParamOverride(mpoInt("val", 5)); + scene.getChild("C").addMatParamOverride(mpoInt("val", 7)); + + validateScene(scene); + } + + @Test + public void testOverrides_CloningScene_DoesntCloneMPO() { + Node originalScene = createDummyScene(); + + originalScene.getChild("A").addMatParamOverride(mpoInt("int", 5)); + originalScene.getChild("A").addMatParamOverride(mpoBool("bool", true)); + originalScene.getChild("A").addMatParamOverride(mpoFloat("float", 3.12f)); + + Node clonedScene = originalScene.clone(false); + + validateScene(clonedScene); + validateScene(originalScene); + + List clonedOverrides = clonedScene.getChild("A").getLocalMatParamOverrides(); + List originalOverrides = originalScene.getChild("A").getLocalMatParamOverrides(); + + assertNotSame(clonedOverrides, originalOverrides); + assertEquals(clonedOverrides, originalOverrides); + + for (int i = 0; i < clonedOverrides.size(); i++) { + assertNotSame(clonedOverrides.get(i), originalOverrides.get(i)); + assertEquals(clonedOverrides.get(i), originalOverrides.get(i)); + } + } + + @Test + public void testOverrides_SaveAndLoad_KeepsMPOs() { + MatParamOverride override = mpoInt("val", 5); + Node scene = createDummyScene(); + scene.getChild("A").addMatParamOverride(override); + + AssetManager assetManager = TestUtil.createAssetManager(); + Node loadedScene = BinaryExporter.saveAndLoad(assetManager, scene); + + Node root = new Node("Root Node"); + root.attachChild(loadedScene); + validateScene(root); + validateScene(scene); + + assertNotSame(override, loadedScene.getChild("A").getLocalMatParamOverrides().get(0)); + assertEquals(override, loadedScene.getChild("A").getLocalMatParamOverrides().get(0)); + } + + @Test + public void testEquals() { + assertEquals(mpoInt("val", 5), mpoInt("val", 5)); + assertEquals(mpoBool("val", true), mpoBool("val", true)); + assertNotEquals(mpoInt("val", 5), mpoInt("val", 6)); + assertNotEquals(mpoInt("val1", 5), mpoInt("val2", 5)); + assertNotEquals(mpoBool("val", true), mpoInt("val", 1)); + } +} diff --git a/jme3-core/src/test/java/com/jme3/shader/DefineListTest.java b/jme3-core/src/test/java/com/jme3/shader/DefineListTest.java new file mode 100644 index 000000000..35812b7c3 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/shader/DefineListTest.java @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2009-2015 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.shader; + +import com.jme3.math.FastMath; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class DefineListTest { + + private static final List DEFINE_NAMES = Arrays.asList("BOOL_VAR", "INT_VAR", "FLOAT_VAR"); + private static final List DEFINE_TYPES = Arrays.asList(VarType.Boolean, VarType.Int, VarType.Float); + private static final int NUM_DEFINES = DEFINE_NAMES.size(); + private static final int BOOL_VAR = 0; + private static final int INT_VAR = 1; + private static final int FLOAT_VAR = 2; + private static final DefineList EMPTY = new DefineList(NUM_DEFINES); + + @Test + public void testHashCollision() { + DefineList dl1 = new DefineList(64); + DefineList dl2 = new DefineList(64); + + // Try to cause a hash collision + // (since bit #32 is aliased to bit #1 in 32-bit ints) + dl1.set(0, 123); + dl1.set(32, 0); + + dl2.set(32, 0); + dl2.set(0, 123); + + assert dl1.hashCode() == dl2.hashCode(); + assert dl1.equals(dl2); + } + + @Test + public void testGetSet() { + DefineList dl = new DefineList(NUM_DEFINES); + + assertFalse(dl.getBoolean(BOOL_VAR)); + assertEquals(dl.getInt(INT_VAR), 0); + assertEquals(dl.getFloat(FLOAT_VAR), 0f, 0f); + + dl.set(BOOL_VAR, true); + dl.set(INT_VAR, -1); + dl.set(FLOAT_VAR, Float.NaN); + + assertTrue(dl.getBoolean(BOOL_VAR)); + assertEquals(dl.getInt(INT_VAR), -1); + assertTrue(Float.isNaN(dl.getFloat(FLOAT_VAR))); + } + + private String generateSource(DefineList dl) { + StringBuilder sb = new StringBuilder(); + dl.generateSource(sb, DEFINE_NAMES, DEFINE_TYPES); + return sb.toString(); + } + + @Test + public void testSourceInitial() { + DefineList dl = new DefineList(NUM_DEFINES); + assert dl.hashCode() == 0; + assert generateSource(dl).equals(""); + } + + @Test + public void testSourceBooleanDefine() { + DefineList dl = new DefineList(NUM_DEFINES); + + dl.set(BOOL_VAR, true); + assert dl.hashCode() == 1; + assert generateSource(dl).equals("#define BOOL_VAR 1\n"); + + dl.set(BOOL_VAR, false); + assert dl.hashCode() == 0; + assert generateSource(dl).equals(""); + } + + @Test + public void testSourceIntDefine() { + DefineList dl = new DefineList(NUM_DEFINES); + + int hashCodeWithInt = 1 << INT_VAR; + + dl.set(INT_VAR, 123); + assert dl.hashCode() == hashCodeWithInt; + assert generateSource(dl).equals("#define INT_VAR 123\n"); + + dl.set(INT_VAR, 0); + assert dl.hashCode() == 0; + assert generateSource(dl).equals(""); + + dl.set(INT_VAR, -99); + assert dl.hashCode() == hashCodeWithInt; + assert generateSource(dl).equals("#define INT_VAR -99\n"); + + dl.set(INT_VAR, Integer.MAX_VALUE); + assert dl.hashCode() == hashCodeWithInt; + assert generateSource(dl).equals("#define INT_VAR 2147483647\n"); + } + + @Test + public void testSourceFloatDefine() { + DefineList dl = new DefineList(NUM_DEFINES); + + dl.set(FLOAT_VAR, 1f); + assert dl.hashCode() == (1 << FLOAT_VAR); + assert generateSource(dl).equals("#define FLOAT_VAR 1.0\n"); + + dl.set(FLOAT_VAR, 0f); + assert dl.hashCode() == 0; + assert generateSource(dl).equals(""); + + dl.set(FLOAT_VAR, -1f); + assert generateSource(dl).equals("#define FLOAT_VAR -1.0\n"); + + dl.set(FLOAT_VAR, FastMath.FLT_EPSILON); + assert generateSource(dl).equals("#define FLOAT_VAR 1.1920929E-7\n"); + + dl.set(FLOAT_VAR, FastMath.PI); + assert generateSource(dl).equals("#define FLOAT_VAR 3.1415927\n"); + + try { + dl.set(FLOAT_VAR, Float.NaN); + generateSource(dl); + assert false; + } catch (IllegalArgumentException ex) { } + + try { + dl.set(FLOAT_VAR, Float.POSITIVE_INFINITY); + generateSource(dl); + assert false; + } catch (IllegalArgumentException ex) { } + + try { + dl.set(FLOAT_VAR, Float.NEGATIVE_INFINITY); + generateSource(dl); + assert false; + } catch (IllegalArgumentException ex) { } + } + + @Test + public void testEqualsAndHashCode() { + DefineList dl1 = new DefineList(NUM_DEFINES); + DefineList dl2 = new DefineList(NUM_DEFINES); + + assertTrue(dl1.hashCode() == 0); + assertEquals(dl1, dl2); + + dl1.set(BOOL_VAR, true); + + assertTrue(dl1.hashCode() == 1); + assertNotSame(dl1, dl2); + + dl2.set(BOOL_VAR, true); + + assertEquals(dl1, dl2); + + dl1.set(INT_VAR, 2); + + assertTrue(dl1.hashCode() == (1|2)); + assertNotSame(dl1, dl2); + + dl2.set(INT_VAR, 2); + + assertEquals(dl1, dl2); + + dl1.set(BOOL_VAR, false); + + assertTrue(dl1.hashCode() == 2); + assertNotSame(dl1, dl2); + } + + @Test + public void testDeepClone() { + DefineList dl1 = new DefineList(NUM_DEFINES); + DefineList dl2 = dl1.deepClone(); + + assertFalse(dl1 == dl2); + assertTrue(dl1.equals(dl2)); + assertTrue(dl1.hashCode() == dl2.hashCode()); + + dl1.set(BOOL_VAR, true); + dl2 = dl1.deepClone(); + + assertTrue(dl1.equals(dl2)); + assertTrue(dl1.hashCode() == dl2.hashCode()); + + dl1.set(INT_VAR, 123); + + assertFalse(dl1.equals(dl2)); + assertFalse(dl1.hashCode() == dl2.hashCode()); + + dl2 = dl1.deepClone(); + + assertTrue(dl1.equals(dl2)); + assertTrue(dl1.hashCode() == dl2.hashCode()); + } + + @Test + public void testGenerateSource() { + DefineList dl = new DefineList(NUM_DEFINES); + + assertEquals("", generateSource(dl)); + + dl.set(BOOL_VAR, true); + + assertEquals("#define BOOL_VAR 1\n", generateSource(dl)); + + dl.set(INT_VAR, 123); + + assertEquals("#define BOOL_VAR 1\n" + + "#define INT_VAR 123\n", generateSource(dl)); + + dl.set(BOOL_VAR, false); + + assertEquals("#define INT_VAR 123\n", generateSource(dl)); + + dl.set(BOOL_VAR, true); + + // should have predictable ordering based on defineId + assertEquals("#define BOOL_VAR 1\n" + + "#define INT_VAR 123\n", generateSource(dl)); + } + + private static String doLookup(HashMap map, boolean boolVal, int intVal, float floatVal) { + DefineList dl = new DefineList(NUM_DEFINES); + dl.set(BOOL_VAR, boolVal); + dl.set(INT_VAR, intVal); + dl.set(FLOAT_VAR, floatVal); + return map.get(dl); + } + + @Test + public void testHashLookup() { + String STR_EMPTY = "This is an empty define list"; + String STR_INT = "This define list has an int value"; + String STR_BOOL = "This define list just has boolean value set"; + String STR_BOOL_INT = "This define list has both a boolean and int value"; + String STR_BOOL_INT_FLOAT = "This define list has a boolean, int, and float value"; + + HashMap map = new HashMap(); + + DefineList lookup = new DefineList(NUM_DEFINES); + + map.put(lookup.deepClone(), STR_EMPTY); + + lookup.set(BOOL_VAR, true); + map.put(lookup.deepClone(), STR_BOOL); + + lookup.set(BOOL_VAR, false); + lookup.set(INT_VAR, 123); + map.put(lookup.deepClone(), STR_INT); + + lookup.set(BOOL_VAR, true); + map.put(lookup.deepClone(), STR_BOOL_INT); + + lookup.set(FLOAT_VAR, FastMath.PI); + map.put(lookup.deepClone(), STR_BOOL_INT_FLOAT); + + assertEquals(doLookup(map, false, 0, 0f), STR_EMPTY); + assertEquals(doLookup(map, false, 123, 0f), STR_INT); + assertEquals(doLookup(map, true, 0, 0f), STR_BOOL); + assertEquals(doLookup(map, true, 123, 0f), STR_BOOL_INT); + assertEquals(doLookup(map, true, 123, FastMath.PI), STR_BOOL_INT_FLOAT); + } +} diff --git a/jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java b/jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java new file mode 100644 index 000000000..b9beac7f6 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import com.jme3.audio.AudioRenderer; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; +import java.nio.ByteBuffer; + +public class MockJmeSystemDelegate extends JmeSystemDelegate { + + @Override + public void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException { + } + + @Override + public void showErrorDialog(String message) { + } + + @Override + public boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry) { + return false; + } + + @Override + public URL getPlatformAssetConfigURL() { + return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/General.cfg"); + } + + @Override + public JmeContext newContext(AppSettings settings, JmeContext.Type contextType) { + return null; + } + + @Override + public AudioRenderer newAudioRenderer(AppSettings settings) { + return null; + } + + @Override + public void initialize(AppSettings settings) { + } + + @Override + public void showSoftKeyboard(boolean show) { + } + +} diff --git a/jme3-core/src/test/java/com/jme3/system/TestUtil.java b/jme3-core/src/test/java/com/jme3/system/TestUtil.java new file mode 100644 index 000000000..124b59ba7 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/system/TestUtil.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2009-2015 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import com.jme3.asset.AssetConfig; +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.renderer.RenderManager; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TestUtil { + + static { + JmeSystem.setSystemDelegate(new MockJmeSystemDelegate()); + } + + public static AssetManager createAssetManager() { + Logger.getLogger(AssetConfig.class.getName()).setLevel(Level.OFF); + return new DesktopAssetManager(true); + } + + public static RenderManager createRenderManager() { + return new RenderManager(new NullRenderer()); + } +} diff --git a/jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java b/jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java index c77fa2b2e..d41dde064 100644 --- a/jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java +++ b/jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java @@ -6,11 +6,12 @@ import com.jme3.asset.plugins.FileLocator; import com.jme3.material.MaterialDef; import com.jme3.material.TechniqueDef; import com.jme3.material.plugins.J3MLoader; +import com.jme3.renderer.Caps; import com.jme3.shader.DefineList; import com.jme3.shader.Shader; -import com.jme3.shader.ShaderKey; import com.jme3.shader.plugins.GLSLLoader; import com.jme3.system.JmeSystem; +import java.util.EnumSet; import java.util.logging.Level; import java.util.logging.Logger; @@ -33,23 +34,22 @@ public class ShaderCheck { assetManager.registerLoader(GLSLLoader.class, "vert", "frag","geom","tsctrl","tseval","glsllib"); } - private static void checkMatDef(String matdefName){ + private static void checkMatDef(String matdefName) { MaterialDef def = (MaterialDef) assetManager.loadAsset(matdefName); - for (TechniqueDef techDef : def.getDefaultTechniques()){ - DefineList dl = new DefineList(); - dl.addFrom(techDef.getShaderPresetDefines()); - ShaderKey shaderKey = new ShaderKey(dl,techDef.getShaderProgramLanguages(),techDef.getShaderProgramNames()); - - Shader shader = assetManager.loadShader(shaderKey); - - for (Validator validator : validators){ + EnumSet rendererCaps = EnumSet.noneOf(Caps.class); + rendererCaps.add(Caps.GLSL100); + for (TechniqueDef techDef : def.getDefaultTechniques()) { + DefineList defines = techDef.createDefineList(); + Shader shader = techDef.getShader(assetManager, rendererCaps, defines); + for (Validator validator : validators) { StringBuilder sb = new StringBuilder(); validator.validate(shader, sb); - System.out.println("==== Validator: " + validator.getName() + " " + - validator.getInstalledVersion() + " ===="); + System.out.println("==== Validator: " + validator.getName() + " " + + validator.getInstalledVersion() + " ===="); System.out.println(sb.toString()); } } + throw new UnsupportedOperationException(); } public static void main(String[] args){ 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-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java index 37a01e8ed..d02b5d21c 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java +++ b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java @@ -43,6 +43,7 @@ import com.jme3.system.JmeContext.Type; import com.jme3.util.Screenshots; import java.awt.EventQueue; import java.awt.Graphics2D; +import java.awt.GraphicsEnvironment; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; @@ -116,12 +117,16 @@ public class JmeDesktopSystem extends JmeSystemDelegate { @Override public void showErrorDialog(String message) { - final String msg = message; - EventQueue.invokeLater(new Runnable() { - public void run() { - ErrorDialog.showDialog(msg); - } - }); + if (!GraphicsEnvironment.isHeadless()) { + final String msg = message; + EventQueue.invokeLater(new Runnable() { + public void run() { + ErrorDialog.showDialog(msg); + } + }); + } else { + System.err.println("[JME ERROR] " + message); + } } @Override @@ -129,6 +134,9 @@ public class JmeDesktopSystem extends JmeSystemDelegate { if (SwingUtilities.isEventDispatchThread()) { throw new IllegalStateException("Cannot run from EDT"); } + if (GraphicsEnvironment.isHeadless()) { + throw new IllegalStateException("Cannot show dialog in headless environment"); + } final AppSettings settings = new AppSettings(false); settings.copyFrom(sourceSettings); @@ -333,27 +341,13 @@ public class JmeDesktopSystem extends JmeSystemDelegate { if (initialized) { return; } - initialized = true; - try { - if (!lowPermissions) { - // can only modify logging settings - // if permissions are available -// JmeFormatter formatter = new JmeFormatter(); -// Handler fileHandler = new FileHandler("jme.log"); -// fileHandler.setFormatter(formatter); -// Logger.getLogger("").addHandler(fileHandler); -// Handler consoleHandler = new ConsoleHandler(); -// consoleHandler.setFormatter(formatter); -// Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]); -// Logger.getLogger("").addHandler(consoleHandler); + logger.log(Level.INFO, getBuildInfo()); + if (!lowPermissions) { + if (NativeLibraryLoader.isUsingNativeBullet()) { + NativeLibraryLoader.loadNativeLibrary("bulletjme", true); } -// } catch (IOException ex){ -// logger.log(Level.SEVERE, "I/O Error while creating log file", ex); - } catch (SecurityException ex) { - logger.log(Level.SEVERE, "Security error in creating log file", ex); } - logger.log(Level.INFO, getBuildInfo()); } @Override diff --git a/jme3-examples/build.gradle b/jme3-examples/build.gradle index 371a7aaab..be2e106a6 100644 --- a/jme3-examples/build.gradle +++ b/jme3-examples/build.gradle @@ -11,6 +11,11 @@ task run(dependsOn: 'build', type:JavaExec) { jvmArgs "-XstartOnFirstThread" jvmArgs "-Djava.awt.headless=true" } + + if (System.properties['java.util.logging.config.file'] != null) { + systemProperty "java.util.logging.config.file", System.properties['java.util.logging.config.file'] + } + if( assertions == "true" ){ enableAssertions = true; } 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/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/TestNativeLoader.java b/jme3-examples/src/main/java/jme3test/app/TestNativeLoader.java deleted file mode 100644 index 2854fd217..000000000 --- a/jme3-examples/src/main/java/jme3test/app/TestNativeLoader.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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.app; - -import com.jme3.system.NativeLibraryLoader; -import java.io.File; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Try to load some natives. - * - * @author Kirill Vainer - */ -public class TestNativeLoader { - - private static final File WORKING_FOLDER = new File(System.getProperty("user.dir")); - - private static void tryLoadLwjgl() { - NativeLibraryLoader.loadNativeLibrary("lwjgl", true); - System.out.println("Succeeded in loading LWJGL.\n\tVersion: " + - org.lwjgl.Sys.getVersion()); - } - - private static void tryLoadJinput() { - NativeLibraryLoader.loadNativeLibrary("jinput", true); - NativeLibraryLoader.loadNativeLibrary("jinput-dx8", true); - - net.java.games.input.ControllerEnvironment ce = - net.java.games.input.ControllerEnvironment.getDefaultEnvironment(); - if (ce.isSupported()) { - net.java.games.input.Controller[] c = - ce.getControllers(); - - System.out.println("Succeeded in loading JInput.\n\tVersion: " + - net.java.games.util.Version.getVersion()); - } - } - - private static void tryLoadOpenAL() { - NativeLibraryLoader.loadNativeLibrary("openal", true); - - try { - org.lwjgl.openal.AL.create(); - String renderer = org.lwjgl.openal.AL10.alGetString(org.lwjgl.openal.AL10.AL_RENDERER); - String vendor = org.lwjgl.openal.AL10.alGetString(org.lwjgl.openal.AL10.AL_VENDOR); - String version = org.lwjgl.openal.AL10.alGetString(org.lwjgl.openal.AL10.AL_VERSION); - System.out.println("Succeeded in loading OpenAL."); - System.out.println("\tVersion: " + version); - } catch (org.lwjgl.LWJGLException ex) { - throw new RuntimeException(ex); - } finally { - if (org.lwjgl.openal.AL.isCreated()) { - org.lwjgl.openal.AL.destroy(); - } - } - } - - private static void tryLoadOpenGL() { - org.lwjgl.opengl.Pbuffer pb = null; - try { - pb = new org.lwjgl.opengl.Pbuffer(1, 1, new org.lwjgl.opengl.PixelFormat(0, 0, 0), null); - pb.makeCurrent(); - String version = org.lwjgl.opengl.GL11.glGetString(org.lwjgl.opengl.GL11.GL_VERSION); - System.out.println("Succeeded in loading OpenGL.\n\tVersion: " + version); - } catch (org.lwjgl.LWJGLException ex) { - throw new RuntimeException(ex); - } finally { - if (pb != null) { - pb.destroy(); - } - } - } - - private static void tryLoadBulletJme() { - if (NativeLibraryLoader.isUsingNativeBullet()) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - - com.jme3.bullet.PhysicsSpace physSpace = new com.jme3.bullet.PhysicsSpace(); - - System.out.println("Succeeded in loading BulletJme."); - } else { - System.out.println("Native bullet not included. Cannot test loading."); - } - } - - private static void cleanupNativesFolder(File folder) { - for (File file : folder.listFiles()) { - String lowerCaseName = file.getName().toLowerCase(); - if (lowerCaseName.contains("lwjgl") || - lowerCaseName.contains("jinput") || - lowerCaseName.contains("openal") || - lowerCaseName.contains("bulletjme")) { - file.delete(); - } - } - } - - public static void main(String[] args) { - Logger.getLogger("").getHandlers()[0].setLevel(Level.WARNING); - Logger.getLogger(NativeLibraryLoader.class.getName()).setLevel(Level.ALL); - - // Get a bit more output from LWJGL about issues. - // System.setProperty("org.lwjgl.util.Debug", "true"); - - // Extracting to working folder is no brainer. - // Choose some random path, then load LWJGL. - File customNativesFolder = new File("CustomNativesFolder"); - customNativesFolder.mkdirs(); - - if (!customNativesFolder.isDirectory()) { - throw new IllegalStateException("Failed to make custom natives folder"); - } - - // Let's cleanup our folders first. - cleanupNativesFolder(WORKING_FOLDER); - cleanupNativesFolder(customNativesFolder); - - NativeLibraryLoader.setCustomExtractionFolder(customNativesFolder.getAbsolutePath()); - - tryLoadLwjgl(); - tryLoadOpenGL(); - tryLoadOpenAL(); - tryLoadJinput(); - tryLoadBulletJme(); - } -} 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/audio/TestOgg.java b/jme3-examples/src/main/java/jme3test/audio/TestOgg.java index 2d2343c11..3e4099c66 100644 --- a/jme3-examples/src/main/java/jme3test/audio/TestOgg.java +++ b/jme3-examples/src/main/java/jme3test/audio/TestOgg.java @@ -33,6 +33,7 @@ package jme3test.audio; import com.jme3.app.SimpleApplication; +import com.jme3.audio.AudioData.DataType; import com.jme3.audio.AudioNode; import com.jme3.audio.AudioSource; import com.jme3.audio.LowPassFilter; @@ -49,7 +50,7 @@ public class TestOgg extends SimpleApplication { @Override public void simpleInitApp(){ System.out.println("Playing without filter"); - audioSource = new AudioNode(assetManager, "Sound/Effects/Foot steps.ogg", true); + audioSource = new AudioNode(assetManager, "Sound/Effects/Foot steps.ogg", DataType.Buffer); audioSource.play(); } @@ -59,7 +60,7 @@ public class TestOgg extends SimpleApplication { audioRenderer.deleteAudioData(audioSource.getAudioData()); System.out.println("Playing with low pass filter"); - audioSource = new AudioNode(assetManager, "Sound/Effects/Foot steps.ogg", true); + audioSource = new AudioNode(assetManager, "Sound/Effects/Foot steps.ogg", DataType.Buffer); audioSource.setDryFilter(new LowPassFilter(1f, .1f)); audioSource.setVolume(3); audioSource.play(); 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/effect/TestEverything.java b/jme3-examples/src/main/java/jme3test/effect/TestEverything.java index 2aca26f5e..be05958c3 100644 --- a/jme3-examples/src/main/java/jme3test/effect/TestEverything.java +++ b/jme3-examples/src/main/java/jme3test/effect/TestEverything.java @@ -123,11 +123,7 @@ public class TestEverything extends SimpleApplication { public void setupFloor(){ Material mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"); - mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat); - mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat); - mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat); - - Box floor = new Box(Vector3f.ZERO, 50, 1f, 50); + Box floor = new Box(50, 1f, 50); TangentBinormalGenerator.generate(floor); floor.scaleTextureCoordinates(new Vector2f(5, 5)); Geometry floorGeom = new Geometry("Floor", floor); diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java index 150b49329..75fac22c1 100644 --- a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java +++ b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java @@ -84,7 +84,7 @@ public class TestPBRLighting extends SimpleApplication { viewPort.setBackgroundColor(ColorRGBA.White); modelNode = (Node) new Node("modelNode"); model = (Geometry) assetManager.loadModel("Models/Tank/tank.j3o"); - //MikktspaceTangentGenerator.generate(model); + MikktspaceTangentGenerator.generate(model); modelNode.attachChild(model); dl = new DirectionalLight(); diff --git a/jme3-examples/src/main/java/jme3test/material/TestMatParamOverride.java b/jme3-examples/src/main/java/jme3test/material/TestMatParamOverride.java new file mode 100644 index 000000000..224290f25 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/material/TestMatParamOverride.java @@ -0,0 +1,92 @@ +/* + * 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 jme3test.material; + +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.MatParamOverride; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.shader.VarType; + +/** + * Test if {@link MatParamOverride}s are working correctly. + * + * @author Kirill Vainer + */ +public class TestMatParamOverride extends SimpleApplication { + + private Box box = new Box(1, 1, 1); + private MatParamOverride override = new MatParamOverride(VarType.Vector4, "Color", ColorRGBA.Yellow); + + public static void main(String[] args) { + TestMatParamOverride app = new TestMatParamOverride(); + app.start(); + } + + private void createBox(float location, ColorRGBA color) { + Geometry geom = new Geometry("Box", box); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", color); + geom.setMaterial(mat); + geom.move(location, 0, 0); + rootNode.attachChild(geom); + } + + @Override + public void simpleInitApp() { + inputManager.setCursorVisible(true); + + createBox(-3, ColorRGBA.Red); + createBox(0, ColorRGBA.Green); + createBox(3, ColorRGBA.Blue); + + inputManager.addMapping("override", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("override") && isPressed) { + if (!rootNode.getLocalMatParamOverrides().isEmpty()) { + rootNode.clearMatParamOverrides(); + } else { + rootNode.addMatParamOverride(override); + } + System.out.println(rootNode.getLocalMatParamOverrides()); + } + } + }, "override"); + } +} diff --git a/jme3-examples/src/main/java/jme3test/material/TestParallax.java b/jme3-examples/src/main/java/jme3test/material/TestParallax.java index f5af57f1f..cf5263ed2 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestParallax.java +++ b/jme3-examples/src/main/java/jme3test/material/TestParallax.java @@ -71,8 +71,7 @@ public class TestParallax extends SimpleApplication { Material mat; public void setupFloor() { - mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall2.j3m"); - //mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"); + mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"); Node floorGeom = new Node("floorGeom"); Quad q = new Quad(100, 100); diff --git a/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java b/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java index db8a21c97..3b8088223 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java +++ b/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java @@ -30,9 +30,9 @@ public class TestShaderNodes extends SimpleApplication { mat.selectTechnique("Default", renderManager); Technique t = mat.getActiveTechnique(); - for (Shader.ShaderSource shaderSource : t.getShader().getSources()) { - System.out.println(shaderSource.getSource()); - } +// for (Shader.ShaderSource shaderSource : t.getShader().getSources()) { +// System.out.println(shaderSource.getSource()); +// } mat.setColor("Color", ColorRGBA.Yellow); diff --git a/jme3-examples/src/main/java/jme3test/post/TestPostFilters.java b/jme3-examples/src/main/java/jme3test/post/TestPostFilters.java index 4ad419668..3ab2ae295 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestPostFilters.java +++ b/jme3-examples/src/main/java/jme3test/post/TestPostFilters.java @@ -107,10 +107,7 @@ public class TestPostFilters extends SimpleApplication implements ActionListener public void setupFloor() { Material mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"); - mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat); - mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat); - mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat); - Box floor = new Box(Vector3f.ZERO, 50, 1f, 50); + Box floor = new Box(50, 1f, 50); TangentBinormalGenerator.generate(floor); floor.scaleTextureCoordinates(new Vector2f(5, 5)); Geometry floorGeom = new Geometry("Floor", floor); diff --git a/jme3-examples/src/main/java/jme3test/post/TestRenderToMemory.java b/jme3-examples/src/main/java/jme3test/post/TestRenderToMemory.java index ad0172511..5ea1336cd 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestRenderToMemory.java +++ b/jme3-examples/src/main/java/jme3test/post/TestRenderToMemory.java @@ -116,7 +116,7 @@ public class TestRenderToMemory extends SimpleApplication implements SceneProces frames ++; t = t2; - if (total > 1000){ + if (total > timer.getResolution()) { fps = frames; total = 0; frames = 0; diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java index 016440bf0..483343f72 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java @@ -237,12 +237,12 @@ public class PhysicsSpace { || (collisionObject1.getCollideWithGroups() & collisionObject.getCollisionGroup()) > 0) { PhysicsCollisionGroupListener listener = collisionGroupListeners.get(collisionObject.getCollisionGroup()); PhysicsCollisionGroupListener listener1 = collisionGroupListeners.get(collisionObject1.getCollisionGroup()); - if (listener != null) { - return listener.collide(collisionObject, collisionObject1); - } else if (listener1 != null) { - return listener1.collide(collisionObject, collisionObject1); + if(listener != null){ + collides = listener.collide(collisionObject, collisionObject1); + } + if(listener1 != null && collisionObject.getCollisionGroup() != collisionObject1.getCollisionGroup()){ + collides = listener1.collide(collisionObject, collisionObject1) && collides; } - return true; } else { return false; } diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java index 3da2ed49e..2ca2fb266 100644 --- a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java +++ b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java @@ -600,4 +600,9 @@ public class JoglGL implements GL, GL2, GL3, GL4 { checkLimit(arrays); GLContext.getCurrentGL().getGL2ES3().glDeleteVertexArrays(arrays.limit(), arrays); } + + @Override + public void glFramebufferTextureLayer(int param1, int param2, int param3, int param4, int param5) { + GLContext.getCurrentGL().getGL3().glFramebufferTextureLayer(param1, param2, param3, param4, param5); + } } diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java index 3b468f1aa..c4f38f970 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java @@ -77,8 +77,6 @@ public abstract class JoglAbstractDisplay extends JoglContext implements GLEvent protected boolean wasAnimating = false; protected void initGLCanvas() { - loadNatives(); - device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); GLCapabilities caps; diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java index 3ed543bc9..24a647ef6 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java @@ -53,8 +53,6 @@ import com.jme3.renderer.opengl.GLTracer; import com.jme3.system.AppSettings; import com.jme3.system.JmeContext; import com.jme3.system.NanoTimer; -import com.jme3.system.NativeLibraryLoader; -import com.jme3.system.NullRenderer; import com.jme3.system.SystemListener; import com.jme3.system.Timer; @@ -86,13 +84,6 @@ public abstract class JoglContext implements JmeContext { protected MouseInput mouseInput; protected JoyInput joyInput; - public void loadNatives() { - // Not sure if need to load OpenAL here ... - if (NativeLibraryLoader.isUsingNativeBullet()) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - } - } - @Override public void setSystemListener(SystemListener listener){ this.listener = listener; diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java index 9c7a49d17..48f191cb3 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java @@ -73,7 +73,6 @@ public abstract class JoglNewtAbstractDisplay extends JoglContext implements GLE protected boolean wasAnimating = false; protected void initGLCanvas() { - loadNatives(); GLCapabilities caps; if (settings.getRenderer().equals(AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE)) { caps = new GLCapabilities(GLProfile.getMaxProgrammable(true)); diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java index 56aa2d53f..54fa3b552 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java @@ -123,8 +123,7 @@ public class JoglOffscreenBuffer extends JoglContext implements Runnable { } @Override - public void run(){ - loadNatives(); + public void run() { logger.log(Level.FINE, "Using JOGL {0}", JoglVersion.getInstance().getImplementationVersion()); initInThread(); while (!needClose.get()){ diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java index 2b7df131a..6f640042f 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java @@ -457,4 +457,9 @@ public final class LwjglGL implements GL, GL2, GL3, GL4 { checkLimit(arrays); ARBVertexArrayObject.glDeleteVertexArrays(arrays); } + + @Override + public void glFramebufferTextureLayer(int param1, int param2, int param3, int param4, int param5) { + GL30.glFramebufferTextureLayer(param1, param2, param3, param4, param5); + } } diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index 2452e470b..0c5392b19 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -301,7 +301,7 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex // crashes on bad drivers if (pbufferFormat == null){ pbufferFormat = new PixelFormat(settings.getBitsPerPixel(), - 0, + settings.getAlphaBits(), settings.getDepthBits(), settings.getStencilBits(), 0, // samples @@ -315,7 +315,7 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex if (canvasFormat == null){ int samples = getNumSamplesToUse(); canvasFormat = new PixelFormat(settings.getBitsPerPixel(), - 0, + settings.getAlphaBits(), settings.getDepthBits(), settings.getStencilBits(), samples, diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index 3f1139886..be3014da5 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -29,6 +29,7 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + package com.jme3.system.lwjgl; import com.jme3.input.lwjgl.JInputJoyInput; @@ -69,7 +70,6 @@ public abstract class LwjglContext implements JmeContext { private static final Logger logger = Logger.getLogger(LwjglContext.class.getName()); protected static final String THREAD_NAME = "jME3 Main"; - protected AtomicBoolean created = new AtomicBoolean(false); protected AtomicBoolean renderable = new AtomicBoolean(false); protected final Object createdLock = new Object(); @@ -113,7 +113,6 @@ public abstract class LwjglContext implements JmeContext { return null; } } - protected int determineMaxSamples(int requestedSamples) { try { // If we already have a valid context, determine samples using current @@ -131,13 +130,11 @@ public abstract class LwjglContext implements JmeContext { } catch (LWJGLException ex) { listener.handleError("Failed to check if display is current", ex); } - if ((Pbuffer.getCapabilities() & Pbuffer.PBUFFER_SUPPORTED) == 0) { // No pbuffer, assume everything is supported. return Integer.MAX_VALUE; } else { Pbuffer pb = null; - // OpenGL2 method: Create pbuffer and query samples // from GL_ARB_framebuffer_object or GL_EXT_framebuffer_multisample. try { @@ -162,7 +159,6 @@ public abstract class LwjglContext implements JmeContext { } } } - protected void loadNatives() { if (JmeSystem.isLowPermissions()) { return; @@ -174,12 +170,8 @@ public abstract class LwjglContext implements JmeContext { NativeLibraryLoader.loadNativeLibrary("jinput", true); NativeLibraryLoader.loadNativeLibrary("jinput-dx8", true); } - if (NativeLibraryLoader.isUsingNativeBullet()) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - } NativeLibraryLoader.loadNativeLibrary("lwjgl", true); } - protected int getNumSamplesToUse() { int samples = 0; if (settings.getSamples() > 1) { @@ -190,7 +182,6 @@ public abstract class LwjglContext implements JmeContext { "Couldn''t satisfy antialiasing samples requirement: x{0}. " + "Video hardware only supports: x{1}", new Object[]{samples, supportedSamples}); - samples = supportedSamples; } } @@ -202,48 +193,43 @@ public abstract class LwjglContext implements JmeContext { throw new RendererException("OpenGL 2.0 or higher is " + "required for jMonkeyEngine"); } - + if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL2) || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) { GL gl = new LwjglGL(); GLExt glext = new LwjglGLExt(); GLFbo glfbo; - + if (GLContext.getCapabilities().OpenGL30) { glfbo = new LwjglGLFboGL3(); } else { glfbo = new LwjglGLFboEXT(); } - + if (settings.getBoolean("GraphicsDebug")) { gl = new GLDebugDesktop(gl, glext, glfbo); glext = (GLExt) gl; glfbo = (GLFbo) gl; } - if (settings.getBoolean("GraphicsTiming")) { GLTimingState timingState = new GLTimingState(); gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class); glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class); glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class); } - if (settings.getBoolean("GraphicsTrace")) { gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class); glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class); glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class); } - renderer = new GLRenderer(gl, glext, glfbo); renderer.initialize(); } else { throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer()); } - if (GLContext.getCapabilities().GL_ARB_debug_output && settings.getBoolean("GraphicsDebug")) { ARBDebugOutput.glDebugMessageCallbackARB(new ARBDebugOutputCallback(new LwjglGLDebugOutputHandler())); } - renderer.setMainFrameBufferSrgb(settings.isGammaCorrection()); renderer.setLinearizeSrgbImages(settings.isGammaCorrection()); @@ -270,15 +256,12 @@ public abstract class LwjglContext implements JmeContext { createdLock.notifyAll(); } } - public void internalCreate() { timer = new LwjglTimer(); - synchronized (createdLock) { created.set(true); createdLock.notifyAll(); } - if (renderable.get()) { initContextFirstTime(); } else { @@ -308,7 +291,6 @@ public abstract class LwjglContext implements JmeContext { public boolean isCreated() { return created.get(); } - public boolean isRenderable() { return renderable.get(); } diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java index 853e02933..71ba74388 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java @@ -84,7 +84,7 @@ public class LwjglDisplay extends LwjglAbstractDisplay { int samples = getNumSamplesToUse(); PixelFormat pf = new PixelFormat(settings.getBitsPerPixel(), - 0, + settings.getAlphaBits(), settings.getDepthBits(), settings.getStencilBits(), samples, @@ -99,6 +99,7 @@ public class LwjglDisplay extends LwjglAbstractDisplay { boolean pixelFormatChanged = false; if (created.get() && (pixelFormat.getBitsPerPixel() != pf.getBitsPerPixel() + ||pixelFormat.getAlphaBits() != pf.getAlphaBits() ||pixelFormat.getDepthBits() != pf.getDepthBits() ||pixelFormat.getStencilBits() != pf.getStencilBits() ||pixelFormat.getSamples() != pf.getSamples())){ diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java index ddfb8b62b..afd2c7508 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java @@ -62,7 +62,7 @@ public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { int samples = getNumSamplesToUse(); pixelFormat = new PixelFormat(settings.getBitsPerPixel(), - 0, + settings.getAlphaBits(), settings.getDepthBits(), settings.getStencilBits(), samples); diff --git a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyInput.java b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyInput.java index cd87cc70a..e0fa70b92 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyInput.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyInput.java @@ -36,13 +36,21 @@ import com.jme3.input.KeyInput; import com.jme3.input.RawInputListener; import com.jme3.input.event.KeyInputEvent; import com.jme3.system.lwjgl.LwjglWindow; + +import org.lwjgl.glfw.GLFWCharCallback; import org.lwjgl.glfw.GLFWKeyCallback; import java.util.LinkedList; import java.util.Queue; import java.util.logging.Logger; -import static org.lwjgl.glfw.GLFW.*; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_LAST; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_SPACE; +import static org.lwjgl.glfw.GLFW.GLFW_PRESS; +import static org.lwjgl.glfw.GLFW.GLFW_REPEAT; +import static org.lwjgl.glfw.GLFW.glfwGetTime; +import static org.lwjgl.glfw.GLFW.glfwSetCharCallback; +import static org.lwjgl.glfw.GLFW.glfwSetKeyCallback; public class GlfwKeyInput implements KeyInput { @@ -52,6 +60,7 @@ public class GlfwKeyInput implements KeyInput { private RawInputListener listener; private boolean initialized; private GLFWKeyCallback keyCallback; + private GLFWCharCallback charCallback; private Queue keyInputEvents = new LinkedList(); public GlfwKeyInput(LwjglWindow context) { @@ -66,14 +75,38 @@ public class GlfwKeyInput implements KeyInput { glfwSetKeyCallback(context.getWindowHandle(), keyCallback = new GLFWKeyCallback() { @Override public void invoke(long window, int key, int scancode, int action, int mods) { + + if (key < 0 || key > GLFW_KEY_LAST) { + return; + } + int jmeKey = GlfwKeyMap.toJmeKeyCode(key); - final KeyInputEvent evt = new KeyInputEvent(jmeKey, (char) key, GLFW_PRESS == action, GLFW_REPEAT == action); - evt.setTime(getInputTimeNanos()); - keyInputEvents.add(evt); + + final KeyInputEvent event = new KeyInputEvent(jmeKey, '\0', GLFW_PRESS == action, GLFW_REPEAT == action); + event.setTime(getInputTimeNanos()); + + keyInputEvents.add(event); } }); - glfwSetInputMode(context.getWindowHandle(), GLFW_STICKY_KEYS, 1); + glfwSetCharCallback(context.getWindowHandle(), charCallback = new GLFWCharCallback() { + + @Override + public void invoke(long window, int codepoint) { + + final char keyChar = (char) codepoint; + + final KeyInputEvent pressed = new KeyInputEvent(KeyInput.KEY_UNKNOWN, keyChar, true, false); + pressed.setTime(getInputTimeNanos()); + + keyInputEvents.add(pressed); + + final KeyInputEvent released = new KeyInputEvent(KeyInput.KEY_UNKNOWN, keyChar, false, false); + released.setTime(getInputTimeNanos()); + + keyInputEvents.add(released); + } + }); initialized = true; logger.fine("Keyboard created."); @@ -100,6 +133,7 @@ public class GlfwKeyInput implements KeyInput { } keyCallback.release(); + charCallback.release(); logger.fine("Keyboard destroyed."); } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java index bf5478cba..17c91aa33 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java @@ -67,6 +67,8 @@ public class GlfwMouseInput implements MouseInput { private static final Logger logger = Logger.getLogger(GlfwMouseInput.class.getName()); + private static final int WHEEL_SCALE = 120; + private LwjglWindow context; private RawInputListener listener; private boolean cursorVisible = true; @@ -136,7 +138,7 @@ public class GlfwMouseInput implements MouseInput { glfwSetScrollCallback(context.getWindowHandle(), scrollCallback = new GLFWScrollCallback() { @Override public void invoke(final long window, final double xOffset, final double yOffset) { - onWheelScroll(window, xOffset, yOffset); + onWheelScroll(window, xOffset, yOffset * WHEEL_SCALE); } }); @@ -213,7 +215,7 @@ public class GlfwMouseInput implements MouseInput { // TODO: currently animated cursors are not supported IntBuffer imageData = jmeCursor.getImagesData(); - ByteBuffer buf = BufferUtils.createByteBuffer(imageData.capacity()); + ByteBuffer buf = BufferUtils.createByteBuffer(imageData.capacity() * 4); buf.asIntBuffer().put(imageData); glfwImage.set(jmeCursor.getWidth(), jmeCursor.getHeight(), buf); diff --git a/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java b/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java index 187b232f2..25de6b148 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java @@ -486,4 +486,9 @@ public class LwjglGL implements GL, GL2, GL3, GL4 { checkLimit(arrays); ARBVertexArrayObject.glDeleteVertexArrays(arrays); } + + @Override + public void glFramebufferTextureLayer(int param1, int param2, int param3, int param4, int param5) { + GL30.glFramebufferTextureLayer(param1, param2, param3, param4, param5); + } } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index 0fe8a7b23..20532df36 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -116,17 +116,6 @@ public abstract class LwjglContext implements JmeContext { return samples; } - protected void loadNatives() { - if (JmeSystem.isLowPermissions()) { - return; - } - - if (NativeLibraryLoader.isUsingNativeBullet()) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - } - } - - protected void initContextFirstTime() { final GLCapabilities capabilities = createCapabilities(settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)); diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java index aaa24ce42..cac8ecdd1 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java @@ -315,8 +315,6 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { }); } - loadNatives(); - timer = new NanoTimer(); // For canvas, this will create a pbuffer, 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 c43c491ec..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 @@ -69,12 +69,24 @@ public class NormalRecalcControl extends AbstractControl { } - @Override + /** + * 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; - } + 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) { @@ -83,7 +95,7 @@ public class NormalRecalcControl extends AbstractControl { 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 f52b1ca89..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 @@ -74,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 { @@ -92,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() { } @@ -111,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) @@ -134,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) { @@ -145,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 @@ -160,7 +160,7 @@ public class TerrainLodControl extends AbstractControl { lodCalculator.turnOffLod(); } } - + if (cameras != null) { cameraLocations.clear(); for (Camera c : cameras) // populate them @@ -170,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. @@ -186,7 +186,7 @@ public class TerrainLodControl extends AbstractControl { if(getSpatial() == null){ return; } - + // update any existing ones that need updating updateQuadLODs(); @@ -196,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 @@ -218,9 +218,9 @@ public class TerrainLodControl extends AbstractControl { if (executor == null) executor = createExecutorService(); - + prepareTerrain(); - + UpdateLOD updateLodThread = getLodThread(locations, lodCalculator); indexer = executor.submit(updateLodThread); } @@ -232,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); } @@ -249,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 @@ -257,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) { @@ -268,7 +268,7 @@ public class TerrainLodControl extends AbstractControl { } } } - + private boolean lastCameraLocationsTheSame(List locations) { boolean theSame = true; for (Vector3f l : locations) { @@ -281,7 +281,7 @@ public class TerrainLodControl extends AbstractControl { } return theSame; } - + protected synchronized boolean isLodCalcRunning() { return lodCalcRunning.get(); } @@ -297,11 +297,11 @@ public class TerrainLodControl extends AbstractControl { return cloned; } - - - - - @Override + + + + + @Override public Object jmeClone() { if (spatial instanceof Terrain) { TerrainLodControl cloned = new TerrainLodControl((Terrain) spatial, cameras); @@ -310,21 +310,23 @@ public class TerrainLodControl extends AbstractControl { return cloned; } return null; - } + } - @Override + @Override public void cloneFields( Cloner cloner, Object original ) { + super.cloneFields(cloner, original); + this.lodCalculator = cloner.clone(lodCalculator); - - try { + + 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) { @@ -346,7 +348,7 @@ public class TerrainLodControl extends AbstractControl { cams.add(camera); setCameras(cams); } - + public void setCameras(List cameras) { this.cameras = cameras; cameraLocations.clear(); @@ -374,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; @@ -386,8 +388,8 @@ public class TerrainLodControl extends AbstractControl { lodCalculator.turnOnLod(); } } - - + + /** * Calculates the LOD of all child terrain patches. */ @@ -408,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 @@ -418,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); @@ -430,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/Textures/Terrain/BrickWall/BrickWall.j3m b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall.j3m index 41af10431..c35ab04c4 100644 --- a/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall.j3m +++ b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall.j3m @@ -1,7 +1,8 @@ Material Pong Rock : Common/MatDefs/Light/Lighting.j3md { MaterialParameters { - Shininess: 2.0 - DiffuseMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg - ParallaxMap : Repeat Textures/Terrain/BrickWall/BrickWall_height.jpg + Shininess : 2.0 + DiffuseMap : Repeat Textures/Terrain/BrickWall/BrickWall.dds + NormalMap : Repeat Textures/Terrain/BrickWall/BrickWall_normal_parallax.dds + PackedNormalParallax : true } } \ No newline at end of file diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall2.j3m b/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall2.j3m deleted file mode 100644 index 8f90a9454..000000000 --- a/jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWall2.j3m +++ /dev/null @@ -1,8 +0,0 @@ -Material Pong Rock : Common/MatDefs/Light/Lighting.j3md { - MaterialParameters { - Shininess: 2.0 - DiffuseMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg - NormalMap : Repeat Textures/Terrain/BrickWall/BrickWall_normal_parallax.dds - PackedNormalParallax: true - } -} \ No newline at end of file