diff --git a/.gitignore b/.gitignore index 5e7951c48..8baaf9fc4 100644 --- a/.gitignore +++ b/.gitignore @@ -146,3 +146,4 @@ /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/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java b/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java index bbbfaca7a..efaf4f3bb 100644 --- a/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java +++ b/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java @@ -122,9 +122,13 @@ public class JmeAndroidSystem extends JmeSystemDelegate { return Platform.Android_ARM6; } else if (arch.contains("v7")) { return Platform.Android_ARM7; + } else if (arch.contains("v8")) { + return Platform.Android_ARM8; } else { return Platform.Android_ARM5; // unknown ARM } + } else if (arch.contains("aarch")) { + return Platform.Android_ARM8; } else { return Platform.Android_Other; } diff --git a/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java b/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java index c412c07b3..3b72088ab 100644 --- a/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java +++ b/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java @@ -230,18 +230,22 @@ public class BlenderKey extends ModelKey { } /** + * Not used any more. * This method sets the asset root path. * @param assetRootPath * the assets root path */ + @Deprecated public void setAssetRootPath(String assetRootPath) { this.assetRootPath = assetRootPath; } /** + * Not used any more. * This method returns the asset root path. * @return the asset root path */ + @Deprecated public String getAssetRootPath() { return assetRootPath; } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java index e11101c14..142f757e2 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java @@ -31,8 +31,6 @@ */ package com.jme3.scene.plugins.blender; -import java.io.File; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -40,25 +38,17 @@ import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; -import com.jme3.animation.Animation; import com.jme3.asset.AssetNotFoundException; import com.jme3.asset.BlenderKey; import com.jme3.export.Savable; -import com.jme3.light.Light; import com.jme3.math.FastMath; import com.jme3.math.Quaternion; -import com.jme3.post.Filter; -import com.jme3.renderer.Camera; -import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.materials.MaterialContext; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; import com.jme3.scene.plugins.blender.objects.Properties; -import com.jme3.texture.Texture; /** * A purpose of the helper class is to split calculation code into several classes. Each helper after use should be cleared because it can @@ -157,7 +147,6 @@ public abstract class AbstractBlenderHelper { * @throws BlenderFileException * and exception is throw when problems with reading a blend file occur */ - @SuppressWarnings("unchecked") protected Object loadLibrary(Structure id) throws BlenderFileException { Pointer pLib = (Pointer) id.getFieldValue("lib"); if (pLib.isNotNull()) { @@ -167,79 +156,21 @@ public abstract class AbstractBlenderHelper { String path = library.getFieldValue("filepath").toString(); if (!blenderContext.getLinkedFeatures().keySet().contains(path)) { - File file = new File(path); - List pathsToCheck = new ArrayList(); - String currentPath = file.getName(); - do { - pathsToCheck.add(currentPath); - file = file.getParentFile(); - if (file != null) { - currentPath = file.getName() + '/' + currentPath; - } - } while (file != null); - Spatial loadedAsset = null; - BlenderKey blenderKey = null; - for (String p : pathsToCheck) { - blenderKey = new BlenderKey(p); - blenderKey.setLoadUnlinkedAssets(true); - try { - loadedAsset = blenderContext.getAssetManager().loadAsset(blenderKey); - break;// break if no exception was thrown - } catch (AssetNotFoundException e) { - LOGGER.log(Level.FINEST, "Cannot locate linked resource at path: {0}.", p); - } + BlenderKey blenderKey = new BlenderKey(path); + blenderKey.setLoadUnlinkedAssets(true); + try { + loadedAsset = blenderContext.getAssetManager().loadAsset(blenderKey); + } catch (AssetNotFoundException e) { + LOGGER.log(Level.FINEST, "Cannot locate linked resource at path: {0}.", path); } - + if (loadedAsset != null) { Map> linkedData = loadedAsset.getUserData("linkedData"); + for (Entry> entry : linkedData.entrySet()) { String linkedDataFilePath = "this".equals(entry.getKey()) ? path : entry.getKey(); - - List scenes = (List) entry.getValue().get("scenes"); - for (Node scene : scenes) { - blenderContext.addLinkedFeature(linkedDataFilePath, "SC" + scene.getName(), scene); - } - List objects = (List) entry.getValue().get("objects"); - for (Node object : objects) { - blenderContext.addLinkedFeature(linkedDataFilePath, "OB" + object.getName(), object); - } - List meshes = (List) entry.getValue().get("meshes"); - for (TemporalMesh mesh : meshes) { - blenderContext.addLinkedFeature(linkedDataFilePath, "ME" + mesh.getName(), mesh); - } - List materials = (List) entry.getValue().get("materials"); - for (MaterialContext materialContext : materials) { - blenderContext.addLinkedFeature(linkedDataFilePath, "MA" + materialContext.getName(), materialContext); - } - List textures = (List) entry.getValue().get("textures"); - for (Texture texture : textures) { - blenderContext.addLinkedFeature(linkedDataFilePath, "TE" + texture.getName(), texture); - } - List images = (List) entry.getValue().get("images"); - for (Texture image : images) { - blenderContext.addLinkedFeature(linkedDataFilePath, "IM" + image.getName(), image); - } - List animations = (List) entry.getValue().get("animations"); - for (Animation animation : animations) { - blenderContext.addLinkedFeature(linkedDataFilePath, "AC" + animation.getName(), animation); - } - List cameras = (List) entry.getValue().get("cameras"); - for (Camera camera : cameras) { - blenderContext.addLinkedFeature(linkedDataFilePath, "CA" + camera.getName(), camera); - } - List lights = (List) entry.getValue().get("lights"); - for (Light light : lights) { - blenderContext.addLinkedFeature(linkedDataFilePath, "LA" + light.getName(), light); - } - Spatial sky = (Spatial) entry.getValue().get("sky"); - if (sky != null) { - blenderContext.addLinkedFeature(linkedDataFilePath, sky.getName(), sky); - } - List filters = (List) entry.getValue().get("filters"); - for (Filter filter : filters) { - blenderContext.addLinkedFeature(linkedDataFilePath, filter.getName(), filter); - } + blenderContext.getLinkedFeatures().put(linkedDataFilePath, entry.getValue()); } } else { LOGGER.log(Level.WARNING, "No features loaded from path: {0}.", path); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java index 6e8042b09..58114cf28 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java @@ -44,8 +44,11 @@ import com.jme3.animation.Bone; import com.jme3.animation.Skeleton; import com.jme3.asset.AssetManager; import com.jme3.asset.BlenderKey; +import com.jme3.light.Light; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; +import com.jme3.post.Filter; +import com.jme3.renderer.Camera; import com.jme3.scene.Node; import com.jme3.scene.plugins.blender.animations.BlenderAction; import com.jme3.scene.plugins.blender.animations.BoneContext; @@ -55,6 +58,8 @@ import com.jme3.scene.plugins.blender.file.DnaBlockData; import com.jme3.scene.plugins.blender.file.FileBlockHeader; import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode; import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.materials.MaterialContext; +import com.jme3.texture.Texture; /** * The class that stores temporary data and manages it during loading the belnd @@ -77,7 +82,7 @@ public class BlenderContext { /** The asset manager. */ private AssetManager assetManager; /** The blocks read from the file. */ - protected List blocks; + protected List blocks = new ArrayList(); /** * A map containing the file block headers. The key is the old memory address. */ @@ -233,6 +238,7 @@ public class BlenderContext { * the block header to store */ public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) { + blocks.add(fileBlockHeader); fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader); List headers = fileBlockHeadersByCode.get(fileBlockHeader.getCode()); if (headers == null) { @@ -242,6 +248,13 @@ public class BlenderContext { headers.add(fileBlockHeader); } + /** + * @return the block headers + */ + public List getBlocks() { + return blocks; + } + /** * This method returns the block header of a given memory address. If the * header is not present then null is returned. @@ -332,22 +345,14 @@ public class BlenderContext { * The method adds linked content to the blender context. * @param blenderFilePath * the path of linked blender file - * @param featureName - * the linked feature name + * @param featureGroup + * the linked feature group (ie. scenes, materials, meshes, etc.) * @param feature * the linked feature */ - public void addLinkedFeature(String blenderFilePath, String featureName, Object feature) { - if (feature != null) { - Map linkedFeatures = this.linkedFeatures.get(blenderFilePath); - if (linkedFeatures == null) { - linkedFeatures = new HashMap(); - this.linkedFeatures.put(blenderFilePath, linkedFeatures); - } - if (!linkedFeatures.containsKey(featureName)) { - linkedFeatures.put(featureName, feature); - } - } + @Deprecated + public void addLinkedFeature(String blenderFilePath, String featureGroup, Object feature) { + // the method is deprecated and empty at the moment } /** @@ -358,9 +363,106 @@ public class BlenderContext { * the feature name we want to get * @return linked feature or null if none was found */ + @SuppressWarnings("unchecked") public Object getLinkedFeature(String blenderFilePath, String featureName) { Map linkedFeatures = this.linkedFeatures.get(blenderFilePath); - return linkedFeatures != null ? linkedFeatures.get(featureName) : null; + if(linkedFeatures != null) { + String namePrefix = (featureName.charAt(0) + "" + featureName.charAt(1)).toUpperCase(); + featureName = featureName.substring(2); + + if("SC".equals(namePrefix)) { + List scenes = (List) linkedFeatures.get("scenes"); + if(scenes != null) { + for(Node scene : scenes) { + if(featureName.equals(scene.getName())) { + return scene; + } + } + } + } else if("OB".equals(namePrefix)) { + List features = (List) linkedFeatures.get("objects"); + if(features != null) { + for(Node feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("ME".equals(namePrefix)) { + List features = (List) linkedFeatures.get("meshes"); + if(features != null) { + for(Node feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("MA".equals(namePrefix)) { + List features = (List) linkedFeatures.get("materials"); + if(features != null) { + for(MaterialContext feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("TX".equals(namePrefix)) { + List features = (List) linkedFeatures.get("textures"); + if(features != null) { + for(Texture feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("IM".equals(namePrefix)) { + List features = (List) linkedFeatures.get("images"); + if(features != null) { + for(Texture feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("AC".equals(namePrefix)) { + List features = (List) linkedFeatures.get("animations"); + if(features != null) { + for(Animation feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("CA".equals(namePrefix)) { + List features = (List) linkedFeatures.get("cameras"); + if(features != null) { + for(Camera feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("LA".equals(namePrefix)) { + List features = (List) linkedFeatures.get("lights"); + if(features != null) { + for(Light feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("FI".equals(featureName)) { + List features = (List) linkedFeatures.get("lights"); + if(features != null) { + for(Filter feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } + } + return null; } /** @@ -659,4 +761,9 @@ public class BlenderContext { public static enum LoadedDataType { STRUCTURE, FEATURE, TEMPORAL_MESH; } + + @Override + public String toString() { + return blenderKey == null ? "BlenderContext [key = null]" : "BlenderContext [ key = " + blenderKey.toString() + " ]"; + } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java index dd0160063..f519cb9b5 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java @@ -31,6 +31,9 @@ */ package com.jme3.scene.plugins.blender; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -41,9 +44,13 @@ import java.util.logging.Logger; import com.jme3.animation.Animation; import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; import com.jme3.asset.AssetLoader; +import com.jme3.asset.AssetLocator; +import com.jme3.asset.AssetManager; import com.jme3.asset.BlenderKey; import com.jme3.asset.ModelKey; +import com.jme3.asset.StreamAssetInfo; import com.jme3.light.Light; import com.jme3.math.ColorRGBA; import com.jme3.post.Filter; @@ -81,22 +88,17 @@ import com.jme3.texture.Texture; public class BlenderLoader implements AssetLoader { private static final Logger LOGGER = Logger.getLogger(BlenderLoader.class.getName()); - /** The blocks read from the file. */ - protected List blocks; - /** The blender context. */ - protected BlenderContext blenderContext; - @Override public Spatial load(AssetInfo assetInfo) throws IOException { try { - this.setup(assetInfo); + BlenderContext blenderContext = this.setup(assetInfo); AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); animationHelper.loadAnimations(); BlenderKey blenderKey = blenderContext.getBlenderKey(); LoadedFeatures loadedFeatures = new LoadedFeatures(); - for (FileBlockHeader block : blocks) { + for (FileBlockHeader block : blenderContext.getBlocks()) { switch (block.getCode()) { case BLOCK_OB00: ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); @@ -181,7 +183,7 @@ public class BlenderLoader implements AssetLoader { LOGGER.fine("Loading scenes and attaching them to the root object."); for (FileBlockHeader sceneBlock : loadedFeatures.sceneBlocks) { - loadedFeatures.scenes.add(this.toScene(sceneBlock.getStructure(blenderContext))); + loadedFeatures.scenes.add(this.toScene(sceneBlock.getStructure(blenderContext), blenderContext)); } LOGGER.fine("Creating the root node of the model and applying loaded nodes of the scene and loaded features to it."); @@ -220,7 +222,7 @@ public class BlenderLoader implements AssetLoader { } catch (Exception e) { throw new IOException("Unexpected importer exception occured: " + e.getLocalizedMessage(), e); } finally { - this.clear(); + this.clear(assetInfo); } } @@ -228,11 +230,12 @@ public class BlenderLoader implements AssetLoader { * This method converts the given structure to a scene node. * @param structure * structure of a scene + * @param blenderContext the blender context * @return scene's node * @throws BlenderFileException * an exception throw when problems with blender file occur */ - private Node toScene(Structure structure) throws BlenderFileException { + private Node toScene(Structure structure, BlenderContext blenderContext) throws BlenderFileException { ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); Node result = new Node(structure.getName()); List base = ((Structure) structure.getFieldValue("base")).evaluateListBase(); @@ -265,7 +268,7 @@ public class BlenderLoader implements AssetLoader { * @throws BlenderFileException * an exception is throw when something wrong happens with blender file */ - protected void setup(AssetInfo assetInfo) throws BlenderFileException { + protected BlenderContext setup(AssetInfo assetInfo) throws BlenderFileException { // registering loaders ModelKey modelKey = (ModelKey) assetInfo.getKey(); BlenderKey blenderKey; @@ -273,16 +276,15 @@ public class BlenderLoader implements AssetLoader { blenderKey = (BlenderKey) modelKey; } else { blenderKey = new BlenderKey(modelKey.getName()); - blenderKey.setAssetRootPath(modelKey.getFolder()); } // opening stream BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream()); // reading blocks - blocks = new ArrayList(); + List blocks = new ArrayList(); FileBlockHeader fileBlock; - blenderContext = new BlenderContext(); + BlenderContext blenderContext = new BlenderContext(); blenderContext.setBlenderVersion(inputStream.getVersionNumber()); blenderContext.setAssetManager(assetInfo.getManager()); blenderContext.setInputStream(inputStream); @@ -317,15 +319,19 @@ public class BlenderLoader implements AssetLoader { if (sceneFileBlock != null) { blenderContext.setSceneStructure(sceneFileBlock.getStructure(blenderContext)); } + + // adding locator for linked content + assetInfo.getManager().registerLocator(assetInfo.getKey().getName(), LinkedContentLocator.class); + + return blenderContext; } /** * The internal data is only needed during loading so make it unreachable so that the GC can release * that memory (which can be quite large amount). */ - protected void clear() { - blenderContext = null; - blocks = null; + protected void clear(AssetInfo assetInfo) { + assetInfo.getManager().unregisterLocator(assetInfo.getKey().getName(), LinkedContentLocator.class); } /** @@ -362,4 +368,50 @@ public class BlenderLoader implements AssetLoader { */ private ColorRGBA backgroundColor = ColorRGBA.Gray; } + + public static class LinkedContentLocator implements AssetLocator { + private File rootFolder; + + @Override + public void setRootPath(String rootPath) { + rootFolder = new File(rootPath); + if(rootFolder.isFile()) { + rootFolder = rootFolder.getParentFile(); + } + } + + @SuppressWarnings("rawtypes") + @Override + public AssetInfo locate(AssetManager manager, AssetKey key) { + if(key instanceof BlenderKey) { + File linkedAbsoluteFile = new File(key.getName()); + if(linkedAbsoluteFile.exists() && linkedAbsoluteFile.isFile()) { + try { + return new StreamAssetInfo(manager, key, new FileInputStream(linkedAbsoluteFile)); + } catch (FileNotFoundException e) { + return null; + } + } + + File linkedFileInCurrentAssetFolder = new File(rootFolder, linkedAbsoluteFile.getName()); + if(linkedFileInCurrentAssetFolder.exists() && linkedFileInCurrentAssetFolder.isFile()) { + try { + return new StreamAssetInfo(manager, key, new FileInputStream(linkedFileInCurrentAssetFolder)); + } catch (FileNotFoundException e) { + return null; + } + } + + File linkedFileInCurrentFolder = new File(".", linkedAbsoluteFile.getName()); + if(linkedFileInCurrentFolder.exists() && linkedFileInCurrentFolder.isFile()) { + try { + return new StreamAssetInfo(manager, key, new FileInputStream(linkedFileInCurrentFolder)); + } catch (FileNotFoundException e) { + return null; + } + } + } + return null; + } + } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java index 8466d5cce..32160c13a 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java @@ -81,6 +81,7 @@ public class LandscapeHelper extends AbstractBlenderHelper { if ((mode & MODE_MIST) != 0) { LOGGER.fine("Loading fog."); result = new FogFilter(); + result.setName("FIfog"); result.setFogColor(this.toBackgroundColor(worldStructure)); } return result; diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java index d4ef0d1bc..da20d3fb4 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java @@ -46,7 +46,9 @@ import com.jme3.scene.plugins.blender.objects.Properties; */ public class TemporalMesh extends Geometry { private static final Logger LOGGER = Logger.getLogger(TemporalMesh.class.getName()); - + /** A minimum weight value. */ + private static final double MINIMUM_BONE_WEIGHT = FastMath.DBL_EPSILON; + /** The blender context. */ protected final BlenderContext blenderContext; @@ -530,7 +532,11 @@ public class TemporalMesh extends Geometry { for (Entry entry : boneIndexes.entrySet()) { if (vertexGroupsForVertex.containsKey(entry.getKey())) { float weight = vertexGroupsForVertex.get(entry.getKey()); - if (weight > 0) {// no need to use such weights + if (weight > MINIMUM_BONE_WEIGHT) { + // only values of weight greater than MINIMUM_BONE_WEIGHT are used + // if all non zero weights were used, and they were samm enough, problems with normalisation would occur + // because adding a very small value to 1.0 will give 1.0 + // so in order to avoid such errors, which can cause severe animation artifacts we need to use some minimum weight value boneBuffersForVertex.put(weight, entry.getValue()); } } diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java index 6f6eb4d76..0342f4c77 100644 --- a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java +++ b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java @@ -737,8 +737,8 @@ public class PhysicsRigidBody extends PhysicsCollisionObject { setKinematic(capsule.readBoolean("kinematic", false)); setRestitution(capsule.readFloat("restitution", 0)); - Vector3f angularFactor = (Vector3f) capsule.readSavable("angularFactor", Vector3f.NAN.clone()); - if(angularFactor == Vector3f.NAN) { + Vector3f angularFactor = (Vector3f) capsule.readSavable("angularFactor", null); + if(angularFactor == null) { setAngularFactor(capsule.readFloat("angularFactor", 1)); } else { setAngularFactor(angularFactor); 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 1edac967c..58dc87550 100644 --- a/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java +++ b/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java @@ -59,6 +59,7 @@ class BitmapTextPage extends Geometry { BitmapTextPage(BitmapFont font, boolean arrayBased, int page) { super("BitmapFont", new Mesh()); + setRequiresUpdates(false); setBatchHint(BatchHint.Never); if (font == null) { throw new IllegalArgumentException("font cannot be null."); 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 5edfa021b..74b14188d 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Node.java +++ b/jme3-core/src/main/java/com/jme3/scene/Node.java @@ -195,7 +195,7 @@ public class Node extends Spatial { void invalidateUpdateList() { updateListValid = false; if ( parent != null ) { - parent.invalidateUpdateList(); + parent.invalidateUpdateList(); } } @@ -570,6 +570,35 @@ public class Node extends Spatial { // 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(); @@ -578,6 +607,7 @@ public class Node extends Spatial { // collideWith without CollisionResults parameter used to avoid allocation when possible if (bv.collideWith(other) == 0) return 0; } + */ for (Spatial child : children.getArray()){ total += child.collideWith(other, results); } diff --git a/jme3-core/src/main/java/com/jme3/system/AppSettings.java b/jme3-core/src/main/java/com/jme3/system/AppSettings.java index c98845e41..2d7071c9c 100644 --- a/jme3-core/src/main/java/com/jme3/system/AppSettings.java +++ b/jme3-core/src/main/java/com/jme3/system/AppSettings.java @@ -112,13 +112,22 @@ public final class AppSettings extends HashMap { public static final String ANDROID_OPENAL_SOFT = "OpenAL_SOFT"; /** - * Use JogAmp's JOGL as the display system + * Use JogAmp's JOGL as the display system, with the OpenGL forward compatible profile *

* N.B: This backend is EXPERIMENTAL * * @see AppSettings#setRenderer(java.lang.String) */ - public static final String JOGL = "JOGL"; + public static final String JOGL_OPENGL_FORWARD_COMPATIBLE = "JOGL_OPENGL_FORWARD_COMPATIBLE"; + + /** + * Use JogAmp's JOGL as the display system with the backward compatible profile + *

+ * N.B: This backend is EXPERIMENTAL + * + * @see AppSettings#setRenderer(java.lang.String) + */ + public static final String JOGL_OPENGL_BACKWARD_COMPATIBLE = "JOGL_OPENGL_BACKWARD_COMPATIBLE"; /** * Use JogAmp's JOAL as the display system diff --git a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java index 150275d46..2134e0a7e 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java @@ -153,6 +153,10 @@ public abstract class JmeSystemDelegate { return false; } else if (arch.equals("universal")) { return false; + } else if (arch.equals("aarch32")) { + return false; + } else if (arch.equals("aarch64")) { + return true; } else if (arch.equals("arm")) { return false; } else { diff --git a/jme3-core/src/main/java/com/jme3/system/Platform.java b/jme3-core/src/main/java/com/jme3/system/Platform.java index 8a30fff0c..f9206d33f 100644 --- a/jme3-core/src/main/java/com/jme3/system/Platform.java +++ b/jme3-core/src/main/java/com/jme3/system/Platform.java @@ -88,6 +88,11 @@ public enum Platform { */ Android_ARM7, + /** + * Android ARM8 + */ + Android_ARM8, + /** * Android x86 */ diff --git a/jme3-core/src/main/java/com/jme3/texture/Image.java b/jme3-core/src/main/java/com/jme3/texture/Image.java index ca9404720..91b80ad3e 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Image.java +++ b/jme3-core/src/main/java/com/jme3/texture/Image.java @@ -1041,6 +1041,7 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { capsule.write(mipMapSizes, "mipMapSizes", null); capsule.write(multiSamples, "multiSamples", 1); capsule.writeByteBufferArrayList(data, "data", null); + capsule.write(colorSpace, "colorSpace", null); } public void read(JmeImporter e) throws IOException { @@ -1052,6 +1053,7 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { mipMapSizes = capsule.readIntArray("mipMapSizes", null); multiSamples = capsule.readInt("multiSamples", 1); data = (ArrayList) capsule.readByteBufferArrayList("data", null); + colorSpace = capsule.readEnum("colorSpace", ColorSpace.class, null); if (mipMapSizes != null) { needGeneratedMips = false; diff --git a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.frag b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.frag index 619682ce0..4a4b433de 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.frag @@ -1,3 +1,5 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + #ifdef TEXTURE uniform sampler2D m_Texture; varying vec2 texCoord; diff --git a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md index f37f6c979..23fbff826 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md @@ -7,8 +7,8 @@ MaterialDef Default GUI { } Technique { - VertexShader GLSL100: Common/MatDefs/Gui/Gui.vert - FragmentShader GLSL100: Common/MatDefs/Gui/Gui.frag + VertexShader GLSL150: Common/MatDefs/Gui/Gui.vert + FragmentShader GLSL150: Common/MatDefs/Gui/Gui.frag WorldParameters { WorldViewProjectionMatrix @@ -21,6 +21,17 @@ MaterialDef Default GUI { } Technique { + VertexShader GLSL100: Common/MatDefs/Gui/Gui.vert + FragmentShader GLSL100: Common/MatDefs/Gui/Gui.frag + + WorldParameters { + WorldViewProjectionMatrix + } + + Defines { + TEXTURE : Texture + VERTEX_COLOR : VertexColor + } } } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.vert b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.vert index 7640573dc..f0c50f5be 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.vert @@ -1,3 +1,5 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + uniform mat4 g_WorldViewProjectionMatrix; uniform vec4 m_Color; diff --git a/jme3-core/src/main/resources/joystick-mapping.properties b/jme3-core/src/main/resources/joystick-mapping.properties index 966832036..b4f1b77bd 100644 --- a/jme3-core/src/main/resources/joystick-mapping.properties +++ b/jme3-core/src/main/resources/joystick-mapping.properties @@ -69,3 +69,47 @@ Microsoft\ PC-joystick\ driver.12=POV +Y Microsoft\ PC-joystick\ driver.13=POV +X Microsoft\ PC-joystick\ driver.14=POV -Y Microsoft\ PC-joystick\ driver.15=POV -X + +# Logitech F310 gamepad with dip switch DirectInput +Logitech\ Dual\ Action.1=2 +Logitech\ Dual\ Action.2=1 +Logitech\ Dual\ Action.3=0 +Logitech\ Dual\ Action.0=3 + +# Logitech F310 gamepad with dip switch XInput +Gamepad\ F310\ (Controller).0=2 +Gamepad\ F310\ (Controller).1=1 +Gamepad\ F310\ (Controller).2=3 +Gamepad\ F310\ (Controller).3=0 + +Gamepad\ F310\ (Controller).6=8 +Gamepad\ F310\ (Controller).7=9 + +Gamepad\ F310\ (Controller).8=10 +Gamepad\ F310\ (Controller).9=11 + +Gamepad\ F310\ (Controller).rx=z +Gamepad\ F310\ (Controller).ry=rz + +# requires custom code to support trigger buttons but this +# keeps it from confusing the .rx mapping. +Gamepad\ F310\ (Controller).z=trigger + +# Alternate version of the XBOX 360 controller +XBOX\ 360\ For\ Windows\ (Controller).0=2 +XBOX\ 360\ For\ Windows\ (Controller).1=1 +XBOX\ 360\ For\ Windows\ (Controller).2=3 +XBOX\ 360\ For\ Windows\ (Controller).3=0 + +XBOX\ 360\ For\ Windows\ (Controller).6=8 +XBOX\ 360\ For\ Windows\ (Controller).7=9 + +XBOX\ 360\ For\ Windows\ (Controller).8=10 +XBOX\ 360\ For\ Windows\ (Controller).9=11 + +XBOX\ 360\ For\ Windows\ (Controller).rx=z +XBOX\ 360\ For\ Windows\ (Controller).ry=rz + +# requires custom code to support trigger buttons but this +# keeps it from confusing the .rx mapping. +XBOX\ 360\ For\ Windows\ (Controller).z=trigger diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java index 611e208d4..315d71955 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java @@ -308,8 +308,8 @@ public class HDRLoader implements AssetLoader { } in.close(); - dataStore.rewind(); - //TODO, HDR color space? considered linear here + dataStore.rewind(); + //HDR files color data is actually stored in linear space. return new Image(pixelFormat, width, height, dataStore, ColorSpace.Linear); } diff --git a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java index 32bd681c5..707937e78 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java +++ b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java @@ -149,7 +149,7 @@ public final class NativeLibraryLoader { registerNativeLibrary("glfw-lwjgl3", Platform.Windows32, "native/windows/glfw32.dll"); registerNativeLibrary("glfw-lwjgl3", Platform.Windows64, "native/windows/glfw.dll"); registerNativeLibrary("glfw-lwjgl3", Platform.Linux32, "native/linux/libglfw32.so"); - registerNativeLibrary("glfw-lwjgl3", Platform.Linux64, "native/linux/libglfw.dll"); + registerNativeLibrary("glfw-lwjgl3", Platform.Linux64, "native/linux/libglfw.so"); registerNativeLibrary("glfw-lwjgl3", Platform.MacOSX32, "native/macosx/libglfw.dylib"); registerNativeLibrary("glfw-lwjgl3", Platform.MacOSX64, "native/macosx/libglfw.dylib"); diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java index fddb047c4..a36853913 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java @@ -32,6 +32,8 @@ package jme3test.network; import com.jme3.network.Client; +import com.jme3.network.ClientStateListener; +import com.jme3.network.ErrorListener; import com.jme3.network.Message; import com.jme3.network.MessageListener; import com.jme3.network.Network; @@ -51,11 +53,11 @@ import jme3test.network.TestChatServer.ChatMessage; */ public class TestChatClient extends JFrame { - private Client client; - private JEditorPane chatLog; - private StringBuilder chatMessages = new StringBuilder(); - private JTextField nameField; - private JTextField messageField; + private final Client client; + private final JEditorPane chatLog; + private final StringBuilder chatMessages = new StringBuilder(); + private final JTextField nameField; + private final JTextField messageField; public TestChatClient(String host) throws IOException { super("jME3 Test Chat Client - to:" + host); @@ -90,7 +92,20 @@ public class TestChatClient extends JFrame { client = Network.connectToServer(TestChatServer.NAME, TestChatServer.VERSION, host, TestChatServer.PORT, TestChatServer.UDP_PORT); client.addMessageListener(new ChatHandler(), ChatMessage.class); + client.addClientStateListener(new ChatClientStateListener()); + client.addErrorListener(new ChatErrorListener()); client.start(); + + System.out.println("Started client:" + client); + } + + @Override + public void dispose() { + System.out.println("Chat window closing."); + super.dispose(); + if( client.isConnected() ) { + client.close(); + } } public static String getString(Component owner, String title, String message, String initialValue) { @@ -99,7 +114,12 @@ public class TestChatClient extends JFrame { } public static void main(String... args) throws Exception { - TestChatServer.initializeClasses(); + + // Note: in JME 3.1 this is generally unnecessary as the server will + // send a message with all server-registered classes. + // TestChatServer.initializeClasses(); + // Leaving the call commented out to be illustrative regarding the + // common old pattern. // Grab a host string from the user String s = getString(null, "Host Info", "Enter chat host:", "localhost"); @@ -108,12 +128,23 @@ public class TestChatClient extends JFrame { return; } + // Register a shutdown hook to get a message on the console when the + // app actually finishes + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + System.out.println("Chat client is terminating."); + } + }); + + TestChatClient test = new TestChatClient(s); test.setVisible(true); } private class ChatHandler implements MessageListener { + @Override public void messageReceived(Client source, Message m) { ChatMessage chat = (ChatMessage) m; @@ -134,15 +165,50 @@ public class TestChatClient extends JFrame { } } + private class ChatClientStateListener implements ClientStateListener { + + @Override + public void clientConnected(Client c) { + System.out.println("clientConnected(" + c + ")"); + } + + @Override + public void clientDisconnected(Client c, DisconnectInfo info) { + System.out.println("clientDisconnected(" + c + "):" + info); + if( info != null ) { + // The connection was closed by the server + JOptionPane.showMessageDialog(rootPane, + info.reason, + "Connection Closed", + JOptionPane.INFORMATION_MESSAGE); + dispose(); + } + } + } + + private class ChatErrorListener implements ErrorListener { + + @Override + public void handleError( Client source, Throwable t ) { + System.out.println("handleError(" + source + ", " + t + ")"); + JOptionPane.showMessageDialog(rootPane, + String.valueOf(t), + "Connection Error", + JOptionPane.ERROR_MESSAGE); + } + + } + private class SendAction extends AbstractAction { - private boolean reliable; + private final boolean reliable; public SendAction(boolean reliable) { super(reliable ? "TCP" : "UDP"); this.reliable = reliable; } + @Override public void actionPerformed(ActionEvent evt) { String name = nameField.getText(); String message = messageField.getText(); diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatClientAndServer.java b/jme3-examples/src/main/java/jme3test/network/TestChatClientAndServer.java new file mode 100644 index 000000000..ee08067ae --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/network/TestChatClientAndServer.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 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 jme3test.network; + + +/** + * Combines the server instance and a client instance into the + * same JVM to show an example of, and to test, a pattern like + * self-hosted multiplayer games. + * + * @author Paul Speed + */ +public class TestChatClientAndServer { + + public static void main( String... args ) throws Exception { + + System.out.println("Starting chat server..."); + TestChatServer chatServer = new TestChatServer(); + chatServer.start(); + + System.out.println("Waiting for connections on port:" + TestChatServer.PORT); + + // Now launch a client + + TestChatClient test = new TestChatClient("localhost"); + test.setVisible(true); + + // Register a shutdown hook to get a message on the console when the + // app actually finishes + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + System.out.println("Client and server test is terminating."); + } + }); + + // Keep running basically forever or until the server + // shuts down + while( chatServer.isRunning() ) { + synchronized (chatServer) { + chatServer.wait(); + } + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java index a2aa2ee1c..4d8642881 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java @@ -34,6 +34,7 @@ package jme3test.network; import com.jme3.network.*; import com.jme3.network.serializing.Serializable; import com.jme3.network.serializing.Serializer; +import java.io.IOException; /** * A simple test chat server. When SM implements a set @@ -51,51 +52,134 @@ public class TestChatServer { public static final int PORT = 5110; public static final int UDP_PORT = 5110; - public static void initializeClasses() { - // Doing it here means that the client code only needs to - // call our initialize. - Serializer.registerClass(ChatMessage.class); - } - - public static void main(String... args) throws Exception { + private Server server; + private boolean isRunning; + + public TestChatServer() throws IOException { initializeClasses(); // Use this to test the client/server name version check - Server server = Network.createServer(NAME, VERSION, PORT, UDP_PORT); - server.start(); + this.server = Network.createServer(NAME, VERSION, PORT, UDP_PORT); ChatHandler handler = new ChatHandler(); server.addMessageListener(handler, ChatMessage.class); + + server.addConnectionListener(new ChatConnectionListener()); + } + + public boolean isRunning() { + return isRunning; + } + + public synchronized void start() { + if( isRunning ) { + return; + } + server.start(); + isRunning = true; + } + + public synchronized void close() { + if( !isRunning ) { + return; + } + + // Gracefully let any connections know that the server is + // going down. Without this, their connections will simply + // error out. + for( HostedConnection conn : server.getConnections() ) { + conn.close("Server is shutting down."); + } + try { + Thread.sleep(1000); // wait a couple beats to let the messages go out + } catch( InterruptedException e ) { + e.printStackTrace(); + } + + server.close(); + isRunning = false; + notifyAll(); + } + + protected void runCommand( HostedConnection conn, String user, String command ) { + if( "/shutdown".equals(command) ) { + server.broadcast(new ChatMessage("server", "Server is shutting down.")); + close(); + } else if( "/help".equals(command) ) { + StringBuilder sb = new StringBuilder(); + sb.append("Chat commands:\n"); + sb.append("/help - prints this message.\n"); + sb.append("/shutdown - shuts down the server."); + server.broadcast(new ChatMessage("server", sb.toString())); + } + } + + public static void initializeClasses() { + // Doing it here means that the client code only needs to + // call our initialize. + Serializer.registerClass(ChatMessage.class); + } + public static void main(String... args) throws Exception { + + TestChatServer chatServer = new TestChatServer(); + chatServer.start(); + + System.out.println("Waiting for connections on port:" + PORT); + // Keep running basically forever - synchronized (NAME) { - NAME.wait(); + while( chatServer.isRunning ) { + synchronized (chatServer) { + chatServer.wait(); + } } } - private static class ChatHandler implements MessageListener { + private class ChatHandler implements MessageListener { public ChatHandler() { } + @Override public void messageReceived(HostedConnection source, Message m) { if (m instanceof ChatMessage) { // Keep track of the name just in case we // want to know it for some other reason later and it's // a good example of session data - source.setAttribute("name", ((ChatMessage) m).getName()); + ChatMessage cm = (ChatMessage)m; + source.setAttribute("name", cm.getName()); + + // Check for a / command + if( cm.message.startsWith("/") ) { + runCommand(source, cm.name, cm.message); + return; + } System.out.println("Broadcasting:" + m + " reliable:" + m.isReliable()); // Just rebroadcast... the reliable flag will stay the // same so if it came in on UDP it will go out on that too - source.getServer().broadcast(m); + source.getServer().broadcast(cm); } else { System.err.println("Received odd message:" + m); } } } + private class ChatConnectionListener implements ConnectionListener { + + @Override + public void connectionAdded( Server server, HostedConnection conn ) { + System.out.println("connectionAdded(" + conn + ")"); + } + + @Override + public void connectionRemoved(Server server, HostedConnection conn) { + System.out.println("connectionRemoved(" + conn + ")"); + } + + } + @Serializable public static class ChatMessage extends AbstractMessage { @@ -126,6 +210,7 @@ public class TestChatServer { return message; } + @Override public String toString() { return name + ":" + message; } diff --git a/jme3-jogl/build.gradle b/jme3-jogl/build.gradle index e82139729..df24c15ec 100644 --- a/jme3-jogl/build.gradle +++ b/jme3-jogl/build.gradle @@ -5,7 +5,7 @@ if (!hasProperty('mainClass')) { dependencies { compile project(':jme3-core') compile project(':jme3-desktop') - compile 'org.jogamp.gluegen:gluegen-rt-main:2.3.1' - compile 'org.jogamp.jogl:jogl-all-main:2.3.1' - compile 'org.jogamp.joal:joal-main:2.3.1' + compile 'org.jogamp.gluegen:gluegen-rt-main:2.3.2' + compile 'org.jogamp.jogl:jogl-all-main:2.3.2' + compile 'org.jogamp.joal:joal-main:2.3.2' } diff --git a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java index 773f62b8a..d7dd905fd 100644 --- a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java +++ b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java @@ -50,6 +50,8 @@ import com.jogamp.nativewindow.util.DimensionImmutable; import com.jogamp.nativewindow.util.PixelFormat; import com.jogamp.nativewindow.util.PixelRectangle; import com.jogamp.nativewindow.util.Point; +import com.jogamp.newt.event.WindowAdapter; +import com.jogamp.newt.event.WindowEvent; public class NewtMouseInput implements MouseInput, MouseListener { @@ -106,40 +108,67 @@ public class NewtMouseInput implements MouseInput, MouseListener { component = comp; component.addMouseListener(this); + component.addWindowListener(new WindowAdapter(){ + + @Override + public void windowGainedFocus(WindowEvent e) { + setCursorVisible(visible); + } + + @Override + public void windowLostFocus(WindowEvent e) { + //without those lines, + //on Linux (OpenBox) the mouse is not restored if invisible (eg via Alt-Tab) + component.setPointerVisible(true); + component.confinePointer(false); + } + + }); } + @Override public void initialize() { } + @Override public void destroy() { } + @Override public boolean isInitialized() { return true; } + @Override public void setInputListener(RawInputListener listener) { this.listener = listener; } + @Override public long getInputTimeNanos() { return System.nanoTime(); } + @Override public void setCursorVisible(boolean visible) { - if (this.visible != visible) { - lastKnownLocation.setX(0); - lastKnownLocation.setY(0); - - this.visible = visible; - component.setPointerVisible(visible); - if (!visible) { - recenterMouse(component); - } - } + lastKnownLocation.setX(0); + lastKnownLocation.setY(0); + + this.visible = visible; + component.setPointerVisible(visible); + component.confinePointer(!visible); + hack_confinePointer(); } + private void hack_confinePointer() { + if (component.hasFocus() && component.isPointerConfined() && !component.isPointerVisible()) { + recenterMouse(component); + } + } + + @Override public void update() { + if (!component.hasFocus()) return; if (cursorMoved) { int newX = location.getX(); int newY = location.getY(); @@ -147,7 +176,7 @@ public class NewtMouseInput implements MouseInput, MouseListener { // invert DY int actualX = lastKnownLocation.getX(); - int actualY = component.getHeight() - lastKnownLocation.getY(); + int actualY = component.getSurfaceHeight() - lastKnownLocation.getY(); MouseMotionEvent evt = new MouseMotionEvent(actualX, actualY, newX - lastEventX, lastEventY - newY, @@ -173,43 +202,46 @@ public class NewtMouseInput implements MouseInput, MouseListener { } } + @Override public int getButtonCount() { return 3; } + @Override public void mouseClicked(MouseEvent awtEvt) { // MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), false); // listener.onMouseButtonEvent(evt); } + @Override public void mousePressed(MouseEvent newtEvt) { - MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(newtEvt), true, newtEvt.getX(), newtEvt.getY()); + MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(newtEvt), true, newtEvt.getX(), component.getSurfaceHeight() - newtEvt.getY()); evt.setTime(newtEvt.getWhen()); synchronized (eventQueue) { eventQueue.add(evt); } } - public void mouseReleased(MouseEvent awtEvt) { - MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(awtEvt), false, awtEvt.getX(), awtEvt.getY()); + @Override + public void mouseReleased(MouseEvent awtEvt) { + MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(awtEvt), false, awtEvt.getX(), component.getSurfaceHeight() - awtEvt.getY()); evt.setTime(awtEvt.getWhen()); synchronized (eventQueue) { eventQueue.add(evt); } } + @Override public void mouseEntered(MouseEvent awtEvt) { - if (!visible) { - recenterMouse(component); - } + hack_confinePointer(); } + @Override public void mouseExited(MouseEvent awtEvt) { - if (!visible) { - recenterMouse(component); - } + hack_confinePointer(); } + @Override public void mouseWheelMoved(MouseEvent awtEvt) { //FIXME not sure this is the right way to handle this case // [0] should be used when the shift key is down @@ -218,10 +250,12 @@ public class NewtMouseInput implements MouseInput, MouseListener { cursorMoved = true; } + @Override public void mouseDragged(MouseEvent awtEvt) { mouseMoved(awtEvt); } + @Override public void mouseMoved(MouseEvent awtEvt) { if (isRecentering) { // MHenze (cylab) Fix Issue 35: @@ -239,22 +273,20 @@ public class NewtMouseInput implements MouseInput, MouseListener { int dy = awtEvt.getY() - lastKnownLocation.getY(); location.setX(location.getX() + dx); location.setY(location.getY() + dy); - if (!visible) { - recenterMouse(component); - } + hack_confinePointer(); lastKnownLocation.setX(awtEvt.getX()); lastKnownLocation.setY(awtEvt.getY()); cursorMoved = true; } } - + // MHenze (cylab) Fix Issue 35: A method to generate recenter the mouse to allow the InputSystem to "grab" the mouse private void recenterMouse(final GLWindow component) { eventsSinceRecenter = 0; isRecentering = true; - centerLocation.setX(component.getWidth() / 2); - centerLocation.setY(component.getHeight() / 2); + centerLocation.setX(component.getSurfaceWidth() / 2); + centerLocation.setY(component.getSurfaceHeight() / 2); centerLocationOnScreen.setX(centerLocation.getX()); centerLocationOnScreen.setY(centerLocation.getY()); @@ -287,12 +319,13 @@ public class NewtMouseInput implements MouseInput, MouseListener { return index; } + @Override public void setNativeCursor(JmeCursor cursor) { final ByteBuffer pixels = Buffers.copyIntBufferAsByteBuffer(cursor.getImagesData()); final DimensionImmutable size = new Dimension(cursor.getWidth(), cursor.getHeight()); final PixelFormat pixFormat = PixelFormat.RGBA8888; final PixelRectangle.GenericPixelRect rec = new PixelRectangle.GenericPixelRect(pixFormat, size, 0, true, pixels); - final PointerIcon joglCursor = component.getScreen().getDisplay().createPointerIcon(rec, cursor.getXHotSpot(), cursor.getYHotSpot()); + final PointerIcon joglCursor = component.getScreen().getDisplay().createPointerIcon(rec, cursor.getXHotSpot(), cursor.getHeight() - cursor.getYHotSpot()); component.setPointerIcon(joglCursor); } -} \ No newline at end of file +} 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 e1c2a2505..3da2ed49e 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 @@ -532,6 +532,15 @@ public class JoglGL implements GL, GL2, GL3, GL4 { @Override public void glShaderSource(int param1, String[] param2, IntBuffer param3) { checkLimit(param3); + + int param3pos = param3.position(); + try { + for (final String param2string : param2) { + param3.put(Math.max(param2string.length(), param2string.getBytes().length)); + } + } finally { + param3.position(param3pos); + } GLContext.getCurrentGL().getGL2ES2().glShaderSource(param1, param2.length, param2, param3); } 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 7b3f2e655..3b468f1aa 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 @@ -37,6 +37,7 @@ import com.jme3.input.MouseInput; import com.jme3.input.TouchInput; import com.jme3.input.awt.AwtKeyInput; import com.jme3.input.awt.AwtMouseInput; +import com.jme3.system.AppSettings; import com.jogamp.opengl.util.Animator; import com.jogamp.opengl.util.AnimatorBase; import com.jogamp.opengl.util.FPSAnimator; @@ -80,9 +81,13 @@ public abstract class JoglAbstractDisplay extends JoglContext implements GLEvent device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - //FIXME use the settings to know whether to use the max programmable profile - //then call GLProfile.getMaxProgrammable(true); - GLCapabilities caps = new GLCapabilities(GLProfile.getMaxFixedFunc(true)); + GLCapabilities caps; + if (settings.getRenderer().equals(AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE)) { + caps = new GLCapabilities(GLProfile.getMaxProgrammable(true)); + } else { + caps = new GLCapabilities(GLProfile.getMaxFixedFunc(true)); + } + caps.setHardwareAccelerated(true); caps.setDoubleBuffered(true); caps.setStencilBits(settings.getStencilBits()); 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 25ad4fc8e..497fa8edb 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 @@ -167,7 +167,7 @@ public abstract class JoglContext implements JmeContext { "required for jMonkeyEngine"); } - if (settings.getRenderer().equals("JOGL")) { + if (settings.getRenderer().startsWith("JOGL")) { com.jme3.renderer.opengl.GL gl = new JoglGL(); GLExt glext = new JoglGLExt(); GLFbo glfbo = new JoglGLFbo(); 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 4ac6f5c49..9c7a49d17 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 @@ -37,6 +37,7 @@ import com.jme3.input.MouseInput; import com.jme3.input.TouchInput; import com.jme3.input.jogl.NewtKeyInput; import com.jme3.input.jogl.NewtMouseInput; +import com.jme3.system.AppSettings; import com.jogamp.newt.opengl.GLWindow; import com.jogamp.opengl.util.Animator; import com.jogamp.opengl.util.AnimatorBase; @@ -73,10 +74,12 @@ public abstract class JoglNewtAbstractDisplay extends JoglContext implements GLE protected void initGLCanvas() { loadNatives(); - //FIXME use the settings to know whether to use the max programmable profile - //then call GLProfile.getMaxProgrammable(true); - //FIXME use the default profile only on embedded devices - GLCapabilities caps = new GLCapabilities(GLProfile.getDefault()); + GLCapabilities caps; + if (settings.getRenderer().equals(AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE)) { + caps = new GLCapabilities(GLProfile.getMaxProgrammable(true)); + } else { + caps = new GLCapabilities(GLProfile.getMaxFixedFunc(true)); + } caps.setHardwareAccelerated(true); caps.setDoubleBuffered(true); caps.setStencilBits(settings.getStencilBits()); 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 cd2b84f73..56aa2d53f 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 @@ -37,16 +37,18 @@ import com.jme3.input.MouseInput; import com.jme3.input.TouchInput; import com.jme3.input.dummy.DummyKeyInput; import com.jme3.input.dummy.DummyMouseInput; -import com.jogamp.newt.NewtVersion; + import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; + import com.jogamp.opengl.GL; import com.jogamp.opengl.GLCapabilities; import com.jogamp.opengl.GLContext; import com.jogamp.opengl.GLDrawableFactory; import com.jogamp.opengl.GLOffscreenAutoDrawable; import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.JoglVersion; public class JoglOffscreenBuffer extends JoglContext implements Runnable { @@ -123,7 +125,7 @@ public class JoglOffscreenBuffer extends JoglContext implements Runnable { @Override public void run(){ loadNatives(); - logger.log(Level.FINE, "Using JOGL {0}", NewtVersion.getInstance().getImplementationVersion()); + logger.log(Level.FINE, "Using JOGL {0}", JoglVersion.getInstance().getImplementationVersion()); initInThread(); while (!needClose.get()){ runLoop(); diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java index b297a933b..9881830a8 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java @@ -67,18 +67,18 @@ public class DefaultClient implements Client private static final int CH_UNRELIABLE = 1; private static final int CH_FIRST = 2; - private ThreadLocal dataBuffer = new ThreadLocal(); + private final ThreadLocal dataBuffer = new ThreadLocal(); private int id = -1; private boolean isRunning = false; - private CountDownLatch connecting = new CountDownLatch(1); + private final CountDownLatch connecting = new CountDownLatch(1); private String gameName; private int version; - private MessageListenerRegistry messageListeners = new MessageListenerRegistry(); - private List stateListeners = new CopyOnWriteArrayList(); - private List> errorListeners = new CopyOnWriteArrayList>(); - private Redispatch dispatcher = new Redispatch(); - private List channels = new ArrayList(); + private final MessageListenerRegistry messageListeners = new MessageListenerRegistry(); + private final List stateListeners = new CopyOnWriteArrayList(); + private final List> errorListeners = new CopyOnWriteArrayList>(); + private final Redispatch dispatcher = new Redispatch(); + private final List channels = new ArrayList(); private ConnectorFactory connectorFactory; @@ -100,6 +100,7 @@ public class DefaultClient implements Client } protected void addStandardServices() { + log.fine("Adding standard services..."); services.addService(new ClientSerializerRegistrationsService()); } @@ -128,6 +129,7 @@ public class DefaultClient implements Client throw new IllegalStateException( "Client is not started." ); } + @Override public void start() { if( isRunning ) @@ -179,6 +181,7 @@ public class DefaultClient implements Client } } + @Override public boolean isStarted() { return isRunning; } @@ -195,33 +198,42 @@ public class DefaultClient implements Client } } + @Override public boolean isConnected() { return id != -1 && isRunning; } + @Override public int getId() { return id; } + @Override public String getGameName() { return gameName; } + @Override public int getVersion() { return version; } + @Override public ClientServiceManager getServices() { return services; } + @Override public void send( Message message ) { + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "send({0})", message); + } if( message.isReliable() || channels.get(CH_UNRELIABLE) == null ) { send(CH_RELIABLE, message, true); } else { @@ -229,8 +241,12 @@ public class DefaultClient implements Client } } + @Override public void send( int channel, Message message ) { + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "send({0}, {1})", new Object[]{channel, message}); + } if( channel >= 0 ) { // Make sure we aren't still connecting. Channels // won't be valid until we are fully connected since @@ -275,6 +291,7 @@ public class DefaultClient implements Client channels.get(channel).write(buffer); } + @Override public void close() { checkRunning(); @@ -287,9 +304,11 @@ public class DefaultClient implements Client if( !isRunning ) return; - // Let the services get a chance to stop before we - // kill the connection. - services.stop(); + if( services.isStarted() ) { + // Let the services get a chance to stop before we + // kill the connection. + services.stop(); + } // Send a close message @@ -313,41 +332,49 @@ public class DefaultClient implements Client services.terminate(); } + @Override public void addClientStateListener( ClientStateListener listener ) { stateListeners.add( listener ); } + @Override public void removeClientStateListener( ClientStateListener listener ) { stateListeners.remove( listener ); } + @Override public void addMessageListener( MessageListener listener ) { messageListeners.addMessageListener( listener ); } + @Override public void addMessageListener( MessageListener listener, Class... classes ) { messageListeners.addMessageListener( listener, classes ); } + @Override public void removeMessageListener( MessageListener listener ) { messageListeners.removeMessageListener( listener ); } + @Override public void removeMessageListener( MessageListener listener, Class... classes ) { messageListeners.removeMessageListener( listener, classes ); } + @Override public void addErrorListener( ErrorListener listener ) { errorListeners.add( listener ); } + @Override public void removeErrorListener( ErrorListener listener ) { errorListeners.remove( listener ); @@ -461,11 +488,13 @@ public class DefaultClient implements Client protected class Redispatch implements MessageListener, ErrorListener { + @Override public void messageReceived( Object source, Message m ) { dispatch( m ); } + @Override public void handleError( Object source, Throwable t ) { // Only doing the DefaultClient.this to make the code diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java index 2b9add2ef..5c62733e8 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java @@ -66,26 +66,26 @@ public class DefaultServer implements Server private static final int CH_FIRST = 2; private boolean isRunning = false; - private AtomicInteger nextId = new AtomicInteger(0); + private final AtomicInteger nextId = new AtomicInteger(0); private String gameName; private int version; - private KernelFactory kernelFactory = KernelFactory.DEFAULT; + private final KernelFactory kernelFactory = KernelFactory.DEFAULT; private KernelAdapter reliableAdapter; private KernelAdapter fastAdapter; - private List channels = new ArrayList(); - private List alternatePorts = new ArrayList(); - private Redispatch dispatcher = new Redispatch(); - private Map connections = new ConcurrentHashMap(); - private Map endpointConnections + private final List channels = new ArrayList(); + private final List alternatePorts = new ArrayList(); + private final Redispatch dispatcher = new Redispatch(); + private final Map connections = new ConcurrentHashMap(); + private final Map endpointConnections = new ConcurrentHashMap(); // Keeps track of clients for whom we've only received the UDP // registration message - private Map connecting = new ConcurrentHashMap(); + private final Map connecting = new ConcurrentHashMap(); - private MessageListenerRegistry messageListeners + private final MessageListenerRegistry messageListeners = new MessageListenerRegistry(); - private List connectionListeners = new CopyOnWriteArrayList(); + private final List connectionListeners = new CopyOnWriteArrayList(); private HostedServiceManager services; @@ -108,24 +108,29 @@ public class DefaultServer implements Server } protected void addStandardServices() { + log.fine("Adding standard services..."); services.addService(new ServerSerializerRegistrationsService()); } + @Override public String getGameName() { return gameName; } + @Override public int getVersion() { return version; } + @Override public HostedServiceManager getServices() { return services; } + @Override public int addChannel( int port ) { if( isRunning ) @@ -164,6 +169,7 @@ public class DefaultServer implements Server } } + @Override public void start() { if( isRunning ) @@ -185,11 +191,13 @@ public class DefaultServer implements Server services.start(); } + @Override public boolean isRunning() { return isRunning; } + @Override public void close() { if( !isRunning ) @@ -214,13 +222,19 @@ public class DefaultServer implements Server } } + @Override public void broadcast( Message message ) { broadcast( null, message ); } + @Override public void broadcast( Filter filter, Message message ) { + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "broadcast({0}, {1})", new Object[]{filter, message}); + } + if( connections.isEmpty() ) return; @@ -237,8 +251,13 @@ public class DefaultServer implements Server } } + @Override public void broadcast( int channel, Filter filter, Message message ) { + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "broadcast({0}, {1}. {2})", new Object[]{channel, filter, message}); + } + if( connections.isEmpty() ) return; @@ -251,46 +270,55 @@ public class DefaultServer implements Server channels.get(channel+CH_FIRST).broadcast( adapter, buffer, true, false ); } + @Override public HostedConnection getConnection( int id ) { return connections.get(id); } + @Override public boolean hasConnections() { return !connections.isEmpty(); } + @Override public Collection getConnections() { return Collections.unmodifiableCollection((Collection)connections.values()); } + @Override public void addConnectionListener( ConnectionListener listener ) { connectionListeners.add(listener); } + @Override public void removeConnectionListener( ConnectionListener listener ) { connectionListeners.remove(listener); } + @Override public void addMessageListener( MessageListener listener ) { messageListeners.addMessageListener( listener ); } + @Override public void addMessageListener( MessageListener listener, Class... classes ) { messageListeners.addMessageListener( listener, classes ); } + @Override public void removeMessageListener( MessageListener listener ) { messageListeners.removeMessageListener( listener ); } + @Override public void removeMessageListener( MessageListener listener, Class... classes ) { messageListeners.removeMessageListener( listener, classes ); @@ -484,12 +512,12 @@ public class DefaultServer implements Server protected class Connection implements HostedConnection { - private int id; + private final int id; private boolean closed; private Endpoint[] channels; private int setChannelCount = 0; - private Map sessionData = new ConcurrentHashMap(); + private final Map sessionData = new ConcurrentHashMap(); public Connection( int channelCount ) { @@ -523,23 +551,30 @@ public class DefaultServer implements Server return setChannelCount == channels.length; } + @Override public Server getServer() { return DefaultServer.this; } + @Override public int getId() { return id; } + @Override public String getAddress() { return channels[CH_RELIABLE] == null ? null : channels[CH_RELIABLE].getAddress(); } + @Override public void send( Message message ) { + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "send({0})", message); + } ByteBuffer buffer = MessageProtocol.messageToBuffer(message, null); if( message.isReliable() || channels[CH_UNRELIABLE] == null ) { channels[CH_RELIABLE].send( buffer ); @@ -548,8 +583,12 @@ public class DefaultServer implements Server } } + @Override public void send( int channel, Message message ) { + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "send({0}, {1})", new Object[]{channel, message}); + } checkChannel(channel); ByteBuffer buffer = MessageProtocol.messageToBuffer(message, null); channels[channel+CH_FIRST].send(buffer); @@ -573,6 +612,7 @@ public class DefaultServer implements Server fireConnectionRemoved( this ); } + @Override public void close( String reason ) { // Send a reason @@ -593,6 +633,7 @@ public class DefaultServer implements Server } } + @Override public Object setAttribute( String name, Object value ) { if( value == null ) @@ -601,11 +642,13 @@ public class DefaultServer implements Server } @SuppressWarnings("unchecked") + @Override public T getAttribute( String name ) { return (T)sessionData.get(name); } + @Override public Set attributeNames() { return Collections.unmodifiableSet(sessionData.keySet()); @@ -621,6 +664,7 @@ public class DefaultServer implements Server protected class Redispatch implements MessageListener { + @Override public void messageReceived( HostedConnection source, Message m ) { dispatch( source, m ); @@ -629,13 +673,14 @@ public class DefaultServer implements Server protected class FilterAdapter implements Filter { - private Filter delegate; + private final Filter delegate; public FilterAdapter( Filter delegate ) { this.delegate = delegate; } + @Override public boolean apply( Endpoint input ) { HostedConnection conn = getConnection( input ); diff --git a/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java b/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java index 6751ecc3f..bd85ececc 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java @@ -53,7 +53,7 @@ import java.util.LinkedList; */ public class MessageProtocol { - private LinkedList messages = new LinkedList(); + private final LinkedList messages = new LinkedList(); private ByteBuffer current; private int size; private Byte carry; diff --git a/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java index 9c3896475..4d1decc08 100644 --- a/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java +++ b/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java @@ -94,7 +94,7 @@ public class SerializerRegistrationsMessage extends AbstractMessage { public static Registration[] compiled; private static final Serializer fieldSerializer = new FieldSerializer(); - private Registration[] registrations; + private Registration[] registrations; public SerializerRegistrationsMessage() { setReliable(true); @@ -132,7 +132,25 @@ public class SerializerRegistrationsMessage extends AbstractMessage { Serializer.setReadOnly(true); } - public void registerAll() { + public void registerAll() { + + // See if we will have problems because our registry is locked + if( Serializer.isReadOnly() ) { + // Check to see if maybe we are executing this from the + // same JVM that sent the message, ie: client and server are running on + // the same JVM + // There could be more advanced checks than this but for now we'll + // assume that if the registry was compiled here then it means + // we are also the server process. Note that this wouldn't hold true + // under complicated examples where there are clients of one server + // that also run their own servers but realistically they would have + // to disable the ServerSerializerRegistrationsServer anyway. + if( compiled != null ) { + log.log( Level.INFO, "Skipping registration as registry is locked, presumably by a local server process."); + return; + } + } + for( Registration reg : registrations ) { log.log( Level.INFO, "Registering:{0}", reg); reg.register(); diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java index d4c054403..7e6b18849 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java @@ -45,6 +45,7 @@ import java.nio.ByteBuffer; import java.util.*; import java.util.jar.Attributes; import java.util.logging.Level; +import java.util.logging.LogManager; import java.util.logging.Logger; /** @@ -403,6 +404,10 @@ public abstract class Serializer { if (reg == null) { throw new SerializerException( "Class not registered:" + type ); } + + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "writing class:{0} with ID:{1}", new Object[]{type, reg.getId()}); + } buffer.putShort(reg.getId()); return reg; } diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java index 370b1c5af..3123e8c82 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java @@ -106,6 +106,6 @@ public abstract class AbstractService implements Servi @Override public String toString() { - return getClass().getName() + "[serviceManager.class=" + serviceManager.getClass() + "]"; + return getClass().getName() + "[serviceManager.class=" + (serviceManager != null ? serviceManager.getClass() : "") + "]"; } } diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java index b8ee7d3c4..afcfd73c2 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java @@ -34,6 +34,8 @@ package com.jme3.network.service; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; /** * The base service manager class from which the HostedServiceManager @@ -44,7 +46,9 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public abstract class ServiceManager { - private List> services = new CopyOnWriteArrayList>(); + static final Logger log = Logger.getLogger(ServiceManager.class.getName()); + + private final List> services = new CopyOnWriteArrayList>(); private volatile boolean started = false; protected ServiceManager() { @@ -76,6 +80,9 @@ public abstract class ServiceManager { return; } for( Service s : services ) { + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "Starting service:{0}", s); + } s.start(); } started = true; @@ -96,6 +103,9 @@ public abstract class ServiceManager { throw new IllegalStateException(getClass().getSimpleName() + " not started."); } for( Service s : services ) { + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "Stopping service:{0}", s); + } s.stop(); } started = false; @@ -106,9 +116,18 @@ public abstract class ServiceManager { * has already been started then the service will also be started. */ public > void addService( S s ) { + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "addService({0})", s); + } services.add(s); + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "Initializing service:{0}", s); + } s.initialize(getParent()); if( started ) { + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "Starting service:{0}", s); + } s.start(); } } @@ -120,10 +139,19 @@ public abstract class ServiceManager { * the service will be terminated. */ public > void removeService( S s ) { + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "removeService({0})", s); + } if( started ) { + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "Stopping service:{0}", s); + } s.stop(); } services.remove(s); + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "Terminating service:{0}", s); + } s.terminate(getParent()); } diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/Asynchronous.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/Asynchronous.java new file mode 100644 index 000000000..a26d09635 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/Asynchronous.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 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.network.service.rmi; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + + +/** + * Indicates that a given method should be executed asynchronously + * through the RMI service. This must annotate the method on the + * shared interface for it to have an effect. If reliable=false + * is specified then remote method invocation is done over UDP + * instead of TCP, ie: unreliably... but faster. + * + * @author Paul Speed + */ +@Retention(value=RUNTIME) +@Target(value=METHOD) +public @interface Asynchronous { + boolean reliable() default true; +} + + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/CallType.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/CallType.java new file mode 100644 index 000000000..5167bd7eb --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/CallType.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 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.network.service.rmi; + + +/** + * Internal type denoting the type of call to make when remotely + * invoking methods. + * + * @author Paul Speed + */ +public enum CallType { + /** + * Caller will block until a response is received and returned. + */ + Synchronous, + + /** + * Caller does not block or wait for a response. The other end + * of the connection will also not send one. + */ + Asynchronous, + + /** + * Similar to asynchronous in that no response is expected or sent + * but differs in that the call will be sent over UDP and so may + * not make it to the other end. + */ + Unreliable +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java new file mode 100644 index 000000000..4f7f7de32 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 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.network.service.rmi; + +import com.jme3.network.serializing.Serializable; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + + +/** + * Internal information about a shared class. This is the information + * that is sent over the wire for shared types. + * + * @author Paul Speed + */ +@Serializable +public final class ClassInfo { + private String name; + private short typeId; + private MethodInfo[] methods; + + /** + * For serialization only. + */ + public ClassInfo() { + } + + public ClassInfo( short typeId, Class type ) { + this.typeId = typeId; + this.name = type.getName(); + this.methods = toMethodInfo(type, type.getMethods()); + } + + public String getName() { + return name; + } + + public Class getType() { + try { + return Class.forName(name); + } catch( ClassNotFoundException e ) { + throw new RuntimeException("Error finding class for:" + this, e); + } + } + + public short getId() { + return typeId; + } + + public MethodInfo getMethod( short id ) { + return methods[id]; + } + + public MethodInfo getMethod( Method m ) { + for( MethodInfo mi : methods ) { + if( mi.matches(m) ) { + return mi; + } + } + return null; + } + + private MethodInfo[] toMethodInfo( Class type, Method[] methods ) { + List result = new ArrayList(); + short methodId = 0; + for( Method m : methods ) { + // Simple... add all methods exposed through the interface + result.add(new MethodInfo(methodId++, m)); + } + return result.toArray(new MethodInfo[result.size()]); + } + + public MethodInfo[] getMethods() { + return methods; + } + + @Override + public String toString() { + return "ClassInfo[" + name + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java new file mode 100644 index 000000000..addc03593 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 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.network.service.rmi; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + + +/** + * Internal registry of shared types and their ClassInfo and MethodInfo + * objects. + * + * @author Paul Speed + */ +public class ClassInfoRegistry { + + //private final LoadingCache cache; // Guava version + private final Map cache = new HashMap(); + private final AtomicInteger nextClassId = new AtomicInteger(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + public ClassInfoRegistry() { + //this.cache = CacheBuilder.newBuilder().build(new ClassInfoLoader()); // Guava version + } + + public ClassInfo getClassInfo( Class type ) { + //return cache.getUnchecked(type); // Guava version + + // More complicated without guava + lock.readLock().lock(); + try { + ClassInfo result = cache.get(type); + if( result != null ) { + return result; + } + // Else we need to create it and store it... so grab the write + // lock + lock.readLock().unlock(); + lock.writeLock().lock(); + try { + // Note: it's technically possible that a race with another thread + // asking for the same class already created one between our read unlock + // and our write lock. No matter as it's cheap to create one and does + // no harm. Code is simpler without the double-check. + result = new ClassInfo((short)nextClassId.getAndIncrement(), type); + cache.put(type, result); + + // Regrab the read lock before leaving... kind of unnecessary but + // it makes the method cleaner and widens the gap of lock races. + // Downgrading a write lock to read is ok. + lock.readLock().lock(); + + return result; + } finally { + // Unlock the write lock while still holding onto read + lock.writeLock().unlock(); + } + } finally { + lock.readLock().unlock(); + } + } + + /* + would be more straight-forward with guava Guava version + private class ClassInfoLoader extends CacheLoader { + @Override + public ClassInfo load( Class type ) { + return new ClassInfo((short)nextClassId.getAndIncrement(), type); + } + }*/ +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java new file mode 100644 index 000000000..9b324a6c3 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 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.network.service.rmi; + +import com.jme3.network.serializing.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import javax.jws.Oneway; + + +/** + * Internal information about shared methods. This is part of the data that + * is passed over the wire when an object is shared. + * + * @author Paul Speed + */ +@Serializable +public final class MethodInfo { + + public static final MethodInfo NULL_INFO = new MethodInfo(); + + private String representation; + private short id; + private CallType callType; + private transient Method method; + + /** + * For serialization only. + */ + public MethodInfo() { + } + + public MethodInfo( short id, Method m ) { + this.id = id; + this.method = m; + this.representation = methodToString(m); + this.callType = getCallType(m); + } + + public Object invoke( Object target, Object... parms ) { + try { + return method.invoke(target, parms); + } catch (IllegalAccessException e) { + throw new RuntimeException("Error invoking:" + method + " on:" + target, e); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Error invoking:" + method + " on:" + target, e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Error invoking:" + method + " on:" + target, e); + } + } + + public short getId() { + return id; + } + + public CallType getCallType() { + return callType; + } + + public boolean matches( Method m ) { + return representation.equals(methodToString(m)); + } + + public static String methodToString( Method m ) { + StringBuilder sb = new StringBuilder(); + for( Class t : m.getParameterTypes() ) { + if( sb.length() > 0 ) + sb.append(", "); + sb.append(t.getName()); + } + return m.getReturnType().getName() + " " + m.getName() + "(" + sb + ")"; + } + + public static CallType getCallType( Method m ) { + if( m.getReturnType() != Void.TYPE ) + return CallType.Synchronous; + if( m.getAnnotation(Oneway.class) != null ) + return CallType.Asynchronous; + if( m.getAnnotation(Asynchronous.class) == null ) + return CallType.Synchronous; + + Asynchronous async = m.getAnnotation(Asynchronous.class); + return async.reliable() ? CallType.Asynchronous : CallType.Unreliable; + } + + @Override + public int hashCode() { + return representation.hashCode(); + } + + @Override + public boolean equals( Object o ) { + if( o == this ) { + return true; + } + if( o == null || o.getClass() != getClass() ) { + return false; + } + MethodInfo other = (MethodInfo)o; + return representation.equals(other.representation); + } + + @Override + public String toString() { + return "MethodInfo[#" + getId() + ", callType=" + callType + ", " + representation + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java new file mode 100644 index 000000000..f42ada84f --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 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.network.service.rmi; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +/** + * Used internally to remotely invoke methods on RMI shared objects. + * + * @author Paul Speed + */ +public class RemoteObjectHandler implements InvocationHandler { + + private final RmiRegistry rmi; + private final byte channel; + private final short objectId; + private final ClassInfo typeInfo; + private final Map methodIndex = new ConcurrentHashMap(); + + public RemoteObjectHandler( RmiRegistry rmi, byte channel, short objectId, ClassInfo typeInfo ) { + this.rmi = rmi; + this.channel = channel; + this.objectId = objectId; + this.typeInfo = typeInfo; + } + + protected MethodInfo getMethodInfo( Method method ) { + MethodInfo mi = methodIndex.get(method); + if( mi == null ) { + mi = typeInfo.getMethod(method); + if( mi == null ) { + mi = MethodInfo.NULL_INFO; + } + methodIndex.put(method, mi); + } + return mi == MethodInfo.NULL_INFO ? null : mi; + } + + @Override + public Object invoke(Object o, Method method, Object[] os) throws Throwable { + MethodInfo mi = getMethodInfo(method); + if( mi == null ) { + // Try to invoke locally + return method.invoke(this, os); + } + return rmi.invokeRemote(channel, objectId, mi.getId(), mi.getCallType(), os); + } + + @Override + public String toString() { + return "RemoteObject[#" + objectId + ", " + typeInfo.getName() + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java new file mode 100644 index 000000000..ea1506f72 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 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.network.service.rmi; + +import com.jme3.network.MessageConnection; +import com.jme3.network.service.AbstractClientService; +import com.jme3.network.service.ClientServiceManager; +import com.jme3.network.service.rpc.RpcClientService; +import java.util.ArrayList; +import java.util.List; + + +/** + * A service that can be added to the client to support a simple + * shared objects protocol. + * + *

Objects are shared by adding them to the RmiRegistry with one of the + * share() methods. Shared objects must have a separate interface and implementation. + * The interface is what the other end of the connection will use to interact + * with the object and that interface class must be available on both ends of + * the connection. The implementing class need only be on the sharing end.

+ * + *

Shared objects can be accessed on the other end of the connection by + * using one of the RmiRegistry's getRemoteObject() methods. These can be + * used to lookup an object by class if it is a shared singleton or by name + * if it was registered with a name.

+ * + *

Note: This RMI implementation is not as advanced as Java's regular + * RMI as it won't marshall shared references, ie: you can't pass + * a shared objects as an argument to another shared object's method.

+ * + * @author Paul Speed + */ +public class RmiClientService extends AbstractClientService { + + private RpcClientService rpc; + private byte defaultChannel; + private short rmiObjectId; + private RmiRegistry rmi; + private volatile boolean isStarted = false; + + private final List pending = new ArrayList(); + + public RmiClientService() { + this((short)-1, (byte)MessageConnection.CHANNEL_DEFAULT_RELIABLE); + } + + public RmiClientService( short rmiObjectId, byte defaultChannel ) { + this.defaultChannel = defaultChannel; + this.rmiObjectId = rmiObjectId; + } + + /** + * Shares the specified object with the server and associates it with the + * specified type. Objects shared in this way are available in the connection-specific + * RMI registry on the server and are not available to other connections. + */ + public void share( T object, Class type ) { + share(defaultChannel, object, type); + } + + /** + * Shares the specified object with the server and associates it with the + * specified type. Objects shared in this way are available in the connection-specific + * RMI registry on the server and are not available to other connections. + * All object related communication will be done over the specified connection + * channel. + */ + public void share( byte channel, T object, Class type ) { + share(channel, type.getName(), object, type); + } + + /** + * Shares the specified object with the server and associates it with the + * specified name. Objects shared in this way are available in the connection-specific + * RMI registry on the server and are not available to other connections. + */ + public void share( String name, T object, Class type ) { + share(defaultChannel, name, object, type); + } + + /** + * Shares the specified object with the server and associates it with the + * specified name. Objects shared in this way are available in the connection-specific + * RMI registry on the server and are not available to other connections. + * All object related communication will be done over the specified connection + * channel. + */ + public void share( byte channel, String name, T object, Class type ) { + if( !isStarted ) { + synchronized(pending) { + if( !isStarted ) { + pending.add(new ObjectInfo(channel, name, object, type)); + return; + } + } + } + + // Else we can add it directly. + rmi.share(channel, name, object, type); + } + + /** + * Looks up a remote object on the server by type and returns a local proxy to the + * remote object that was shared on the other end of the network connection. + */ + public T getRemoteObject( Class type ) { + return rmi.getRemoteObject(type); + } + + /** + * Looks up a remote object on the server by name and returns a local proxy to the + * remote object that was shared on the other end of the network connection. + */ + public T getRemoteObject( String name, Class type ) { + return rmi.getRemoteObject(name, type); + } + + @Override + protected void onInitialize( ClientServiceManager s ) { + rpc = getService(RpcClientService.class); + if( rpc == null ) { + throw new RuntimeException("RmiClientService requires RpcClientService"); + } + + // Register it now so that it is available when the + // server starts to send us stuff. Waiting until start() + // is too late in this case. + rmi = new RmiRegistry(rpc.getRpcConnection(), rmiObjectId, defaultChannel); + } + + @Override + public void start() { + super.start(); + + // Register all of the classes that have been waiting. + synchronized(pending) { + for( ObjectInfo info : pending ) { + rmi.share(info.channel, info.name, info.object, info.type); + } + pending.clear(); + isStarted = true; + } + } + + private class ObjectInfo { + byte channel; + String name; + Object object; + Class type; + + public ObjectInfo( byte channel, String name, Object object, Class type ) { + this.channel = channel; + this.name = name; + this.object = object; + this.type = type; + } + + @Override + public String toString() { + return "ObjectInfo[" + channel + ", " + name + ", " + object + ", " + type + "]"; + } + } +} + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java new file mode 100644 index 000000000..547e83dd4 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 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.network.service.rmi; + +import com.jme3.network.HostedConnection; + + +/** + * Keeps track of the current connection performing a particular + * RMI call. RMI-based services can use this to find out which + * connection is calling a particular method without having to + * pass additional problematic data on the method calls. + * + * @author Paul Speed + */ +public class RmiContext { + private static final ThreadLocal connection = new ThreadLocal(); + + /** + * Returns the HostedConnection that is responsible for any + * RMI-related calls on this thread. + */ + public static HostedConnection getRmiConnection() { + return connection.get(); + } + + static void setRmiConnection( HostedConnection conn ) { + connection.set(conn); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java new file mode 100644 index 000000000..997658a77 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 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.network.service.rmi; + +import com.jme3.network.HostedConnection; +import com.jme3.network.MessageConnection; +import com.jme3.network.Server; +import com.jme3.network.serializing.Serializer; +import com.jme3.network.service.AbstractHostedService; +import com.jme3.network.service.HostedServiceManager; +import com.jme3.network.service.rpc.RpcHostedService; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * A service that can be added to the host to support a simple + * shared objects protocol. + * + *

Objects are shared by adding them to the RmiRegistry with one of the + * share() methods. Shared objects must have a separate interface and implementation. + * The interface is what the other end of the connection will use to interact + * with the object and that interface class must be available on both ends of + * the connection. The implementing class need only be on the sharing end.

+ * + *

Shared objects can be accessed on the other end of the connection by + * using one of the RmiRegistry's getRemoteObject() methods. These can be + * used to lookup an object by class if it is a shared singleton or by name + * if it was registered with a name.

+ * + *

On the hosting side, a special shardGlobal() method is provided that + * will register shared objects that will automatically be provided to every + * new joining client and they will all be calling the same server-side instance. + * Normally, shared objects themselves are connection specific and handled + * at the connection layer. The shareGlobal() space is a way to have global + * resources passed directly though the need is relatively rare.

+ * + *

Note: This RMI implementation is not as advanced as Java's regular + * RMI as it won't marshall shared references, ie: you can't pass + * a shared objects as an argument to another shared object's method.

+ * + * @author Paul Speed + */ +public class RmiHostedService extends AbstractHostedService { + + static final Logger log = Logger.getLogger(RpcHostedService.class.getName()); + + public static final String ATTRIBUTE_NAME = "rmi"; + + private RpcHostedService rpcService; + private short rmiId; + private byte defaultChannel; + private boolean autoHost; + private final Map globalShares = new ConcurrentHashMap(); + + public RmiHostedService() { + this((short)-1, (byte)MessageConnection.CHANNEL_DEFAULT_RELIABLE, true); + } + + public RmiHostedService( short rmiId, byte defaultChannel, boolean autoHost ) { + this.rmiId = rmiId; + this.defaultChannel = defaultChannel; + this.autoHost = autoHost; + + Serializer.registerClasses(ClassInfo.class, MethodInfo.class); + } + + /** + * Shares a server-wide object associated with the specified type. All connections + * with RMI hosting started will have access to this shared object as soon as they + * connect and they will all share the same instance. It is up to the shared object + * to handle any multithreading that might be required. + */ + public void shareGlobal( T object, Class type ) { + shareGlobal(defaultChannel, type.getName(), object, type); + } + + /** + * Shares a server-wide object associated with the specified name. All connections + * with RMI hosting started will have access to this shared object as soon as they + * connect and they will all share the same instance. It is up to the shared object + * to handle any multithreading that might be required. + */ + public void shareGlobal( String name, T object, Class type ) { + shareGlobal(defaultChannel, name, object, type); + } + + /** + * Shares a server-wide object associated with the specified name over the specified + * channel. All connections with RMI hosting started will have access to this shared + * object as soon as they connect and they will all share the same instance. It is up + * to the shared object to handle any multithreading that might be required. + * All network communcation associated with the shared object will be done over + * the specified channel. + */ + public void shareGlobal( byte channel, String name, T object, Class type ) { + GlobalShare share = new GlobalShare(channel, object, type); + GlobalShare existing = globalShares.put(name, share); + if( existing != null ) { + // Shouldn't need to do anything actually. + } + + // Go through all of the children + for( HostedConnection conn : getServer().getConnections() ) { + RmiRegistry child = getRmiRegistry(conn); + if( child == null ) { + continue; + } + child.share(channel, name, object, type); + } + } + + /** + * Set to true if all new connections should automatically have RMI hosting started. + * Set to false if the game-specific connection setup will call startHostingOnConnection() + * after some connection setup is done (for example, logging in). Note: generally + * is is safe to autohost RMI as long as callers are careful about what they've added + * using shareGlobal(). One reasonable use-case is to shareGlobal() some kind of login + * service and nothing else. All other shared objects would then be added as connection + * specific objects during successful login processing. + */ + public void setAutoHost( boolean b ) { + this.autoHost = b; + } + + /** + * Returns true if RMI hosting is automatically started for all new connections. + */ + public boolean getAutoHost() { + return autoHost; + } + + /** + * Returns the RMI registry for the specific HostedConection. Each connection + * has its own registry with its own connection-specific shared objects. + */ + public RmiRegistry getRmiRegistry( HostedConnection hc ) { + return hc.getAttribute(ATTRIBUTE_NAME); + } + + /** + * Sets up RMI hosting services for the hosted connection allowing + * getRmiRegistry() to return a valid RmiRegistry object. + * This method is called automatically for all new connections if + * autohost is set to true. + */ + public void startHostingOnConnection( HostedConnection hc ) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "startHostingOnConnection:{0}", hc); + } + RmiRegistry rmi = new RmiRegistry(hc, rpcService.getRpcConnection(hc), + rmiId, defaultChannel); + hc.setAttribute(ATTRIBUTE_NAME, rmi); + + // Register any global shares + for( Map.Entry e : globalShares.entrySet() ) { + GlobalShare share = e.getValue(); + rmi.share(share.channel, e.getKey(), share.object, share.type); + } + } + + /** + * Removes any RMI hosting services associated with the specified + * connection. Calls to getRmiRegistry() will return null for + * this connection. + * This method is called automatically for all leaving connections if + * autohost is set to true. + */ + public void stopHostingOnConnection( HostedConnection hc ) { + RmiRegistry rmi = hc.getAttribute(ATTRIBUTE_NAME); + if( rmi == null ) { + return; + } + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "stopHostingOnConnection:{0}", hc); + } + hc.setAttribute(ATTRIBUTE_NAME, null); + //rpc.close(); + } + + @Override + protected void onInitialize( HostedServiceManager s ) { + this.rpcService = getService(RpcHostedService.class); + if( rpcService == null ) { + throw new RuntimeException("RmiHostedService requires RpcHostedService"); + } + } + + /** + * Called internally when a new connection is detected for + * the server. If the current autoHost property is true then + * startHostingOnConnection(hc) is called. + */ + @Override + public void connectionAdded(Server server, HostedConnection hc) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "connectionAdded({0}, {1})", new Object[]{server, hc}); + } + if( autoHost ) { + startHostingOnConnection(hc); + } + } + + /** + * Called internally when an existing connection is leaving + * the server. If the current autoHost property is true then + * stopHostingOnConnection(hc) is called. + */ + @Override + public void connectionRemoved(Server server, HostedConnection hc) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "connectionRemoved({0}, {1})", new Object[]{server, hc}); + } + if( autoHost ) { + stopHostingOnConnection(hc); + } + } + + private class GlobalShare { + byte channel; + Object object; + Class type; + + public GlobalShare( byte channel, Object object, Class type ) { + this.channel = channel; + this.object = object; + this.type = type; + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java new file mode 100644 index 000000000..c04a8c7b3 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java @@ -0,0 +1,387 @@ +/* + * Copyright (c) 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.network.service.rmi; + +import com.jme3.network.HostedConnection; +import com.jme3.network.MessageConnection; +import com.jme3.network.service.rpc.RpcConnection; +import com.jme3.network.service.rpc.RpcHandler; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * + * + * @author Paul Speed + */ +public class RmiRegistry { + + static final Logger log = Logger.getLogger(RmiRegistry.class.getName()); + + // RPC IDs for calling our remote endpoint + private static final short NEW_CLASS = 0; + private static final short ADD_OBJECT = 1; + private static final short REMOVE_OBJECT = 2; + + private RpcConnection rpc; + private short rmiId; + private byte defaultChannel; + private final RmiHandler rmiHandler = new RmiHandler(); + private final ClassInfoRegistry classCache = new ClassInfoRegistry(); + private final AtomicInteger nextObjectId = new AtomicInteger(); + + private final ObjectIndex local = new ObjectIndex(); + private final ObjectIndex remote = new ObjectIndex(); + + // Only used on the server to provide thread-local context for + // local RMI calls. + private HostedConnection context; + + public RmiRegistry( RpcConnection rpc, short rmiId, byte defaultChannel ) { + this(null, rpc, rmiId, defaultChannel); + } + + public RmiRegistry( HostedConnection context, RpcConnection rpc, short rmiId, byte defaultChannel ) { + this.context = context; + this.rpc = rpc; + this.rmiId = rmiId; + this.defaultChannel = defaultChannel; + rpc.registerHandler(rmiId, rmiHandler); + } + + /** + * Exposes the specified object to the other end of the connection as + * the specified interface type. The object can be looked up by type + * on the other end. + */ + public void share( T object, Class type ) { + share(defaultChannel, object, type); + } + + /** + * Exposes, through a specific connection channel, the specified object + * to the other end of the connection as the specified interface type. + * The object can be looked up by type on the other end. + * The specified channel will be used for all network communication + * specific to this object. + */ + public void share( byte channel, T object, Class type ) { + share(channel, type.getName(), object, type); + } + + /** + * Exposes the specified object to the other end of the connection as + * the specified interface type and associates it with the specified name. + * The object can be looked up by the associated name on the other end of + * the connection. + */ + public void share( String name, T object, Class type ) { + share(defaultChannel, name, object, type); + } + + /** + * Exposes, through a specific connection channel, the specified object to + * the other end of the connection as the specified interface type and associates + * it with the specified name. + * The object can be looked up by the associated name on the other end of + * the connection. + * The specified channel will be used for all network communication + * specific to this object. + */ + public void share( byte channel, String name, T object, Class type ) { + + ClassInfo typeInfo = classCache.getClassInfo(type); + + local.lock.writeLock().lock(); + try { + + // First see if we've told the remote end about this class + // before + if( local.classes.put(typeInfo.getId(), typeInfo) == null ) { + // It's new + rpc.callAsync(defaultChannel, rmiId, NEW_CLASS, typeInfo); + + // Because type info IDs are global to the class cache, + // we could in theory keep a global index that we broadcast + // on first connection setup... we need only prepopulate + // the index in that case. + } + + // See if we already shared an object under that name + SharedObject existing = local.byName.remove(name); + if( existing != null ) { + local.byId.remove(existing.objectId); + rpc.removeHandler(existing.objectId, rmiHandler); + + // Need to delete the old one from the remote end + rpc.callAsync(defaultChannel, rmiId, REMOVE_OBJECT, existing.objectId); + + // We don't reuse the ID because it's kind of dangerous. + // Churning through a new ID is our safety net for accidents. + } + + SharedObject newShare = new SharedObject(name, object, type, typeInfo); + local.byName.put(name, newShare); + local.byId.put(newShare.objectId, newShare); + + // Make sure we are setup to receive the remote method calls through + // the RPC service + rpc.registerHandler(newShare.objectId, rmiHandler); + + // Let the other end know + rpc.callAsync(defaultChannel, rmiId, ADD_OBJECT, channel, newShare.objectId, name, typeInfo.getId()); + + // We send the ADD_OBJECT to the other end before releasing the + // lock to avoid a potential inconsistency if two threads try to + // jam the same name at the same time. Otherwise, if the timing were + // right, the remove for one object could get there before its add. + + } finally { + local.lock.writeLock().unlock(); + } + } + + /** + * Returns a local object that was previously registered with share() using + * just type registration. + */ + public T getLocalObject( Class type ) { + return getLocalObject(type.getName(), type); + } + + /** + * Returns a local object that was previously registered with share() using + * name registration. + */ + public T getLocalObject( String name, Class type ) { + local.lock.readLock().lock(); + try { + return type.cast(local.byName.get(name)); + } finally { + local.lock.readLock().unlock(); + } + } + + /** + * Looks up a remote object by type and returns a local proxy to the remote object + * that was shared on the other end of the network connection. If this is called + * from a client then it is accessing a shared object registered on the server. + * If this is called from the server then it is accessing a shared object registered + * on the client. + */ + public T getRemoteObject( Class type ) { + return getRemoteObject(type.getName(), type); + } + + /** + * Looks up a remote object by name and returns a local proxy to the remote object + * that was shared on the other end of the network connection. If this is called + * from a client then it is accessing a shared object registered on the server. + * If this is called from the server then it is accessing a shared object registered + * on the client. + */ + public T getRemoteObject( String name, Class type ) { + remote.lock.readLock().lock(); + try { + return type.cast(remote.byName.get(name)); + } finally { + remote.lock.readLock().unlock(); + } + } + + protected void addRemoteClass( ClassInfo info ) { + if( remote.classes.put(info.getId(), info) != null ) { + throw new RuntimeException("Error class already exists for ID:" + info.getId()); + } + } + + protected void removeRemoteObject( short objectId ) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "removeRemoteObject({0})", objectId); + } + throw new UnsupportedOperationException("Removal not yet implemented."); + } + + protected void addRemoteObject( byte channel, short objectId, String name, ClassInfo typeInfo ) { + if( log.isLoggable(Level.FINEST) ) { + log.finest("addRemoveObject(" + objectId + ", " + name + ", " + typeInfo + ")"); + } + remote.lock.writeLock().lock(); + try { + Object existing = remote.byName.get(name); + if( existing != null ) { + throw new RuntimeException("Object already registered for:" + name); + } + + RemoteObjectHandler remoteHandler = new RemoteObjectHandler(this, channel, objectId, typeInfo); + + Object remoteObject = Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[] {typeInfo.getType()}, + remoteHandler); + + remote.byName.put(name, remoteObject); + remote.byId.put(objectId, remoteObject); + } finally { + remote.lock.writeLock().unlock(); + } + } + + protected Object invokeRemote( byte channel, short objectId, short procId, CallType callType, Object[] args ) { + if( log.isLoggable(Level.FINEST) ) { + log.finest("invokeRemote(" + channel + ", " + objectId + ", " + procId + ", " + + callType + ", " + (args == null ? "null" : Arrays.asList(args)) + ")"); + } + switch( callType ) { + case Asynchronous: + log.finest("Sending reliable asynchronous."); + rpc.callAsync(channel, objectId, procId, args); + return null; + case Unreliable: + log.finest("Sending unreliable asynchronous."); + rpc.callAsync((byte)MessageConnection.CHANNEL_DEFAULT_UNRELIABLE, objectId, procId, args); + return null; + default: + case Synchronous: + log.finest("Sending synchronous."); + Object result = rpc.callAndWait(channel, objectId, procId, args); + if( log.isLoggable(Level.FINEST) ) { + log.finest("->got:" + result); + } + return result; + } + } + + /** + * Handle remote object registry updates from the other end. + */ + protected void rmiUpdate( short procId, Object[] args ) { + if( log.isLoggable(Level.FINEST) ) { + log.finest("rmiUpdate(" + procId + ", " + Arrays.asList(args) + ")"); + } + switch( procId ) { + case NEW_CLASS: + addRemoteClass((ClassInfo)args[0]); + break; + case REMOVE_OBJECT: + removeRemoteObject((Short)args[0]); + break; + case ADD_OBJECT: + ClassInfo info = remote.classes.get((Short)args[3]); + addRemoteObject((Byte)args[0], (Short)args[1], (String)args[2], info); + break; + } + } + + /** + * Handle the actual remote object method calls. + */ + protected Object invokeLocal( short objectId, short procId, Object[] args ) { + // Actually could use a regular concurrent map for this + + // Only lock the local registry during lookup and + // not invocation. It prevents a deadlock if the invoked method + // tries to share an object. It should be safe. + SharedObject share = local.byId.get(objectId); + local.lock.readLock().lock(); + try { + share = local.byId.get(objectId); + } finally { + local.lock.readLock().unlock(); + } + + try { + RmiContext.setRmiConnection(context); + return share.invoke(procId, args); + } finally { + RmiContext.setRmiConnection(null); + } + } + + private class SharedObject { + private final short objectId; + private final String name; + private final Object object; + private final Class type; + private final ClassInfo classInfo; + + public SharedObject( String name, Object object, Class type, ClassInfo classInfo ) { + this.objectId = (short)nextObjectId.incrementAndGet(); + this.name = name; + this.object = object; + this.type = type; + this.classInfo = classInfo; + } + + public Object invoke( short procId, Object[] args ) { + return classInfo.getMethod(procId).invoke(object, args); + } + } + + private class RmiHandler implements RpcHandler { + @Override + public Object call( RpcConnection conn, short objectId, short procId, Object... args ) { + if( objectId == rmiId ) { + rmiUpdate(procId, args); + return null; + } else { + return invokeLocal(objectId, procId, args); + } + } + } + + /** + * Keeps a coincident index between short ID, name, and related class info. + * There will be one of these to track our local objects and one to track + * the remote objects and a lock that can guard them. + */ + private class ObjectIndex { + final Map byName = new HashMap(); + final Map byId = new HashMap(); + final Map classes = new HashMap(); + final ReadWriteLock lock = new ReentrantReadWriteLock(); + + public ObjectIndex() { + } + } + +} + + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java index 911ce0fb1..61632282a 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java @@ -39,6 +39,8 @@ import com.jme3.network.message.SerializerRegistrationsMessage; import com.jme3.network.serializing.Serializer; import com.jme3.network.service.AbstractClientService; import com.jme3.network.service.ClientServiceManager; +import java.util.logging.Level; +import java.util.logging.Logger; /** @@ -48,19 +50,27 @@ import com.jme3.network.service.ClientServiceManager; */ public class ClientSerializerRegistrationsService extends AbstractClientService implements MessageListener { + + static final Logger log = Logger.getLogger(SerializerRegistrationsMessage.class.getName()); @Override protected void onInitialize( ClientServiceManager serviceManager ) { - // Make sure our message type is registered - // This is the minimum we'd need just to be able to register - // the rest... otherwise we can't even receive this message. - Serializer.registerClass(SerializerRegistrationsMessage.class); - Serializer.registerClass(SerializerRegistrationsMessage.Registration.class); + + // Make sure our message type is registered if it isn't already + if( Serializer.getExactSerializerRegistration(SerializerRegistrationsMessage.class) == null ) { + // This is the minimum we'd need just to be able to register + // the rest... otherwise we can't even receive this message. + Serializer.registerClass(SerializerRegistrationsMessage.class); + Serializer.registerClass(SerializerRegistrationsMessage.Registration.class); + } else { + log.log(Level.INFO, "Skipping registration of SerializerRegistrationsMessage."); + } // Add our listener for that message type serviceManager.getClient().addMessageListener(this, SerializerRegistrationsMessage.class); } + @Override public void messageReceived( Client source, Message m ) { // We only wait for one kind of message... SerializerRegistrationsMessage msg = (SerializerRegistrationsMessage)m; diff --git a/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.form b/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.form index 424078412..a5ad10fe2 100644 --- a/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.form +++ b/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.form @@ -9,6 +9,7 @@ + diff --git a/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.java b/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.java index c199fe39a..285ada07c 100644 --- a/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.java +++ b/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.java @@ -31,9 +31,8 @@ */ package com.jme3.gde.core.properties; -import com.jme3.asset.TextureKey; import com.jme3.gde.core.assets.ProjectAssetManager; -import com.jme3.gde.core.properties.preview.DDSPreview; +import com.jme3.gde.core.properties.preview.TexturePreview; import com.jme3.gde.core.util.TreeUtil; import com.jme3.texture.Texture; import java.awt.event.MouseEvent; @@ -42,6 +41,7 @@ import java.awt.event.WindowListener; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.prefs.Preferences; import javax.swing.DefaultListSelectionModel; @@ -52,8 +52,6 @@ import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; -import jme3tools.converters.ImageToAwt; -import org.openide.util.ImageUtilities; /** * Displays all textures in the ProjectAssetManager, @@ -68,7 +66,7 @@ public class TextureBrowser extends javax.swing.JDialog implements TreeSelection private ProjectAssetManager assetManager; private TexturePropertyEditor editor; - private DDSPreview ddsPreview; + private TexturePreview texPreview; private Preferences prefs; private static final String PREF_LAST_SELECTED = "lastSelectedTexture"; @@ -234,8 +232,8 @@ private void noTexturebuttonActionPerformed(java.awt.event.ActionEvent evt) {//G if (node != null && node.isLeaf()) { String selected = TreeUtil.getPath(node.getUserObjectPath()); selected = selected.substring(0, selected.lastIndexOf("/")); - Texture tex = assetManager.loadTexture(selected); - editor.setValue(tex); +// Texture tex = assetManager.loadTexture(selected); +// editor.setValue(tex); editor.setAsText(selected); return true; } @@ -270,7 +268,7 @@ private void noTexturebuttonActionPerformed(java.awt.event.ActionEvent evt) {//G private void setSelectedTexture(Texture texture) { if (texture != null) { - Logger.getLogger(TextureBrowser.class.getName()).finer("Looking for Texture: " + texture.getName()); + Logger.getLogger(TextureBrowser.class.getName()).log(Level.FINER, "Looking for Texture: {0}", texture.getName()); String[] path = ("/" + texture.getName()).split("/"); TreePath parent = new TreePath((TreeNode) jTree1.getModel().getRoot()); TreePath selectedTreePath = TreeUtil.buildTreePath(jTree1, parent, path, 0, true); @@ -287,6 +285,7 @@ private void noTexturebuttonActionPerformed(java.awt.event.ActionEvent evt) {//G } } + @Override public void valueChanged(TreeSelectionEvent e) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) jTree1.getLastSelectedPathComponent(); @@ -295,25 +294,14 @@ private void noTexturebuttonActionPerformed(java.awt.event.ActionEvent evt) {//G return; } - //Object nodeInfo = node.getUserObject(); if (node.isLeaf()) { String selected = TreeUtil.getPath(node.getUserObjectPath()); selected = selected.substring(0, selected.lastIndexOf("/")); Icon newicon = null; - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview(assetManager); - } - ddsPreview.requestPreview(selected, (String) node.getUserObject(), 450, 450, imagePreviewLabel, infoLabel); - - } else { - Texture tex = assetManager.loadTexture(selected); - newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - assetManager.deleteFromCache(new TextureKey(selected)); - imagePreviewLabel.setIcon(newicon); - infoLabel.setText(" " + node.getUserObject() + " w : " + newicon.getIconWidth() + " h : " + newicon.getIconHeight()); + if (texPreview == null) { + texPreview = new TexturePreview(assetManager); } - + texPreview.requestPreview(selected, (String) node.getUserObject(), 450, 450, imagePreviewLabel, infoLabel); prefs.put(PREF_LAST_SELECTED, selected); } else { imagePreviewLabel.setIcon(null); @@ -323,27 +311,34 @@ private void noTexturebuttonActionPerformed(java.awt.event.ActionEvent evt) {//G } + @Override public void windowOpened(WindowEvent e) { } + @Override public void windowClosing(WindowEvent e) { - if (ddsPreview != null) { - ddsPreview.cleanUp(); + if (texPreview != null) { + texPreview.cleanUp(); } } + @Override public void windowClosed(WindowEvent e) { } + @Override public void windowIconified(WindowEvent e) { } + @Override public void windowDeiconified(WindowEvent e) { } + @Override public void windowActivated(WindowEvent e) { } + @Override public void windowDeactivated(WindowEvent e) { } diff --git a/sdk/jme3-core/src/com/jme3/gde/core/properties/TexturePropertyEditor.java b/sdk/jme3-core/src/com/jme3/gde/core/properties/TexturePropertyEditor.java index 9f4658e02..555fc5160 100644 --- a/sdk/jme3-core/src/com/jme3/gde/core/properties/TexturePropertyEditor.java +++ b/sdk/jme3-core/src/com/jme3/gde/core/properties/TexturePropertyEditor.java @@ -80,7 +80,14 @@ public class TexturePropertyEditor implements PropertyEditor { } } + @Override public Object getValue() { + if(texture == null && assetKey != null){ + if (manager == null){ + manager = SceneApplication.getApplication().getAssetManager(); + } + texture = manager.loadTexture(assetKey); + } return texture; } diff --git a/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/DDSPreview.java b/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/DDSPreview.java deleted file mode 100644 index c6597ff6a..000000000 --- a/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/DDSPreview.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2009-2010 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.gde.core.properties.preview; - -import com.jme3.asset.TextureKey; -import com.jme3.gde.core.assets.ProjectAssetManager; -import com.jme3.gde.core.scene.PreviewRequest; -import com.jme3.gde.core.scene.SceneApplication; -import com.jme3.gde.core.scene.SceneListener; -import com.jme3.gde.core.scene.SceneRequest; -import com.jme3.material.Material; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Quad; -import com.jme3.texture.Texture; -import com.jme3.util.SkyFactory; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JLabel; - -/** - * - * @author Nehon - */ -public class DDSPreview implements SceneListener { - - private final ProjectAssetManager assetManager; - private JComponent picPreview; - private final Geometry quad; - private final Geometry quad3D; - private final Material material; - private final Material material3D; - - public DDSPreview(ProjectAssetManager assetManager) { - this.assetManager = assetManager; - - Quad quadMesh = new Quad(4.5f, 4.5f); - Quad quadMesh3D = new Quad(4.5f, 4.5f); - quadMesh3D.scaleTextureCoordinates(new Vector2f(4, 4)); - quad = new Geometry("previewQuad", quadMesh); - quad.setLocalTranslation(new Vector3f(-2.25f, -2.25f, 0)); - quad3D = new Geometry("previewQuad", quadMesh3D); - quad3D.setLocalTranslation(new Vector3f(-2.25f, -2.25f, 0)); - material3D = new Material(assetManager, "com/jme3/gde/core/properties/preview/tex3DThumb.j3md"); - material3D.setFloat("InvDepth", 1f / 16f); - material3D.setInt("Rows", 4); - material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - SceneApplication.getApplication().addSceneListener(this); - } - - public void requestPreview(String textureName, String displayName, int width, int height, JComponent picLabel, JLabel infoLabel) { - TextureKey key = new TextureKey(textureName); - picPreview = picLabel; - assetManager.deleteFromCache(key); - Texture t = assetManager.loadTexture(key); - Spatial geom = quad; - if (key.getTextureTypeHint() == Texture.Type.TwoDimensional) { - material.setTexture("ColorMap", t); - geom.setMaterial(material); - if (infoLabel != null) { - infoLabel.setText(" " + displayName + " w : " + t.getImage().getWidth() + " h : " + t.getImage().getHeight()); - } - } else if (key.getTextureTypeHint() == Texture.Type.ThreeDimensional) { - geom = quad3D; - assetManager.deleteFromCache(key); - key.setTextureTypeHint(Texture.Type.ThreeDimensional); - t = assetManager.loadTexture(key); - material3D.setTexture("Texture", t); - geom.setMaterial(material3D); - if (infoLabel != null) { - infoLabel.setText(" " + displayName + " (Texture3D) w : " + t.getImage().getWidth() + " h : " + t.getImage().getHeight() + " d : " + t.getImage().getDepth()); - } - } else if (key.getTextureTypeHint() == Texture.Type.CubeMap) { - assetManager.deleteFromCache(key); - geom = SkyFactory.createSky(assetManager, textureName, SkyFactory.EnvMapType.CubeMap); - if (infoLabel != null) { - infoLabel.setText(" " + displayName + " (CubeMap) w : " + t.getImage().getWidth() + " h : " + t.getImage().getHeight()); - } - } - - PreviewRequest request = new PreviewRequest(this, geom, width, height); - request.getCameraRequest().setLocation(new Vector3f(0, 0, 5.3f)); - request.getCameraRequest().setLookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y.mult(-1)); - SceneApplication.getApplication().createPreview(request); - } - - public void cleanUp() { - SceneApplication.getApplication().removeSceneListener(this); - } - - public void sceneOpened(SceneRequest request) { - } - - public void sceneClosed(SceneRequest request) { - } - - public void previewCreated(PreviewRequest request) { - if (request.getRequester() == this) { - final ImageIcon icon = new ImageIcon(request.getImage()); - if (picPreview instanceof JLabel) { - ((JLabel) picPreview).setIcon(icon); - } - if (picPreview instanceof JButton) { - ((JButton) picPreview).setIcon(icon); - } - } - } -} diff --git a/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/TexturePreview.java b/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/TexturePreview.java new file mode 100644 index 000000000..337e3a34c --- /dev/null +++ b/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/TexturePreview.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2009-2010 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.gde.core.properties.preview; + +import com.jme3.asset.TextureKey; +import com.jme3.gde.core.assets.ProjectAssetManager; +import com.jme3.gde.core.scene.PreviewRequest; +import com.jme3.gde.core.scene.SceneApplication; +import com.jme3.gde.core.scene.SceneListener; +import com.jme3.gde.core.scene.SceneRequest; +import com.jme3.material.Material; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Texture; +import com.jme3.util.SkyFactory; +import java.util.concurrent.Callable; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; + +/** + * + * @author Nehon + */ +public class TexturePreview implements SceneListener { + + private final ProjectAssetManager assetManager; + private JComponent picPreview; + private final Geometry quad; + private final Geometry quad3D; + private final Material material; + private final Material material3D; + + public TexturePreview(ProjectAssetManager assetManager) { + this.assetManager = assetManager; + + Quad quadMesh = new Quad(4.5f, 4.5f); + Quad quadMesh3D = new Quad(4.5f, 4.5f); + quadMesh3D.scaleTextureCoordinates(new Vector2f(4, 4)); + quad = new Geometry("previewQuad", quadMesh); + quad.setLocalTranslation(new Vector3f(-2.25f, -2.25f, 0)); + quad3D = new Geometry("previewQuad", quadMesh3D); + quad3D.setLocalTranslation(new Vector3f(-2.25f, -2.25f, 0)); + material3D = new Material(assetManager, "com/jme3/gde/core/properties/preview/tex3DThumb.j3md"); + material3D.setFloat("InvDepth", 1f / 16f); + material3D.setInt("Rows", 4); + material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + SceneApplication.getApplication().addSceneListener(this); + } + + public void requestPreview(final String textureName, final String displayName, final int width, final int height, final JComponent picLabel, final JLabel infoLabel) { + + picPreview = picLabel; + clearPreview(); + if (infoLabel != null) { + infoLabel.setText(" Creating preview..."); + } + + SceneApplication.getApplication().enqueue(new Callable() { + + @Override + public Void call() throws Exception { + TextureKey key = new TextureKey(textureName); + Texture t = assetManager.loadTexture(key); + Spatial geom = quad; + if (key.getTextureTypeHint() == Texture.Type.TwoDimensional) { + material.setTexture("ColorMap", t); + geom.setMaterial(material); + setLabel(infoLabel, displayName, t.getImage().getWidth(), t.getImage().getHeight(), -1); + } else if (key.getTextureTypeHint() == Texture.Type.ThreeDimensional) { + geom = quad3D; + material3D.setTexture("Texture", t); + geom.setMaterial(material3D); + setLabel(infoLabel, displayName + " (Texture3D)", t.getImage().getWidth(), t.getImage().getHeight(), t.getImage().getDepth()); + + } else if (key.getTextureTypeHint() == Texture.Type.CubeMap) { + geom = SkyFactory.createSky(assetManager, textureName, SkyFactory.EnvMapType.CubeMap); + setLabel(infoLabel, displayName + " (CubeMap)", t.getImage().getWidth(), t.getImage().getHeight(), -1); + } + + PreviewRequest request = new PreviewRequest(TexturePreview.this, geom, width, height); + request.getCameraRequest().setLocation(new Vector3f(0, 0, 5.3f)); + request.getCameraRequest().setLookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y.mult(-1)); + SceneApplication.getApplication().createPreview(request); + + return null; + } + + }); + } + + public void cleanUp() { + SceneApplication.getApplication().removeSceneListener(this); + } + + @Override + public void sceneOpened(SceneRequest request) { + } + + @Override + public void sceneClosed(SceneRequest request) { + } + + private void setLabel(final JLabel label, final String text, final int width, final int height, final int depth) { + + java.awt.EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + if (label != null) { + String labText = " " + text + " w : " + width + " h : " + height; + if (depth > 0) { + labText += " d : " + depth; + } + label.setText(labText); + } + } + }); + } + + private void clearPreview() { + if (picPreview instanceof JLabel) { + ((JLabel) picPreview).setIcon(null); + } + if (picPreview instanceof JButton) { + ((JButton) picPreview).setIcon(null); + } + } + + @Override + public void previewCreated(final PreviewRequest request) { + java.awt.EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + if (request.getRequester() == TexturePreview.this) { + final ImageIcon icon = new ImageIcon(request.getImage()); + if (picPreview instanceof JLabel) { + ((JLabel) picPreview).setIcon(icon); + } + if (picPreview instanceof JButton) { + ((JButton) picPreview).setIcon(icon); + } + } + } + }); + } +} diff --git a/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.frag b/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.frag index f6eb25a3b..4ae52bd3f 100644 --- a/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.frag +++ b/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.frag @@ -5,10 +5,9 @@ uniform float m_InvDepth; varying vec2 texCoord; void main(){ -float depthx=floor(texCoord.x); - float depthy=(m_Rows-1.0) - floor(texCoord.y); - //vec3 texC=vec3(texCoord.x,texCoord.y ,0.7);// + float depthx = floor(texCoord.x); + float depthy = (float(m_Rows) - 1.0) - floor(texCoord.y); - vec3 texC=vec3(fract(texCoord.x),fract(texCoord.y),(depthy*m_Rows+depthx)*m_InvDepth);// - gl_FragColor= texture3D(m_Texture,texC); + vec3 texC = vec3(fract(texCoord.x), fract(texCoord.y), (depthy * float(m_Rows) + depthx) * m_InvDepth);// + gl_FragColor = texture3D(m_Texture, texC); } \ No newline at end of file diff --git a/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.vert b/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.vert index 6d27bc030..fd940682b 100644 --- a/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.vert +++ b/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.vert @@ -6,6 +6,6 @@ attribute vec3 inPosition; varying vec2 texCoord; void main(){ - gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0); - texCoord=inTexCoord; + gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0); + texCoord = inTexCoord; } \ No newline at end of file diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/materials/multiview/widgets/TexturePanel.java b/sdk/jme3-materialeditor/src/com/jme3/gde/materials/multiview/widgets/TexturePanel.java index 0129535e4..1e5856fd1 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/materials/multiview/widgets/TexturePanel.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/materials/multiview/widgets/TexturePanel.java @@ -13,7 +13,7 @@ package com.jme3.gde.materials.multiview.widgets; import com.jme3.asset.AssetNotFoundException; import com.jme3.gde.core.assets.ProjectAssetManager; import com.jme3.gde.core.properties.TexturePropertyEditor; -import com.jme3.gde.core.properties.preview.DDSPreview; +import com.jme3.gde.core.properties.preview.TexturePreview; import com.jme3.gde.materials.MaterialProperty; import com.jme3.gde.materials.multiview.MaterialEditorTopComponent; import com.jme3.texture.Texture; @@ -39,7 +39,7 @@ public class TexturePanel extends MaterialPropertyWidget { private boolean flip = false; private boolean repeat = false; private String textureName = null; - private DDSPreview ddsPreview; + private TexturePreview texPreview; private final ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1); /** Creates new form SelectionPanel */ @@ -53,22 +53,13 @@ public class TexturePanel extends MaterialPropertyWidget { if (!"".equals(textureName)) { exec.execute(new Runnable() { + @Override public void run() { try{ - Texture tex = manager.loadTexture(textureName); - if (textureName.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview(manager); + if (texPreview == null) { + texPreview = new TexturePreview(manager); } - ddsPreview.requestPreview(textureName, "", 80, 80, texturePreview, null); - } else { - final Icon newicon = ImageUtilities.image2Icon(resizeImage(ImageToAwt.convert(tex.getImage(), false, true, 0))); - SwingUtilities.invokeLater(new Runnable() { - public void run() { - texturePreview.setIcon(newicon); - } - }); - } + texPreview.requestPreview(textureName, "", 80, 25, texturePreview, null); } catch (AssetNotFoundException a) { Logger.getLogger(MaterialEditorTopComponent.class.getName()).log(Level.WARNING, "Could not load texture {0}", textureName); } @@ -318,8 +309,8 @@ public class TexturePanel extends MaterialPropertyWidget { @Override public void cleanUp() { - if (ddsPreview != null) { - ddsPreview.cleanUp(); + if (texPreview != null) { + texPreview.cleanUp(); } exec.shutdownNow(); } diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainEditorTopComponent.java b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainEditorTopComponent.java index 9e12371ea..b99a24274 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainEditorTopComponent.java +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainEditorTopComponent.java @@ -34,7 +34,7 @@ package com.jme3.gde.terraineditor; import com.jme3.gde.core.assets.AssetDataObject; import com.jme3.gde.core.assets.ProjectAssetManager; import com.jme3.gde.core.properties.TexturePropertyEditor; -import com.jme3.gde.core.properties.preview.DDSPreview; +import com.jme3.gde.core.properties.preview.TexturePreview; import com.jme3.gde.core.scene.PreviewRequest; import com.jme3.gde.core.scene.SceneApplication; import com.jme3.gde.core.scene.SceneListener; @@ -122,7 +122,7 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce //private TerrainNodeListener terrainDeletedNodeListener; private boolean availableNormalTextures; private HelpCtx ctx = new HelpCtx("sdk.terrain_editor"); - private DDSPreview ddsPreview; + private TexturePreview texPreview; private Map buttons = new HashMap(); private JPanel insideToolSettings; @@ -153,35 +153,29 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce private float max = 0; private final Object lock = new Object(); + @Override public void incrementProgress(float f) { progress += f; progressHandle.progress((int) progress); } + @Override public void setMonitorMax(float f) { max = f; -// java.awt.EventQueue.invokeLater(new Runnable() { -// public void run() { -// synchronized(lock){ if (progressHandle == null) { progressHandle = ProgressHandleFactory.createHandle("Calculating terrain entropies..."); progressHandle.start((int) max); } -// } -// } -// }); } + @Override public float getMonitorMax() { return max; } + @Override public void progressComplete() { -// SwingUtilities.invokeLater(new Runnable() { -// public void run() { progressHandle.finish(); -// } -// }); } } @@ -1631,6 +1625,13 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce protected abstract Texture getTextureFromModel(int index); protected abstract boolean supportsNullTexture(); + + private TexturePreview getTexturePreview(){ + if (texPreview == null) { + texPreview = new TexturePreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); + } + return texPreview; + } private JButton getButton(Object value, final int row, final int column) { @@ -1656,21 +1657,9 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce } Texture tex = getTextureFromModel(index); // delegate to sub-class - - //Texture tex = SceneApplication.getApplication().getAssetManager().loadTexture((String)value); if (tex != null) { String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, lbl, null); - - } else { - Icon icon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - lbl.setIcon(icon); - } + getTexturePreview().requestPreview(selected, "", 80, 80, lbl, null); } } @@ -1694,24 +1683,17 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce TexturePropertyEditor editor = new TexturePropertyEditor(selectedTex); Component view = editor.getCustomEditor(); view.setVisible(true); - Texture tex = (Texture) editor.getValue(); - if (editor.getValue() != null) { - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, lbl, null); - - } else { - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - lbl.setIcon(newicon); - } + + if (editor.getAsText() != null) { + + String selected = editor.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, lbl, null); + Texture tex = SceneApplication.getApplication().getAssetManager().loadTexture(selected); + setTextureInModel(row, tex); } else if (supportsNullTexture()) { lbl.setIcon(null); } - setTextureInModel(row, tex); + } finally { alreadyChoosing = false; } diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainToolController.java b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainToolController.java index e89175b76..d0072d06a 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainToolController.java +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainToolController.java @@ -260,8 +260,7 @@ public class TerrainToolController extends SceneToolController { * The action on the tool has ended (mouse button up), record the Undo (for painting only now) */ void doTerrainEditToolActionEnded() { - if (terrainTool != null) { - System.out.println("undo tagged"); + if (terrainTool != null) { terrainTool.actionEnded(jmeRootNode, editorController.getCurrentDataObject()); } } diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/AddSkyboxAction.java b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/AddSkyboxAction.java index b92aed679..146f1eb17 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/AddSkyboxAction.java +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/AddSkyboxAction.java @@ -69,14 +69,16 @@ public class AddSkyboxAction extends AbstractNewSpatialWizardAction { } else { Texture textureSingle = (Texture) wiz.getProperty("textureSingle"); Vector3f normalScale = (Vector3f) wiz.getProperty("normalScale"); - boolean useSpheremap = (Boolean) wiz.getProperty("useSpheremap"); + SkyFactory.EnvMapType type = (SkyFactory.EnvMapType) wiz.getProperty("envMapType"); boolean flipY = (Boolean) wiz.getProperty("flipY"); // reload the texture so we can use flipY TextureKey key = (TextureKey) textureSingle.getKey(); TextureKey newKey = new TextureKey(key.getName(), flipY); newKey.setGenerateMips(true); - newKey.setAsCube(!useSpheremap); - return SkyFactory.createSky(pm, pm.loadTexture(newKey), normalScale, useSpheremap); + if(type == SkyFactory.EnvMapType.CubeMap){ + newKey.setTextureTypeHint(Texture.Type.CubeMap); + } + return SkyFactory.createSky(pm, pm.loadTexture(newKey), normalScale, type); } } diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/Bundle.properties b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/Bundle.properties index 794a01ca7..550d79478 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/Bundle.properties +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/Bundle.properties @@ -17,7 +17,6 @@ SkyboxVisualPanel2.multipleTexWestLoadButton.text=Load SkyboxVisualPanel2.multipleTexTopLoadButton.text=Load SkyboxVisualPanel2.multipleTexBottomLoadButton.text=Load SkyboxVisualPanel2.jLabel9.text=Normal Scale (x,y,z): -SkyboxVisualPanel2.spheremapCheckBox.text=Sphere map SkyboxVisualPanel2.normal1X.text=1 SkyboxVisualPanel2.normal1Y.text=1 SkyboxVisualPanel2.normal1Z.text=1 @@ -32,3 +31,4 @@ SkyboxVisualPanel2.topPic.text= SkyboxVisualPanel2.bottomPic.text= SkyboxVisualPanel2.singlePic.text= SkyboxVisualPanel2.flipYcheckBox.text=Flip Y +SkyboxVisualPanel2.jLabel10.text=Map type diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.form b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.form index 291361d1e..26439c888 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.form +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.form @@ -1,4 +1,4 @@ - +
@@ -127,7 +127,7 @@ - + @@ -172,7 +172,7 @@ - + @@ -376,11 +376,17 @@ + - + + + + + + - + @@ -391,12 +397,8 @@ - - - - - + @@ -420,7 +422,10 @@ - + + + + @@ -474,24 +479,39 @@ - + - + - + - + - + + + + + + + + + + + + + + + + - + diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.java b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.java index 7abe313dd..9e3cd0396 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.java +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.java @@ -33,10 +33,12 @@ package com.jme3.gde.terraineditor.sky; import com.jme3.gde.core.assets.ProjectAssetManager; import com.jme3.gde.core.properties.TexturePropertyEditor; -import com.jme3.gde.core.properties.preview.DDSPreview; +import com.jme3.gde.core.properties.preview.TexturePreview; import com.jme3.gde.core.scene.SceneApplication; import com.jme3.texture.Texture; +import com.jme3.util.SkyFactory; import java.awt.Component; +import javax.swing.DefaultComboBoxModel; import javax.swing.Icon; import javax.swing.JCheckBox; import javax.swing.JPanel; @@ -46,11 +48,17 @@ import org.openide.util.ImageUtilities; public final class SkyboxVisualPanel2 extends JPanel { - private DDSPreview ddsPreview; + private TexturePreview texPreview; /** Creates new form SkyboxVisualPanel2 */ public SkyboxVisualPanel2() { initComponents(); + + DefaultComboBoxModel model = new DefaultComboBoxModel(); + for (SkyFactory.EnvMapType value : SkyFactory.EnvMapType.values()) { + model.addElement(value); + } + mapTypeCombo.setModel(model); } @Override @@ -101,6 +109,14 @@ public final class SkyboxVisualPanel2 extends JPanel { return editorWest; } + + private TexturePreview getTexturePreview(){ + if (texPreview == null) { + texPreview = new TexturePreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); + } + return texPreview; + } + /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is @@ -140,11 +156,12 @@ public final class SkyboxVisualPanel2 extends JPanel { normal2X = new javax.swing.JTextField(); normal2Y = new javax.swing.JTextField(); normal2Z = new javax.swing.JTextField(); - spheremapCheckBox = new javax.swing.JCheckBox(); singlePic = new javax.swing.JLabel(); flipYcheckBox = new javax.swing.JCheckBox(); + mapTypeCombo = new javax.swing.JComboBox(); + jLabel10 = new javax.swing.JLabel(); - titleLabel.setFont(new java.awt.Font("Tahoma", 1, 14)); + titleLabel.setFont(new java.awt.Font("Tahoma", 1, 14)); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(titleLabel, org.openide.util.NbBundle.getMessage(SkyboxVisualPanel2.class, "SkyboxVisualPanel2.titleLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(SkyboxVisualPanel2.class, "SkyboxVisualPanel2.jLabel1.text")); // NOI18N @@ -279,7 +296,7 @@ public final class SkyboxVisualPanel2 extends JPanel { .addComponent(multipleTexTopLoadButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(topPic, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE))))) - .addContainerGap(27, Short.MAX_VALUE)) + .addContainerGap(29, Short.MAX_VALUE)) ); multipleTexturePanelLayout.setVerticalGroup( multipleTexturePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -315,7 +332,7 @@ public final class SkyboxVisualPanel2 extends JPanel { .addGroup(multipleTexturePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel5) .addComponent(multipleTexTopLoadButton)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addGroup(multipleTexturePanelLayout.createSequentialGroup() .addComponent(westPic, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) @@ -346,12 +363,19 @@ public final class SkyboxVisualPanel2 extends JPanel { normal2Z.setText(org.openide.util.NbBundle.getMessage(SkyboxVisualPanel2.class, "SkyboxVisualPanel2.normal2Z.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(spheremapCheckBox, org.openide.util.NbBundle.getMessage(SkyboxVisualPanel2.class, "SkyboxVisualPanel2.spheremapCheckBox.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(singlePic, org.openide.util.NbBundle.getMessage(SkyboxVisualPanel2.class, "SkyboxVisualPanel2.singlePic.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(flipYcheckBox, org.openide.util.NbBundle.getMessage(SkyboxVisualPanel2.class, "SkyboxVisualPanel2.flipYcheckBox.text")); // NOI18N + mapTypeCombo.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); + mapTypeCombo.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + mapTypeComboActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel10, org.openide.util.NbBundle.getMessage(SkyboxVisualPanel2.class, "SkyboxVisualPanel2.jLabel10.text")); // NOI18N + javax.swing.GroupLayout singleTexturePanelLayout = new javax.swing.GroupLayout(singleTexturePanel); singleTexturePanel.setLayout(singleTexturePanelLayout); singleTexturePanelLayout.setHorizontalGroup( @@ -359,11 +383,16 @@ public final class SkyboxVisualPanel2 extends JPanel { .addGroup(singleTexturePanelLayout.createSequentialGroup() .addContainerGap() .addGroup(singleTexturePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(flipYcheckBox, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(singleTexturePanelLayout.createSequentialGroup() - .addComponent(jLabel8) + .addComponent(jLabel10) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(mapTypeCombo, javax.swing.GroupLayout.PREFERRED_SIZE, 166, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(singleTexturePanelLayout.createSequentialGroup() + .addComponent(jLabel8) + .addGap(21, 21, 21) .addComponent(singleTexLoadButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGap(2, 2, 2) .addComponent(singlePic, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(39, 39, 39) .addComponent(jLabel9) @@ -372,11 +401,8 @@ public final class SkyboxVisualPanel2 extends JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(normal2Y, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(6, 6, 6) - .addComponent(normal2Z, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(singleTexturePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addComponent(flipYcheckBox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(spheremapCheckBox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) - .addContainerGap(31, Short.MAX_VALUE)) + .addComponent(normal2Z, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); singleTexturePanelLayout.setVerticalGroup( singleTexturePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -394,7 +420,9 @@ public final class SkyboxVisualPanel2 extends JPanel { .addComponent(normal2Z, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel9)))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(spheremapCheckBox) + .addGroup(singleTexturePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(mapTypeCombo, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel10)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(flipYcheckBox) .addContainerGap(75, Short.MAX_VALUE)) @@ -428,148 +456,76 @@ public final class SkyboxVisualPanel2 extends JPanel { private void multipleTexSouthLoadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_multipleTexSouthLoadButtonActionPerformed Component view = editorSouth.getCustomEditor(); view.setVisible(true); - if (editorSouth.getValue() != null) { - Texture tex = (Texture) editorSouth.getValue(); - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, southPic, null); - - } else { - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - southPic.setIcon(newicon); - } + if (editorSouth.getAsText()!= null) { + String selected = editorSouth.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, southPic, null); } }//GEN-LAST:event_multipleTexSouthLoadButtonActionPerformed private void multipleTexNorthLoadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_multipleTexNorthLoadButtonActionPerformed Component view = editorNorth.getCustomEditor(); view.setVisible(true); - if (editorNorth.getValue() != null) { - Texture tex = (Texture) editorNorth.getValue(); - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, northPic, null); - - } else { - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - northPic.setIcon(newicon); - } + if (editorNorth.getAsText() != null) { + String selected = editorNorth.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, northPic, null); } }//GEN-LAST:event_multipleTexNorthLoadButtonActionPerformed private void multipleTexEastLoadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_multipleTexEastLoadButtonActionPerformed Component view = editorEast.getCustomEditor(); view.setVisible(true); - if (editorEast.getValue() != null) { - Texture tex = (Texture) editorEast.getValue(); - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, eastPic, null); - - } else { - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - eastPic.setIcon(newicon); - } + if (editorEast.getAsText() != null) { + String selected = editorEast.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, eastPic, null); } }//GEN-LAST:event_multipleTexEastLoadButtonActionPerformed private void multipleTexWestLoadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_multipleTexWestLoadButtonActionPerformed Component view = editorWest.getCustomEditor(); view.setVisible(true); - if (editorWest.getValue() != null) { - Texture tex = (Texture) editorWest.getValue(); - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, westPic, null); - - } else { - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - westPic.setIcon(newicon); - } + if (editorWest.getAsText() != null) { + String selected = editorWest.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, westPic, null); } }//GEN-LAST:event_multipleTexWestLoadButtonActionPerformed private void multipleTexTopLoadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_multipleTexTopLoadButtonActionPerformed Component view = editorTop.getCustomEditor(); view.setVisible(true); - if (editorTop.getValue() != null) { - Texture tex = (Texture) editorTop.getValue(); - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, topPic, null); - - } else { - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - topPic.setIcon(newicon); - } + if (editorTop.getAsText() != null) { + String selected = editorTop.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, topPic, null); } }//GEN-LAST:event_multipleTexTopLoadButtonActionPerformed private void multipleTexBottomLoadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_multipleTexBottomLoadButtonActionPerformed Component view = editorBottom.getCustomEditor(); view.setVisible(true); - if (editorBottom.getValue() != null) { - Texture tex = (Texture) editorBottom.getValue(); - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, bottomPic, null); - - } else { - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - bottomPic.setIcon(newicon); - } + if (editorBottom.getAsText() != null) { + String selected = editorBottom.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, bottomPic, null); } }//GEN-LAST:event_multipleTexBottomLoadButtonActionPerformed private void singleTexLoadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_singleTexLoadButtonActionPerformed Component view = editorSingle.getCustomEditor(); view.setVisible(true); - if (editorSingle.getValue() != null) { - Texture tex = (Texture) editorSingle.getValue(); - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, singlePic, null); - - } else { - - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - singlePic.setIcon(newicon); - } + if (editorSingle.getAsText()!= null) { + String selected = editorSingle.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, singlePic, null); } }//GEN-LAST:event_singleTexLoadButtonActionPerformed + + private void mapTypeComboActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mapTypeComboActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_mapTypeComboActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel bottomPic; private javax.swing.JLabel eastPic; private javax.swing.JCheckBox flipYcheckBox; private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel10; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JLabel jLabel4; @@ -578,6 +534,7 @@ public final class SkyboxVisualPanel2 extends JPanel { private javax.swing.JLabel jLabel7; private javax.swing.JLabel jLabel8; private javax.swing.JLabel jLabel9; + private javax.swing.JComboBox mapTypeCombo; private javax.swing.JButton multipleTexBottomLoadButton; private javax.swing.JButton multipleTexEastLoadButton; private javax.swing.JButton multipleTexNorthLoadButton; @@ -596,7 +553,6 @@ public final class SkyboxVisualPanel2 extends JPanel { private javax.swing.JButton singleTexLoadButton; private javax.swing.JPanel singleTexturePanel; private javax.swing.JLabel southPic; - private javax.swing.JCheckBox spheremapCheckBox; private javax.swing.JLabel titleLabel; private javax.swing.JLabel topPic; private javax.swing.JLabel westPic; @@ -626,10 +582,11 @@ public final class SkyboxVisualPanel2 extends JPanel { return normal2Z; } - public JCheckBox getSpheremapCheckBox() { - return spheremapCheckBox; + public SkyFactory.EnvMapType getEnvMapType(){ + return (SkyFactory.EnvMapType)mapTypeCombo.getSelectedItem(); } + public JCheckBox getFlipYCheckBox() { return flipYcheckBox; } diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxWizardPanel2.java b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxWizardPanel2.java index 67ab8e0f7..267751ab4 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxWizardPanel2.java +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxWizardPanel2.java @@ -123,6 +123,7 @@ public class SkyboxWizardPanel2 implements WizardDescriptor.Panel { } } + @Override public void storeSettings(Object settings) { WizardDescriptor wiz = (WizardDescriptor) settings; SkyboxVisualPanel2 comp = (SkyboxVisualPanel2) getComponent(); @@ -143,7 +144,7 @@ public class SkyboxWizardPanel2 implements WizardDescriptor.Panel { float y = new Float(comp.getNormal2Y().getText()); float z = new Float(comp.getNormal2Z().getText()); wiz.putProperty("normalScale", new Vector3f(x,y,z) ); - wiz.putProperty("useSpheremap", comp.getSpheremapCheckBox().isSelected()); + wiz.putProperty("envMapType", comp.getEnvMapType()); wiz.putProperty("flipY", comp.getFlipYCheckBox().isSelected()); } }