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 c215d0677..c412c07b3 100644 --- a/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java +++ b/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java @@ -32,36 +32,19 @@ package com.jme3.asset; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Queue; -import com.jme3.animation.Animation; -import com.jme3.bounding.BoundingVolume; -import com.jme3.collision.Collidable; -import com.jme3.collision.CollisionResults; -import com.jme3.collision.UnsupportedCollisionException; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import com.jme3.material.Material; import com.jme3.material.RenderState.FaceCullMode; -import com.jme3.math.ColorRGBA; -import com.jme3.post.Filter; -import com.jme3.scene.CameraNode; -import com.jme3.scene.LightNode; -import com.jme3.scene.Node; -import com.jme3.scene.SceneGraphVisitor; -import com.jme3.scene.Spatial; -import com.jme3.texture.Texture; /** * Blender key. Contains path of the blender file and its loading properties. * @author Marcin Roguski (Kaelthas) */ public class BlenderKey extends ModelKey { - protected static final int DEFAULT_FPS = 25; /** * FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time @@ -72,7 +55,7 @@ public class BlenderKey extends ModelKey { * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded. */ protected int featuresToLoad = FeaturesToLoad.ALL; - /** This variable determines if assets that are not linked to the objects should be loaded. */ + /** The variable that tells if content of the file (along with data unlinked to any feature on the scene) should be stored as 'user data' in the result spatial. */ protected boolean loadUnlinkedAssets; /** The root path for all the assets. */ protected String assetRootPath; @@ -268,6 +251,7 @@ public class BlenderKey extends ModelKey { * @param featuresToLoad * bitwise flag of FeaturesToLoad interface values */ + @Deprecated public void includeInLoading(int featuresToLoad) { this.featuresToLoad |= featuresToLoad; } @@ -277,10 +261,12 @@ public class BlenderKey extends ModelKey { * @param featuresNotToLoad * bitwise flag of FeaturesToLoad interface values */ + @Deprecated public void excludeFromLoading(int featuresNotToLoad) { featuresToLoad &= ~featuresNotToLoad; } + @Deprecated public boolean shouldLoad(int featureToLoad) { return (featuresToLoad & featureToLoad) != 0; } @@ -290,6 +276,7 @@ public class BlenderKey extends ModelKey { * the blender file loader. * @return features that will be loaded by the blender file loader */ + @Deprecated public int getFeaturesToLoad() { return featuresToLoad; } @@ -317,15 +304,6 @@ public class BlenderKey extends ModelKey { this.loadUnlinkedAssets = loadUnlinkedAssets; } - /** - * This method creates an object where loading results will be stores. Only those features will be allowed to store - * that were specified by features-to-load flag. - * @return an object to store loading results - */ - public LoadingResults prepareLoadingResults() { - return new LoadingResults(featuresToLoad); - } - /** * This method sets the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By default Y * is up axis. @@ -699,8 +677,11 @@ public class BlenderKey extends ModelKey { /** * This interface describes the features of the scene that are to be loaded. + * @deprecated this interface is deprecated and is not used anymore; to ensure the loading models consistency + * everything must be loaded because in blender one feature might depend on another * @author Marcin Roguski (Kaelthas) */ + @Deprecated public static interface FeaturesToLoad { int SCENES = 0x0000FFFF; @@ -745,281 +726,4 @@ public class BlenderKey extends ModelKey { */ ALL_NAMES_MATCH; } - - /** - * This class holds the loading results according to the given loading flag. - * @author Marcin Roguski (Kaelthas) - */ - public static class LoadingResults extends Spatial { - - /** Bitwise mask of features that are to be loaded. */ - private final int featuresToLoad; - /** The scenes from the file. */ - private List scenes; - /** Objects from all scenes. */ - private List objects; - /** Materials from all objects. */ - private List materials; - /** Textures from all objects. */ - private List textures; - /** Animations of all objects. */ - private List animations; - /** All cameras from the file. */ - private List cameras; - /** All lights from the file. */ - private List lights; - /** Loaded sky. */ - private Spatial sky; - /** Scene filters (ie. FOG). */ - private List filters; - /** - * The background color of the render loaded from the horizon color of the world. If no world is used than the gray color - * is set to default (as in blender editor. - */ - private ColorRGBA backgroundColor = ColorRGBA.Gray; - - /** - * Private constructor prevents users to create an instance of this class from outside the - * @param featuresToLoad - * bitwise mask of features that are to be loaded - * @see FeaturesToLoad FeaturesToLoad - */ - private LoadingResults(int featuresToLoad) { - this.featuresToLoad = featuresToLoad; - if ((featuresToLoad & FeaturesToLoad.SCENES) != 0) { - scenes = new ArrayList(); - } - if ((featuresToLoad & FeaturesToLoad.OBJECTS) != 0) { - objects = new ArrayList(); - if ((featuresToLoad & FeaturesToLoad.MATERIALS) != 0) { - materials = new ArrayList(); - if ((featuresToLoad & FeaturesToLoad.TEXTURES) != 0) { - textures = new ArrayList(); - } - } - if ((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) { - animations = new ArrayList(); - } - } - if ((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) { - cameras = new ArrayList(); - } - if ((featuresToLoad & FeaturesToLoad.LIGHTS) != 0) { - lights = new ArrayList(); - } - } - - /** - * This method returns a bitwise flag describing what features of the blend file will be included in the result. - * @return bitwise mask of features that are to be loaded - * @see FeaturesToLoad FeaturesToLoad - */ - public int getLoadedFeatures() { - return featuresToLoad; - } - - /** - * This method adds a scene to the result set. - * @param scene - * scene to be added to the result set - */ - public void addScene(Node scene) { - if (scenes != null) { - scenes.add(scene); - } - } - - /** - * This method adds an object to the result set. - * @param object - * object to be added to the result set - */ - public void addObject(Node object) { - if (objects != null) { - objects.add(object); - } - } - - /** - * This method adds a material to the result set. - * @param material - * material to be added to the result set - */ - public void addMaterial(Material material) { - if (materials != null) { - materials.add(material); - } - } - - /** - * This method adds a texture to the result set. - * @param texture - * texture to be added to the result set - */ - public void addTexture(Texture texture) { - if (textures != null) { - textures.add(texture); - } - } - - /** - * This method adds a camera to the result set. - * @param camera - * camera to be added to the result set - */ - public void addCamera(CameraNode camera) { - if (cameras != null) { - cameras.add(camera); - } - } - - /** - * This method adds a light to the result set. - * @param light - * light to be added to the result set - */ - public void addLight(LightNode light) { - if (lights != null) { - lights.add(light); - } - } - - /** - * This method sets the sky of the scene. Only one sky can be set. - * @param sky - * the sky to be set - */ - public void setSky(Spatial sky) { - this.sky = sky; - } - - /** - * This method adds a scene filter. Filters are used to load FOG or other - * scene effects that blender can define. - * @param filter - * the filter to be added - */ - public void addFilter(Filter filter) { - if (filter != null) { - if (filters == null) { - filters = new ArrayList(5); - } - filters.add(filter); - } - } - - /** - * @param backgroundColor - * the background color - */ - public void setBackgroundColor(ColorRGBA backgroundColor) { - this.backgroundColor = backgroundColor; - } - - /** - * @return all loaded scenes - */ - public List getScenes() { - return scenes; - } - - /** - * @return all loaded objects - */ - public List getObjects() { - return objects; - } - - /** - * @return all loaded materials - */ - public List getMaterials() { - return materials; - } - - /** - * @return all loaded textures - */ - public List getTextures() { - return textures; - } - - /** - * @return all loaded animations - */ - public List getAnimations() { - return animations; - } - - /** - * @return all loaded cameras - */ - public List getCameras() { - return cameras; - } - - /** - * @return all loaded lights - */ - public List getLights() { - return lights; - } - - /** - * @return the scene's sky - */ - public Spatial getSky() { - return sky; - } - - /** - * @return scene filters - */ - public List getFilters() { - return filters; - } - - /** - * @return the background color - */ - public ColorRGBA getBackgroundColor() { - return backgroundColor; - } - - @Override - public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException { - return 0; - } - - @Override - public void updateModelBound() { - } - - @Override - public void setModelBound(BoundingVolume modelBound) { - } - - @Override - public int getVertexCount() { - return 0; - } - - @Override - public int getTriangleCount() { - return 0; - } - - @Override - public Spatial deepClone() { - return null; - } - - @Override - public void depthFirstTraversal(SceneGraphVisitor visitor) { - } - - @Override - protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue queue) { - } - } } 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 eff993f76..e11101c14 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,17 +31,34 @@ */ 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; +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 @@ -49,14 +66,16 @@ import com.jme3.scene.plugins.blender.objects.Properties; * @author Marcin Roguski */ public abstract class AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(AbstractBlenderHelper.class.getName()); + /** The blender context. */ - protected BlenderContext blenderContext; + protected BlenderContext blenderContext; /** The version of the blend file. */ - protected final int blenderVersion; + protected final int blenderVersion; /** This variable indicates if the Y asxis is the UP axis or not. */ - protected boolean fixUpAxis; + protected boolean fixUpAxis; /** Quaternion used to rotate data when Y is up axis. */ - protected Quaternion upAxisRotationQuaternion; + protected Quaternion upAxisRotationQuaternion; /** * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender @@ -129,4 +148,115 @@ public abstract class AbstractBlenderHelper { } } } + + /** + * The method loads library of a given ID from linked blender file. + * @param id + * the ID of the linked feature (it contains its name and blender path) + * @return loaded feature or null if none was found + * @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()) { + String fullName = id.getFieldValue("name").toString();// we need full name with the prefix + String nameOfFeatureToLoad = id.getName(); + Structure library = pLib.fetchData().get(0); + 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); + } + } + + 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); + } + } + } else { + LOGGER.log(Level.WARNING, "No features loaded from path: {0}.", path); + } + } + + Object result = blenderContext.getLinkedFeature(path, fullName); + if (result == null) { + LOGGER.log(Level.WARNING, "Could NOT find asset named {0} in the library of path: {1}.", new Object[] { nameOfFeatureToLoad, path }); + } else { + blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.STRUCTURE, id); + blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.FEATURE, result); + } + return result; + } else { + LOGGER.warning("Library link points to nothing!"); + } + return null; + } } 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 cde38e327..6e8042b09 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 @@ -53,6 +53,7 @@ import com.jme3.scene.plugins.blender.constraints.Constraint; import com.jme3.scene.plugins.blender.file.BlenderInputStream; 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; /** @@ -64,49 +65,51 @@ import com.jme3.scene.plugins.blender.file.Structure; */ public class BlenderContext { /** The blender file version. */ - private int blenderVersion; + private int blenderVersion; /** The blender key. */ - private BlenderKey blenderKey; + private BlenderKey blenderKey; /** The header of the file block. */ - private DnaBlockData dnaBlockData; + private DnaBlockData dnaBlockData; /** The scene structure. */ - private Structure sceneStructure; + private Structure sceneStructure; /** The input stream of the blend file. */ - private BlenderInputStream inputStream; + private BlenderInputStream inputStream; /** The asset manager. */ - private AssetManager assetManager; + private AssetManager assetManager; /** The blocks read from the file. */ - protected List blocks; + protected List blocks; /** * A map containing the file block headers. The key is the old memory address. */ - private Map fileBlockHeadersByOma = new HashMap(); + private Map fileBlockHeadersByOma = new HashMap(); /** A map containing the file block headers. The key is the block code. */ - private Map> fileBlockHeadersByCode = new HashMap>(); + private Map> fileBlockHeadersByCode = new HashMap>(); /** * This map stores the loaded features by their old memory address. The * first object in the value table is the loaded structure and the second - * the structure already converted into proper data. */ - private Map> loadedFeatures = new HashMap>(); + private Map> loadedFeatures = new HashMap>(); + /** Features loaded from external blender files. The key is the file path and the value is a map between feature name and loaded feature. */ + private Map> linkedFeatures = new HashMap>(); /** A stack that hold the parent structure of currently loaded feature. */ - private Stack parentStack = new Stack(); + private Stack parentStack = new Stack(); /** A list of constraints for the specified object. */ - protected Map> constraints = new HashMap>(); + protected Map> constraints = new HashMap>(); /** Animations loaded for features. */ - private Map> animations = new HashMap>(); + private Map> animations = new HashMap>(); /** Loaded skeletons. */ - private Map skeletons = new HashMap(); + private Map skeletons = new HashMap(); /** A map between skeleton and node it modifies. */ - private Map nodesWithSkeletons = new HashMap(); + private Map nodesWithSkeletons = new HashMap(); /** A map of bone contexts. */ - protected Map boneContexts = new HashMap(); + protected Map boneContexts = new HashMap(); /** A map og helpers that perform loading. */ - private Map helpers = new HashMap(); + private Map helpers = new HashMap(); /** Markers used by loading classes to store some custom data. This is made to avoid putting this data into user properties. */ - private Map> markers = new HashMap>(); + private Map> markers = new HashMap>(); /** A map of blender actions. The key is the action name and the value is the action itself. */ - private Map actions = new HashMap(); + private Map actions = new HashMap(); /** * This method sets the blender file version. @@ -231,10 +234,10 @@ public class BlenderContext { */ public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) { fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader); - List headers = fileBlockHeadersByCode.get(Integer.valueOf(fileBlockHeader.getCode())); + List headers = fileBlockHeadersByCode.get(fileBlockHeader.getCode()); if (headers == null) { headers = new ArrayList(); - fileBlockHeadersByCode.put(Integer.valueOf(fileBlockHeader.getCode()), headers); + fileBlockHeadersByCode.put(fileBlockHeader.getCode(), headers); } headers.add(fileBlockHeader); } @@ -258,7 +261,7 @@ public class BlenderContext { * the code of file blocks * @return a list of file blocks' headers of a specified code */ - public List getFileBlocks(Integer code) { + public List getFileBlocks(BlockCode code) { return fileBlockHeadersByCode.get(code); } @@ -299,7 +302,7 @@ public class BlenderContext { throw new IllegalArgumentException("One of the given arguments is null!"); } Map map = loadedFeatures.get(oldMemoryAddress); - if(map == null) { + if (map == null) { map = new HashMap(); loadedFeatures.put(oldMemoryAddress, map); } @@ -325,6 +328,48 @@ public class BlenderContext { return null; } + /** + * The method adds linked content to the blender context. + * @param blenderFilePath + * the path of linked blender file + * @param featureName + * the linked feature name + * @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); + } + } + } + + /** + * The method returns linked feature of a given name from the specified blender path. + * @param blenderFilePath + * the blender file path + * @param featureName + * the feature name we want to get + * @return linked feature or null if none was found + */ + public Object getLinkedFeature(String blenderFilePath, String featureName) { + Map linkedFeatures = this.linkedFeatures.get(blenderFilePath); + return linkedFeatures != null ? linkedFeatures.get(featureName) : null; + } + + /** + * @return all linked features for the current blend file + */ + public Map> getLinkedFeatures() { + return linkedFeatures; + } + /** * This method adds the structure to the parent stack. * 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 200f83b39..dd0160063 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 @@ -33,17 +33,21 @@ package com.jme3.scene.plugins.blender; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import com.jme3.animation.Animation; import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetLoader; import com.jme3.asset.BlenderKey; -import com.jme3.asset.BlenderKey.FeaturesToLoad; -import com.jme3.asset.BlenderKey.LoadingResults; import com.jme3.asset.ModelKey; import com.jme3.light.Light; +import com.jme3.math.ColorRGBA; +import com.jme3.post.Filter; +import com.jme3.renderer.Camera; import com.jme3.scene.CameraNode; import com.jme3.scene.LightNode; import com.jme3.scene.Node; @@ -55,16 +59,20 @@ import com.jme3.scene.plugins.blender.curves.CurvesHelper; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.BlenderInputStream; import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.landscape.LandscapeHelper; import com.jme3.scene.plugins.blender.lights.LightHelper; +import com.jme3.scene.plugins.blender.materials.MaterialContext; import com.jme3.scene.plugins.blender.materials.MaterialHelper; import com.jme3.scene.plugins.blender.meshes.MeshHelper; +import com.jme3.scene.plugins.blender.meshes.TemporalMesh; import com.jme3.scene.plugins.blender.modifiers.ModifierHelper; import com.jme3.scene.plugins.blender.objects.ObjectHelper; import com.jme3.scene.plugins.blender.particles.ParticlesHelper; import com.jme3.scene.plugins.blender.textures.TextureHelper; +import com.jme3.texture.Texture; /** * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures. @@ -83,72 +91,130 @@ public class BlenderLoader implements AssetLoader { try { this.setup(assetInfo); - List sceneBlocks = new ArrayList(); - BlenderKey blenderKey = blenderContext.getBlenderKey(); - LoadingResults loadingResults = blenderKey.prepareLoadingResults(); - AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); animationHelper.loadAnimations(); - + + BlenderKey blenderKey = blenderContext.getBlenderKey(); + LoadedFeatures loadedFeatures = new LoadedFeatures(); for (FileBlockHeader block : blocks) { switch (block.getCode()) { - case FileBlockHeader.BLOCK_OB00:// Object + case BLOCK_OB00: ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - Object object = objectHelper.toObject(block.getStructure(blenderContext), blenderContext); - if (object instanceof LightNode) { - loadingResults.addLight((LightNode) object); - } else if (object instanceof CameraNode) { - loadingResults.addCamera((CameraNode) object); - } else if (object instanceof Node) { - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); - } - if (this.isRootObject(loadingResults, (Node) object)) { - loadingResults.addObject((Node) object); - } + Node object = (Node) objectHelper.toObject(block.getStructure(blenderContext), blenderContext); + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { object.getName(), object.getLocalTranslation().toString(), object.getParent() == null ? "null" : object.getParent().getName() }); } + if (object.getParent() == null) { + loadedFeatures.objects.add(object); + } + if (object instanceof LightNode && ((LightNode) object).getLight() != null) { + loadedFeatures.lights.add(((LightNode) object).getLight()); + } else if (object instanceof CameraNode && ((CameraNode) object).getCamera() != null) { + loadedFeatures.cameras.add(((CameraNode) object).getCamera()); + } + break; + case BLOCK_SC00:// Scene + loadedFeatures.sceneBlocks.add(block); break; -// case FileBlockHeader.BLOCK_MA00:// Material -// MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); -// MaterialContext materialContext = materialHelper.toMaterialContext(block.getStructure(blenderContext), blenderContext); -// if (blenderKey.isLoadUnlinkedAssets() && blenderKey.shouldLoad(FeaturesToLoad.MATERIALS)) { -// loadingResults.addMaterial(this.toMaterial(block.getStructure(blenderContext))); -// } -// break; - case FileBlockHeader.BLOCK_SC00:// Scene - if (blenderKey.shouldLoad(FeaturesToLoad.SCENES)) { - sceneBlocks.add(block); + case BLOCK_MA00:// Material + MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); + MaterialContext materialContext = materialHelper.toMaterialContext(block.getStructure(blenderContext), blenderContext); + loadedFeatures.materials.add(materialContext); + break; + case BLOCK_ME00:// Mesh + MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); + TemporalMesh temporalMesh = meshHelper.toTemporalMesh(block.getStructure(blenderContext), blenderContext); + loadedFeatures.meshes.add(temporalMesh); + break; + case BLOCK_IM00:// Image + TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); + Texture image = textureHelper.loadImageAsTexture(block.getStructure(blenderContext), 0, blenderContext); + if (image != null && image.getImage() != null) {// render results are stored as images but are not being loaded + loadedFeatures.images.add(image); } break; - case FileBlockHeader.BLOCK_WO00:// World - if (blenderKey.shouldLoad(FeaturesToLoad.WORLD)) { - Structure worldStructure = block.getStructure(blenderContext); - String worldName = worldStructure.getName(); - if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) { - LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class); - Light ambientLight = landscapeHelper.toAmbientLight(worldStructure); - if(ambientLight != null) { - loadingResults.addLight(new LightNode(null, ambientLight)); - } - loadingResults.setSky(landscapeHelper.toSky(worldStructure)); - loadingResults.addFilter(landscapeHelper.toFog(worldStructure)); - loadingResults.setBackgroundColor(landscapeHelper.toBackgroundColor(worldStructure)); + case BLOCK_TE00: + Structure textureStructure = block.getStructure(blenderContext); + int type = ((Number) textureStructure.getFieldValue("type")).intValue(); + if (type == TextureHelper.TEX_IMAGE) { + TextureHelper texHelper = blenderContext.getHelper(TextureHelper.class); + Texture texture = texHelper.getTexture(textureStructure, null, blenderContext); + if (texture != null) {// null is returned when texture has no image + loadedFeatures.textures.add(texture); } + } else { + LOGGER.fine("Only image textures can be loaded as unlinked assets. Generated textures will be applied to an existing object."); } break; + case BLOCK_WO00:// World + LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class); + Structure worldStructure = block.getStructure(blenderContext); + + String worldName = worldStructure.getName(); + if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) { + + Light ambientLight = landscapeHelper.toAmbientLight(worldStructure); + if (ambientLight != null) { + loadedFeatures.objects.add(new LightNode(null, ambientLight)); + loadedFeatures.lights.add(ambientLight); + } + loadedFeatures.sky = landscapeHelper.toSky(worldStructure); + loadedFeatures.backgroundColor = landscapeHelper.toBackgroundColor(worldStructure); + + Filter fogFilter = landscapeHelper.toFog(worldStructure); + if (fogFilter != null) { + loadedFeatures.filters.add(landscapeHelper.toFog(worldStructure)); + } + } + break; + case BLOCK_AC00: + LOGGER.fine("Loading unlinked animations is not yet supported!"); + break; + default: + LOGGER.log(Level.FINEST, "Ommiting the block: {0}.", block.getCode()); } } - // bake constraints after everything is loaded + LOGGER.fine("Baking constraints after every feature is loaded."); ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); constraintHelper.bakeConstraints(blenderContext); - // load the scene at the very end so that the root nodes have no parent during loading or constraints applying - for (FileBlockHeader sceneBlock : sceneBlocks) { - loadingResults.addScene(this.toScene(sceneBlock.getStructure(blenderContext))); + LOGGER.fine("Loading scenes and attaching them to the root object."); + for (FileBlockHeader sceneBlock : loadedFeatures.sceneBlocks) { + loadedFeatures.scenes.add(this.toScene(sceneBlock.getStructure(blenderContext))); + } + + LOGGER.fine("Creating the root node of the model and applying loaded nodes of the scene and loaded features to it."); + Node modelRoot = new Node(blenderKey.getName()); + for (Node scene : loadedFeatures.scenes) { + modelRoot.attachChild(scene); } - return loadingResults; + if (blenderKey.isLoadUnlinkedAssets()) { + LOGGER.fine("Setting loaded content as user data in resulting sptaial."); + Map> linkedData = new HashMap>(); + + Map thisFileData = new HashMap(); + thisFileData.put("scenes", loadedFeatures.scenes == null ? new ArrayList() : loadedFeatures.scenes); + thisFileData.put("objects", loadedFeatures.objects == null ? new ArrayList() : loadedFeatures.objects); + thisFileData.put("meshes", loadedFeatures.meshes == null ? new ArrayList() : loadedFeatures.meshes); + thisFileData.put("materials", loadedFeatures.materials == null ? new ArrayList() : loadedFeatures.materials); + thisFileData.put("textures", loadedFeatures.textures == null ? new ArrayList() : loadedFeatures.textures); + thisFileData.put("images", loadedFeatures.images == null ? new ArrayList() : loadedFeatures.images); + thisFileData.put("animations", loadedFeatures.animations == null ? new ArrayList() : loadedFeatures.animations); + thisFileData.put("cameras", loadedFeatures.cameras == null ? new ArrayList() : loadedFeatures.cameras); + thisFileData.put("lights", loadedFeatures.lights == null ? new ArrayList() : loadedFeatures.lights); + thisFileData.put("filters", loadedFeatures.filters == null ? new ArrayList() : loadedFeatures.filters); + thisFileData.put("backgroundColor", loadedFeatures.backgroundColor); + thisFileData.put("sky", loadedFeatures.sky); + + linkedData.put("this", thisFileData); + linkedData.putAll(blenderContext.getLinkedFeatures()); + + modelRoot.setUserData("linkedData", linkedData); + } + + return modelRoot; } catch (BlenderFileException e) { throw new IOException(e.getLocalizedMessage(), e); } catch (Exception e) { @@ -158,62 +224,36 @@ public class BlenderLoader implements AssetLoader { } } - /** - * This method indicates if the given spatial is a root object. It means it - * has no parent or is directly attached to one of the already loaded scene - * nodes. - * - * @param loadingResults - * loading results containing the scene nodes - * @param spatial - * spatial object - * @return true if the given spatial is a root object and - * false otherwise - */ - protected boolean isRootObject(LoadingResults loadingResults, Spatial spatial) { - if (spatial.getParent() == null) { - return true; - } - for (Node scene : loadingResults.getScenes()) { - if (spatial.getParent().equals(scene)) { - return true; - } - } - return false; - } - /** * This method converts the given structure to a scene node. * @param structure * structure of a scene * @return scene's node + * @throws BlenderFileException + * an exception throw when problems with blender file occur */ - private Node toScene(Structure structure) { + private Node toScene(Structure structure) throws BlenderFileException { ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); Node result = new Node(structure.getName()); - try { - List base = ((Structure) structure.getFieldValue("base")).evaluateListBase(); - for (Structure b : base) { - Pointer pObject = (Pointer) b.getFieldValue("object"); - if (pObject.isNotNull()) { - Structure objectStructure = pObject.fetchData().get(0); + List base = ((Structure) structure.getFieldValue("base")).evaluateListBase(); + for (Structure b : base) { + Pointer pObject = (Pointer) b.getFieldValue("object"); + if (pObject.isNotNull()) { + Structure objectStructure = pObject.fetchData().get(0); - Object object = objectHelper.toObject(objectStructure, blenderContext); - if (object instanceof LightNode) { - result.addLight(((LightNode) object).getLight()); - result.attachChild((LightNode) object); - } else if (object instanceof Node) { - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); - } - if (((Node) object).getParent() == null) { - result.attachChild((Spatial) object); - } + Object object = objectHelper.toObject(objectStructure, blenderContext); + if (object instanceof LightNode) { + result.addLight(((LightNode) object).getLight());// FIXME: check if this is needed !!! + result.attachChild((LightNode) object); + } else if (object instanceof Node) { + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); + } + if (((Node) object).getParent() == null) { + result.attachChild((Spatial) object); } } } - } catch (BlenderFileException e) { - LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); } return result; } @@ -261,7 +301,7 @@ public class BlenderLoader implements AssetLoader { blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext)); blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderContext)); blenderContext.putHelper(LandscapeHelper.class, new LandscapeHelper(inputStream.getVersionNumber(), blenderContext)); - + // reading the blocks (dna block is automatically saved in the blender context when found) FileBlockHeader sceneFileBlock = null; do { @@ -269,7 +309,7 @@ public class BlenderLoader implements AssetLoader { if (!fileBlock.isDnaBlock()) { blocks.add(fileBlock); // save the scene's file block - if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00) { + if (fileBlock.getCode() == BlockCode.BLOCK_SC00) { sceneFileBlock = fileBlock; } } @@ -287,4 +327,39 @@ public class BlenderLoader implements AssetLoader { blenderContext = null; blocks = null; } + + /** + * This class holds the loading results according to the given loading flag. + * @author Marcin Roguski (Kaelthas) + */ + private static class LoadedFeatures { + private List sceneBlocks = new ArrayList(); + /** The scenes from the file. */ + private List scenes = new ArrayList(); + /** Objects from all scenes. */ + private List objects = new ArrayList(); + /** All meshes. */ + private List meshes = new ArrayList(); + /** Materials from all objects. */ + private List materials = new ArrayList(); + /** Textures from all objects. */ + private List textures = new ArrayList(); + /** The images stored in the blender file. */ + private List images = new ArrayList(); + /** Animations of all objects. */ + private List animations = new ArrayList(); + /** All cameras from the file. */ + private List cameras = new ArrayList(); + /** All lights from the file. */ + private List lights = new ArrayList(); + /** Loaded sky. */ + private Spatial sky; + /** Scene filters (ie. FOG). */ + private List filters = new ArrayList(); + /** + * The background color of the render loaded from the horizon color of the world. If no world is used than the gray color + * is set to default (as in blender editor. + */ + private ColorRGBA backgroundColor = ColorRGBA.Gray; + } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java index 021a6082f..eae047421 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java @@ -31,79 +31,10 @@ */ package com.jme3.scene.plugins.blender; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.asset.AssetInfo; -import com.jme3.asset.BlenderKey; -import com.jme3.asset.BlenderKey.FeaturesToLoad; -import com.jme3.scene.LightNode; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.blender.animations.AnimationHelper; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.FileBlockHeader; -import com.jme3.scene.plugins.blender.objects.ObjectHelper; - /** * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures. - * + * @deprecated this class is deprecated; use BlenderLoader instead * @author Marcin Roguski (Kaelthas) */ public class BlenderModelLoader extends BlenderLoader { - - private static final Logger LOGGER = Logger.getLogger(BlenderModelLoader.class.getName()); - - @Override - public Spatial load(AssetInfo assetInfo) throws IOException { - try { - this.setup(assetInfo); - - AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); - animationHelper.loadAnimations(); - - BlenderKey blenderKey = blenderContext.getBlenderKey(); - List rootObjects = new ArrayList(); - for (FileBlockHeader block : blocks) { - if (block.getCode() == FileBlockHeader.BLOCK_OB00) { - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - Object object = objectHelper.toObject(block.getStructure(blenderContext), blenderContext); - if (object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { - rootObjects.add((LightNode) object); - } else if (object instanceof Node && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) { - LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); - if (((Node) object).getParent() == null) { - rootObjects.add((Node) object); - } - } - } - } - - // bake constraints after everything is loaded - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - constraintHelper.bakeConstraints(blenderContext); - - // attach the nodes to the root node at the very end so that the root objects have no parents during constraint applying - LOGGER.fine("Creating the root node of the model and applying loaded nodes of the scene to it."); - Node modelRoot = new Node(blenderKey.getName()); - for (Node node : rootObjects) { - if (node instanceof LightNode) { - modelRoot.addLight(((LightNode) node).getLight()); - } - modelRoot.attachChild(node); - } - - return modelRoot; - } catch (BlenderFileException e) { - throw new IOException(e.getLocalizedMessage(), e); - } catch (Exception e) { - throw new IOException("Unexpected importer exception occured: " + e.getLocalizedMessage(), e); - } finally { - this.clear(); - } - } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java index 1b5a40e79..f8c1cdad3 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java @@ -25,6 +25,7 @@ import com.jme3.scene.plugins.blender.curves.BezierCurve; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.BlenderInputStream; import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.objects.ObjectHelper; @@ -48,7 +49,7 @@ public class AnimationHelper extends AbstractBlenderHelper { */ public void loadAnimations() throws BlenderFileException { LOGGER.info("Loading animations that will be later applied to scene features."); - List actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); + List actionHeaders = blenderContext.getFileBlocks(BlockCode.BLOCK_AC00); if (actionHeaders != null) { for (FileBlockHeader header : actionHeaders) { Structure actionStructure = header.getStructure(blenderContext); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java index c5e1f0192..70cb09b1f 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java @@ -5,7 +5,6 @@ import java.util.logging.Logger; import com.jme3.math.FastMath; import com.jme3.renderer.Camera; -import com.jme3.scene.CameraNode; import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.file.BlenderFileException; @@ -43,7 +42,7 @@ public class CameraHelper extends AbstractBlenderHelper { * an exception is thrown when there are problems with the * blender file */ - public CameraNode toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + public Camera toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException { if (blenderVersion >= 250) { return this.toCamera250(structure, blenderContext.getSceneStructure()); } else { @@ -63,7 +62,7 @@ public class CameraHelper extends AbstractBlenderHelper { * an exception is thrown when there are problems with the * blender file */ - private CameraNode toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException { + private Camera toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException { int width = DEFAULT_CAM_WIDTH; int height = DEFAULT_CAM_HEIGHT; if (sceneStructure != null) { @@ -99,7 +98,7 @@ public class CameraHelper extends AbstractBlenderHelper { sensor = ((Number) structure.getFieldValue(sensorName)).floatValue(); } float focalLength = ((Number) structure.getFieldValue("lens")).floatValue(); - float fov = 2.0f * FastMath.atan((sensor / 2.0f) / focalLength); + float fov = 2.0f * FastMath.atan(sensor / 2.0f / focalLength); if (sensorVertical) { fovY = fov * FastMath.RAD_TO_DEG; } else { @@ -111,7 +110,8 @@ public class CameraHelper extends AbstractBlenderHelper { fovY = ((Number) structure.getFieldValue("ortho_scale")).floatValue(); } camera.setFrustumPerspective(fovY, aspect, clipsta, clipend); - return new CameraNode(null, camera); + camera.setName(structure.getName()); + return camera; } /** @@ -124,7 +124,7 @@ public class CameraHelper extends AbstractBlenderHelper { * an exception is thrown when there are problems with the * blender file */ - private CameraNode toCamera249(Structure structure) throws BlenderFileException { + private Camera toCamera249(Structure structure) throws BlenderFileException { Camera camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT); int type = ((Number) structure.getFieldValue("type")).intValue(); if (type != 0 && type != 1) { @@ -142,6 +142,7 @@ public class CameraHelper extends AbstractBlenderHelper { aspect = ((Number) structure.getFieldValue("ortho_scale")).floatValue(); } camera.setFrustumPerspective(aspect, camera.getWidth() / camera.getHeight(), clipsta, clipend); - return new CameraNode(null, camera); + camera.setName(structure.getName()); + return camera; } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java index 7bd634254..666da7896 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java @@ -31,6 +31,8 @@ */ package com.jme3.scene.plugins.blender.file; +import java.util.logging.Logger; + import com.jme3.scene.plugins.blender.BlenderContext; /** @@ -39,39 +41,23 @@ import com.jme3.scene.plugins.blender.BlenderContext; * @author Marcin Roguski */ public class FileBlockHeader { + private static final Logger LOGGER = Logger.getLogger(FileBlockHeader.class.getName()); - public static final int BLOCK_TE00 = 'T' << 24 | 'E' << 16; // TE00 - public static final int BLOCK_ME00 = 'M' << 24 | 'E' << 16; // ME00 - public static final int BLOCK_SR00 = 'S' << 24 | 'R' << 16; // SR00 - public static final int BLOCK_CA00 = 'C' << 24 | 'A' << 16; // CA00 - public static final int BLOCK_LA00 = 'L' << 24 | 'A' << 16; // LA00 - public static final int BLOCK_OB00 = 'O' << 24 | 'B' << 16; // OB00 - public static final int BLOCK_MA00 = 'M' << 24 | 'A' << 16; // MA00 - public static final int BLOCK_SC00 = 'S' << 24 | 'C' << 16; // SC00 - public static final int BLOCK_WO00 = 'W' << 24 | 'O' << 16; // WO00 - public static final int BLOCK_TX00 = 'T' << 24 | 'X' << 16; // TX00 - public static final int BLOCK_IP00 = 'I' << 24 | 'P' << 16; // IP00 - public static final int BLOCK_AC00 = 'A' << 24 | 'C' << 16; // AC00 - public static final int BLOCK_GLOB = 'G' << 24 | 'L' << 16 | 'O' << 8 | 'B'; // GLOB - public static final int BLOCK_REND = 'R' << 24 | 'E' << 16 | 'N' << 8 | 'D'; // REND - public static final int BLOCK_DATA = 'D' << 24 | 'A' << 16 | 'T' << 8 | 'A'; // DATA - public static final int BLOCK_DNA1 = 'D' << 24 | 'N' << 16 | 'A' << 8 | '1'; // DNA1 - public static final int BLOCK_ENDB = 'E' << 24 | 'N' << 16 | 'D' << 8 | 'B'; // ENDB /** Identifier of the file-block [4 bytes]. */ - private int code; + private BlockCode code; /** Total length of the data after the file-block-header [4 bytes]. */ - private int size; + private int size; /** * Memory address the structure was located when written to disk [4 or 8 bytes (defined in file header as a pointer * size)]. */ - private long oldMemoryAddress; + private long oldMemoryAddress; /** Index of the SDNA structure [4 bytes]. */ - private int sdnaIndex; + private int sdnaIndex; /** Number of structure located in this file-block [4 bytes]. */ - private int count; + private int count; /** Start position of the block's data in the stream. */ - private int blockPosition; + private int blockPosition; /** * Constructor. Loads the block header from the given stream during instance creation. @@ -84,13 +70,13 @@ public class FileBlockHeader { */ public FileBlockHeader(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException { inputStream.alignPosition(4); - code = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); + code = BlockCode.valueOf(inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte()); size = inputStream.readInt(); oldMemoryAddress = inputStream.readPointer(); sdnaIndex = inputStream.readInt(); count = inputStream.readInt(); blockPosition = inputStream.getPosition(); - if (FileBlockHeader.BLOCK_DNA1 == code) { + if (BlockCode.BLOCK_DNA1 == code) { blenderContext.setBlockData(new DnaBlockData(inputStream, blenderContext)); } else { inputStream.setPosition(blockPosition + size); @@ -116,7 +102,7 @@ public class FileBlockHeader { * This method returns the code of this data block. * @return the code of this data block */ - public int getCode() { + public BlockCode getCode() { return code; } @@ -157,7 +143,7 @@ public class FileBlockHeader { * @return true if this block is the last one in the file nad false otherwise */ public boolean isLastBlock() { - return FileBlockHeader.BLOCK_ENDB == code; + return BlockCode.BLOCK_ENDB == code; } /** @@ -165,25 +151,44 @@ public class FileBlockHeader { * @return true if this block is the SDNA block and false otherwise */ public boolean isDnaBlock() { - return FileBlockHeader.BLOCK_DNA1 == code; + return BlockCode.BLOCK_DNA1 == code; } @Override public String toString() { - return "FILE BLOCK HEADER [" + this.codeToString(code) + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]"; + return "FILE BLOCK HEADER [" + code.toString() + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]"; } - /** - * This method transforms the coded bloch id into a string value. - * @param code - * the id of the block - * @return the string value of the block id - */ - protected String codeToString(int code) { - char c1 = (char) ((code & 0xFF000000) >> 24); - char c2 = (char) ((code & 0xFF0000) >> 16); - char c3 = (char) ((code & 0xFF00) >> 8); - char c4 = (char) (code & 0xFF); - return String.valueOf(c1) + c2 + c3 + c4; + public static enum BlockCode { + BLOCK_ME00('M' << 24 | 'E' << 16), // mesh + BLOCK_CA00('C' << 24 | 'A' << 16), // camera + BLOCK_LA00('L' << 24 | 'A' << 16), // lamp + BLOCK_OB00('O' << 24 | 'B' << 16), // object + BLOCK_MA00('M' << 24 | 'A' << 16), // material + BLOCK_SC00('S' << 24 | 'C' << 16), // scene + BLOCK_WO00('W' << 24 | 'O' << 16), // world + BLOCK_TX00('T' << 24 | 'X' << 16), // texture + BLOCK_IP00('I' << 24 | 'P' << 16), // ipo + BLOCK_AC00('A' << 24 | 'C' << 16), // action + BLOCK_IM00('I' << 24 | 'M' << 16), // image + BLOCK_TE00('T' << 24 | 'E' << 16), BLOCK_WM00('W' << 24 | 'M' << 16), BLOCK_SR00('S' << 24 | 'R' << 16), BLOCK_SN00('S' << 24 | 'N' << 16), BLOCK_BR00('B' << 24 | 'R' << 16), BLOCK_LS00('L' << 24 | 'S' << 16), BLOCK_GLOB('G' << 24 | 'L' << 16 | 'O' << 8 | 'B'), BLOCK_REND('R' << 24 | 'E' << 16 | 'N' << 8 | 'D'), BLOCK_DATA('D' << 24 | 'A' << 16 | 'T' << 8 | 'A'), BLOCK_DNA1('D' << 24 | 'N' << 16 | 'A' << 8 | '1'), BLOCK_ENDB('E' << 24 | 'N' << 16 | 'D' << 8 | 'B'), BLOCK_TEST('T' << 24 | 'E' << 16 + | 'S' << 8 | 'T'), BLOCK_UNKN(0); + + private int code; + + private BlockCode(int code) { + this.code = code; + } + + public static BlockCode valueOf(int code) { + for (BlockCode blockCode : BlockCode.values()) { + if (blockCode.code == code) { + return blockCode; + } + } + byte[] codeBytes = new byte[] { (byte) (code >> 24 & 0xFF), (byte) (code >> 16 & 0xFF), (byte) (code >> 8 & 0xFF), (byte) (code & 0xFF) }; + LOGGER.warning("Unknown block header: " + new String(codeBytes)); + return BLOCK_UNKN; + } } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java index 4f0de7e04..941c7a8fc 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java @@ -254,7 +254,8 @@ public class Structure implements Cloneable { Structure id = (Structure) fieldValue; return id == null ? null : id.getFieldValue("name").toString().substring(2);// blender adds 2-charactes as a name prefix } - return null; + Object name = this.getFieldValue("name", null); + return name == null ? null : name.toString().substring(2); } @Override diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java index 17e38c92b..3ae866b7c 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java @@ -40,7 +40,6 @@ import com.jme3.light.PointLight; import com.jme3.light.SpotLight; import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; -import com.jme3.scene.LightNode; import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; @@ -67,8 +66,8 @@ public class LightHelper extends AbstractBlenderHelper { super(blenderVersion, blenderContext); } - public LightNode toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - LightNode result = (LightNode) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE); + public Light toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + Light result = (Light) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE); if (result != null) { return result; } @@ -111,6 +110,7 @@ public class LightHelper extends AbstractBlenderHelper { float g = ((Number) structure.getFieldValue("g")).floatValue(); float b = ((Number) structure.getFieldValue("b")).floatValue(); light.setColor(new ColorRGBA(r, g, b, 1.0f)); - return new LightNode(structure.getName(), light); + light.setName(structure.getName()); + return light; } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java index 6e67da967..51c7072b1 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java @@ -1,11 +1,15 @@ package com.jme3.scene.plugins.blender.materials; +import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.Savable; import com.jme3.material.Material; import com.jme3.material.RenderState.BlendMode; import com.jme3.material.RenderState.FaceCullMode; @@ -30,7 +34,7 @@ import com.jme3.util.BufferUtils; * This class holds the data about the material. * @author Marcin Roguski (Kaelthas) */ -public final class MaterialContext { +public final class MaterialContext implements Savable { private static final Logger LOGGER = Logger.getLogger(MaterialContext.class.getName()); // texture mapping types @@ -67,7 +71,7 @@ public final class MaterialContext { int diff_shader = ((Number) structure.getFieldValue("diff_shader")).intValue(); diffuseShader = DiffuseShader.values()[diff_shader]; ambientFactor = ((Number) structure.getFieldValue("amb")).floatValue(); - + if (shadeless) { float r = ((Number) structure.getFieldValue("r")).floatValue(); float g = ((Number) structure.getFieldValue("g")).floatValue(); @@ -107,6 +111,13 @@ public final class MaterialContext { this.transparent = transparent; } + /** + * @return the name of the material + */ + public String getName() { + return name; + } + /** * Applies material to a given geometry. * @@ -314,4 +325,14 @@ public final class MaterialContext { float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); return new ColorRGBA(r, g, b, alpha); } + + @Override + public void write(JmeExporter e) throws IOException { + throw new IOException("Material context is not for saving! It implements savable only to be passed to another blend file as a Savable in user data!"); + } + + @Override + public void read(JmeImporter e) throws IOException { + throw new IOException("Material context is not for loading! It implements savable only to be passed to another blend file as a Savable in user data!"); + } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java index 5fcccc389..0809cec9a 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java @@ -161,12 +161,17 @@ public class MaterialHelper extends AbstractBlenderHelper { * an exception is throw when problems with blend file occur */ public MaterialContext toMaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.log(Level.FINE, "Loading material."); MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE); if (result != null) { return result; } + if ("ID".equals(structure.getType())) { + LOGGER.fine("Loading material from external blend file."); + return (MaterialContext) this.loadLibrary(structure); + } + + LOGGER.fine("Loading material."); result = new MaterialContext(structure, blenderContext); LOGGER.log(Level.FINE, "Material''s name: {0}", result.name); Long oma = structure.getOldMemoryAddress(); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java index 92b236f4c..5284b964a 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java @@ -40,7 +40,6 @@ import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; -import com.jme3.asset.BlenderKey.FeaturesToLoad; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.Vector2f; @@ -52,7 +51,6 @@ import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.DynamicArray; 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.materials.MaterialHelper; import com.jme3.scene.plugins.blender.objects.Properties; @@ -106,17 +104,18 @@ public class MeshHelper extends AbstractBlenderHelper { return temporalMesh.clone(); } + if ("ID".equals(meshStructure.getType())) { + LOGGER.fine("Loading mesh from external blend file."); + return (TemporalMesh) this.loadLibrary(meshStructure); + } + String name = meshStructure.getName(); LOGGER.log(Level.FINE, "Reading mesh: {0}.", name); temporalMesh = new TemporalMesh(meshStructure, blenderContext); LOGGER.fine("Loading materials."); MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); - MaterialContext[] materials = null; - if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) { - materials = materialHelper.getMaterials(meshStructure, blenderContext); - } - temporalMesh.setMaterials(materials); + temporalMesh.setMaterials(materialHelper.getMaterials(meshStructure, blenderContext)); LOGGER.fine("Reading custom properties."); Properties properties = this.loadProperties(meshStructure, blenderContext); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java index 8e2dc2652..17c0d421e 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java @@ -40,12 +40,15 @@ import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; -import com.jme3.asset.BlenderKey.FeaturesToLoad; +import com.jme3.light.Light; import com.jme3.math.FastMath; import com.jme3.math.Matrix4f; import com.jme3.math.Transform; import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.CameraNode; import com.jme3.scene.Geometry; +import com.jme3.scene.LightNode; import com.jme3.scene.Mesh.Mode; import com.jme3.scene.Node; import com.jme3.scene.Spatial; @@ -106,39 +109,34 @@ public class ObjectHelper extends AbstractBlenderHelper { * an exception is thrown when the given data is inapropriate */ public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.fine("Loading blender object."); + Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedDataType.FEATURE); + if (loadedResult != null) { + return loadedResult; + } + LOGGER.fine("Loading blender object."); + if ("ID".equals(objectStructure.getType())) { + Node object = (Node) this.loadLibrary(objectStructure); + if (object.getParent() != null) { + LOGGER.log(Level.FINEST, "Detaching object {0}, loaded from external file, from its parent.", object); + object.getParent().detachChild(object); + } + return object; + } int type = ((Number) objectStructure.getFieldValue("type")).intValue(); ObjectType objectType = ObjectType.valueOf(type); LOGGER.log(Level.FINE, "Type of the object: {0}.", objectType); - if (objectType == ObjectType.LAMP && !blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.LIGHTS)) { - LOGGER.fine("Lamps are not included in loading."); - return null; - } - if (objectType == ObjectType.CAMERA && !blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.CAMERAS)) { - LOGGER.fine("Cameras are not included in loading."); - return null; - } - if (!blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.OBJECTS)) { - LOGGER.fine("Objects are not included in loading."); - return null; - } + int lay = ((Number) objectStructure.getFieldValue("lay")).intValue(); if ((lay & blenderContext.getBlenderKey().getLayersToLoad()) == 0) { LOGGER.fine("The layer this object is located in is not included in loading."); return null; } - LOGGER.fine("Checking if the object has not been already loaded."); - Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedDataType.FEATURE); - if (loadedResult != null) { - return loadedResult; - } - blenderContext.pushParent(objectStructure); String name = objectStructure.getName(); LOGGER.log(Level.FINE, "Loading obejct: {0}", name); - + int restrictflag = ((Number) objectStructure.getFieldValue("restrictflag")).intValue(); boolean visible = (restrictflag & 0x01) != 0; @@ -171,7 +169,7 @@ public class ObjectHelper extends AbstractBlenderHelper { Pointer pMesh = (Pointer) objectStructure.getFieldValue("data"); List meshesArray = pMesh.fetchData(); TemporalMesh temporalMesh = meshHelper.toTemporalMesh(meshesArray.get(0), blenderContext); - if(temporalMesh != null) { + if (temporalMesh != null) { result.attachChild(temporalMesh); } break; @@ -183,7 +181,7 @@ public class ObjectHelper extends AbstractBlenderHelper { CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class); Structure curveData = pCurve.fetchData().get(0); TemporalMesh curvesTemporalMesh = curvesHelper.toCurve(curveData, blenderContext); - if(curvesTemporalMesh != null) { + if (curvesTemporalMesh != null) { result.attachChild(curvesTemporalMesh); } } @@ -193,10 +191,12 @@ public class ObjectHelper extends AbstractBlenderHelper { if (pLamp.isNotNull()) { LightHelper lightHelper = blenderContext.getHelper(LightHelper.class); List lampsArray = pLamp.fetchData(); - result = lightHelper.toLight(lampsArray.get(0), blenderContext); - if (result == null) { + Light light = lightHelper.toLight(lampsArray.get(0), blenderContext); + if (light == null) { // probably some light type is not supported, just create a node so that we can maintain child-parent relationship for nodes result = new Node(name); + } else { + result = new LightNode(name, light); } } break; @@ -205,19 +205,25 @@ public class ObjectHelper extends AbstractBlenderHelper { if (pCamera.isNotNull()) { CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class); List camerasArray = pCamera.fetchData(); - result = cameraHelper.toCamera(camerasArray.get(0), blenderContext); + Camera camera = cameraHelper.toCamera(camerasArray.get(0), blenderContext); + if (camera == null) { + // just create a node so that we can maintain child-parent relationship for nodes + result = new Node(name); + } else { + result = new CameraNode(name, camera); + } } break; default: LOGGER.log(Level.WARNING, "Unsupported object type: {0}", type); } - + if (result != null) { LOGGER.fine("Storing loaded feature in blender context and applying markers (those will be removed before the final result is released)."); Long oma = objectStructure.getOldMemoryAddress(); blenderContext.addLoadedFeatures(oma, LoadedDataType.STRUCTURE, objectStructure); blenderContext.addLoadedFeatures(oma, LoadedDataType.FEATURE, result); - + blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress()); if (objectType == ObjectType.ARMATURE) { blenderContext.addMarker(ARMATURE_NODE_MARKER, result, Boolean.TRUE); @@ -235,13 +241,13 @@ public class ObjectHelper extends AbstractBlenderHelper { for (Modifier modifier : modifiers) { modifier.apply(result, blenderContext); } - + if (result.getChildren() != null && result.getChildren().size() > 0) { - if(result.getChildren().size() == 1 && result.getChild(0) instanceof TemporalMesh) { + if (result.getChildren().size() == 1 && result.getChild(0) instanceof TemporalMesh) { LOGGER.fine("Converting temporal mesh into jme geometries."); - ((TemporalMesh)result.getChild(0)).toGeometries(); + ((TemporalMesh) result.getChild(0)).toGeometries(); } - + LOGGER.fine("Applying proper scale to the geometries."); for (Spatial child : result.getChildren()) { if (child instanceof Geometry) { diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java index ad35a2ebc..f6ed89ce7 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java @@ -121,7 +121,7 @@ public class TextureHelper extends AbstractBlenderHelper { * data. The returned texture has the name set to the value of its blender * type. * - * @param tex + * @param textureStructure * texture structure filled with data * @param blenderContext * the blender context @@ -130,23 +130,29 @@ public class TextureHelper extends AbstractBlenderHelper { * this exception is thrown when the blend file structure is * somehow invalid or corrupted */ - public Texture getTexture(Structure tex, Structure mTex, BlenderContext blenderContext) throws BlenderFileException { - Texture result = (Texture) blenderContext.getLoadedFeature(tex.getOldMemoryAddress(), LoadedDataType.FEATURE); + public Texture getTexture(Structure textureStructure, Structure mTex, BlenderContext blenderContext) throws BlenderFileException { + Texture result = (Texture) blenderContext.getLoadedFeature(textureStructure.getOldMemoryAddress(), LoadedDataType.FEATURE); if (result != null) { return result; } - int type = ((Number) tex.getFieldValue("type")).intValue(); - int imaflag = ((Number) tex.getFieldValue("imaflag")).intValue(); + + if ("ID".equals(textureStructure.getType())) { + LOGGER.fine("Loading texture from external blend file."); + return (Texture) this.loadLibrary(textureStructure); + } + + int type = ((Number) textureStructure.getFieldValue("type")).intValue(); + int imaflag = ((Number) textureStructure.getFieldValue("imaflag")).intValue(); switch (type) { case TEX_IMAGE:// (it is first because probably this will be most commonly used) - Pointer pImage = (Pointer) tex.getFieldValue("ima"); + Pointer pImage = (Pointer) textureStructure.getFieldValue("ima"); if (pImage.isNotNull()) { Structure image = pImage.fetchData().get(0); - Texture loadedTexture = this.loadTexture(image, imaflag, blenderContext); + Texture loadedTexture = this.loadImageAsTexture(image, imaflag, blenderContext); if (loadedTexture != null) { result = loadedTexture; - this.applyColorbandAndColorFactors(tex, result.getImage(), blenderContext); + this.applyColorbandAndColorFactors(textureStructure, result.getImage(), blenderContext); } } break; @@ -160,7 +166,7 @@ public class TextureHelper extends AbstractBlenderHelper { case TEX_MUSGRAVE: case TEX_VORONOI: case TEX_DISTNOISE: - result = new GeneratedTexture(tex, mTex, textureGeneratorFactory.createTextureGenerator(type), blenderContext); + result = new GeneratedTexture(textureStructure, mTex, textureGeneratorFactory.createTextureGenerator(type), blenderContext); break; case TEX_NONE:// No texture, do nothing break; @@ -169,13 +175,13 @@ public class TextureHelper extends AbstractBlenderHelper { case TEX_PLUGIN: case TEX_ENVMAP: case TEX_OCEAN: - LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, tex.getName() }); + LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, textureStructure.getName() }); break; default: - throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + tex.getName()); + throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + textureStructure.getName()); } if (result != null) { - result.setName(tex.getName()); + result.setName(textureStructure.getName()); result.setWrap(WrapMode.Repeat); // decide if the mipmaps will be generated @@ -195,14 +201,14 @@ public class TextureHelper extends AbstractBlenderHelper { } if (type != TEX_IMAGE) {// only generated textures should have this key - result.setKey(new GeneratedTextureKey(tex.getName())); + result.setKey(new GeneratedTextureKey(textureStructure.getName())); } if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), tex.getOldMemoryAddress() }); + LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), textureStructure.getOldMemoryAddress() }); } - blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), LoadedDataType.STRUCTURE, tex); - blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), LoadedDataType.FEATURE, result); + blenderContext.addLoadedFeatures(textureStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, textureStructure); + blenderContext.addLoadedFeatures(textureStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result); } return result; } @@ -222,30 +228,40 @@ public class TextureHelper extends AbstractBlenderHelper { * this exception is thrown when the blend file structure is * somehow invalid or corrupted */ - protected Texture loadTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException { + public Texture loadImageAsTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException { LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", imageStructure.getOldMemoryAddress()); Texture result = null; Image im = (Image) blenderContext.getLoadedFeature(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE); if (im == null) { - String texturePath = imageStructure.getFieldValue("name").toString(); - Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile"); - if (pPackedFile.isNull()) { - LOGGER.log(Level.FINE, "Reading texture from file: {0}", texturePath); - result = this.loadImageFromFile(texturePath, imaflag, blenderContext); + if ("ID".equals(imageStructure.getType())) { + LOGGER.fine("Loading texture from external blend file."); + result = (Texture) this.loadLibrary(imageStructure); } else { - LOGGER.fine("Packed texture. Reading directly from the blend file!"); - Structure packedFile = pPackedFile.fetchData().get(0); - Pointer pData = (Pointer) packedFile.getFieldValue("data"); - FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pData.getOldMemoryAddress()); - blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition()); - ImageLoader imageLoader = new ImageLoader(); - - // Should the texture be flipped? It works for sinbad .. - result = new Texture2D(imageLoader.loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true)); + String texturePath = imageStructure.getFieldValue("name").toString(); + Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile"); + if (pPackedFile.isNull()) { + LOGGER.log(Level.FINE, "Reading texture from file: {0}", texturePath); + result = this.loadImageFromFile(texturePath, imaflag, blenderContext); + } else { + LOGGER.fine("Packed texture. Reading directly from the blend file!"); + Structure packedFile = pPackedFile.fetchData().get(0); + Pointer pData = (Pointer) packedFile.getFieldValue("data"); + FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pData.getOldMemoryAddress()); + blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition()); + + // Should the texture be flipped? It works for sinbad .. + result = new Texture2D(new ImageLoader().loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true)); + } } } else { result = new Texture2D(im); } + + if (result != null) {// render result is not being loaded + blenderContext.addLoadedFeatures(imageStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, imageStructure); + blenderContext.addLoadedFeatures(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result.getImage()); + result.setName(imageStructure.getName()); + } return result; } @@ -524,6 +540,18 @@ public class TextureHelper extends AbstractBlenderHelper { return result; } + /** + * Reads the texture data from the given material or sky structure. + * @param structure + * the structure of material or sky + * @param diffuseColorArray + * array of diffuse colors + * @param skyTexture + * indicates it we're going to read sky texture or not + * @return a list of combined textures + * @throws BlenderFileException + * an exception is thrown when problems with reading the blend file occur + */ @SuppressWarnings("unchecked") public List readTextureData(Structure structure, float[] diffuseColorArray, boolean skyTexture) throws BlenderFileException { DynamicArray mtexsArray = (DynamicArray) structure.getFieldValue("mtex"); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java index 5c3504771..8ff55baac 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java @@ -1,10 +1,12 @@ package com.jme3.scene.plugins.blender.textures.blending; +import java.util.logging.Logger; + +import jme3tools.converters.MipMapGenerator; + import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.materials.MaterialHelper; import com.jme3.texture.Image; -import java.util.logging.Logger; -import jme3tools.converters.MipMapGenerator; /** * An abstract class that contains the basic methods used by the classes that @@ -103,12 +105,12 @@ import jme3tools.converters.MipMapGenerator; public void copyBlendingData(TextureBlender textureBlender) { if (textureBlender instanceof AbstractTextureBlender) { - this.flag = ((AbstractTextureBlender) textureBlender).flag; - this.negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture; - this.blendType = ((AbstractTextureBlender) textureBlender).blendType; - this.materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone(); - this.color = ((AbstractTextureBlender) textureBlender).color.clone(); - this.blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor; + flag = ((AbstractTextureBlender) textureBlender).flag; + negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture; + blendType = ((AbstractTextureBlender) textureBlender).blendType; + materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone(); + color = ((AbstractTextureBlender) textureBlender).color.clone(); + blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor; } else { LOGGER.warning("Cannot copy blending data from other types than " + this.getClass()); } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java index c682ed1e5..f487e240d 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java @@ -31,13 +31,13 @@ */ package com.jme3.scene.plugins.blender.textures.blending; +import java.util.logging.Level; +import java.util.logging.Logger; + import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; -import java.util.logging.Level; -import java.util.logging.Logger; - /** * This class creates the texture blending class depending on the texture type. * @@ -66,7 +66,6 @@ public class TextureBlenderFactory { * the texture format * @return texture blending class */ - @SuppressWarnings("deprecation") public static TextureBlender createTextureBlender(Format format, int flag, boolean negate, int blendType, float[] materialColor, float[] color, float colfac) { switch (format) { case Luminance8: diff --git a/jme3-core/src/main/java/com/jme3/scene/UserData.java b/jme3-core/src/main/java/com/jme3/scene/UserData.java index 4b5d70404..3047618fc 100644 --- a/jme3-core/src/main/java/com/jme3/scene/UserData.java +++ b/jme3-core/src/main/java/com/jme3/scene/UserData.java @@ -33,6 +33,12 @@ package com.jme3.scene; import com.jme3.export.*; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * UserData is used to contain user data objects @@ -48,28 +54,40 @@ public final class UserData implements Savable { * shape generation should ignore them. */ public static final String JME_PHYSICSIGNORE = "JmePhysicsIgnore"; - + /** * For geometries using shared mesh, this will specify the shared * mesh reference. */ - public static final String JME_SHAREDMESH = "JmeSharedMesh"; - - protected byte type; - protected Object value; + public static final String JME_SHAREDMESH = "JmeSharedMesh"; + + private static final int TYPE_INTEGER = 0; + private static final int TYPE_FLOAT = 1; + private static final int TYPE_BOOLEAN = 2; + private static final int TYPE_STRING = 3; + private static final int TYPE_LONG = 4; + private static final int TYPE_SAVABLE = 5; + private static final int TYPE_LIST = 6; + private static final int TYPE_MAP = 7; + private static final int TYPE_ARRAY = 8; + + protected byte type; + protected Object value; public UserData() { } /** - * Creates a new UserData with the given + * Creates a new UserData with the given * type and value. * - * @param type Type of data, should be between 0 and 4. - * @param value Value of the data + * @param type + * Type of data, should be between 0 and 8. + * @param value + * Value of the data */ public UserData(byte type, Object value) { - assert type >= 0 && type <= 4; + assert type >= 0 && type <= 8; this.type = type; this.value = value; } @@ -85,15 +103,23 @@ public final class UserData implements Savable { public static byte getObjectType(Object type) { if (type instanceof Integer) { - return 0; + return TYPE_INTEGER; } else if (type instanceof Float) { - return 1; + return TYPE_FLOAT; } else if (type instanceof Boolean) { - return 2; + return TYPE_BOOLEAN; } else if (type instanceof String) { - return 3; + return TYPE_STRING; } else if (type instanceof Long) { - return 4; + return TYPE_LONG; + } else if (type instanceof Savable) { + return TYPE_SAVABLE; + } else if (type instanceof List) { + return TYPE_LIST; + } else if (type instanceof Map) { + return TYPE_MAP; + } else if (type instanceof Object[]) { + return TYPE_ARRAY; } else { throw new IllegalArgumentException("Unsupported type: " + type.getClass().getName()); } @@ -101,56 +127,195 @@ public final class UserData implements Savable { public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); - oc.write(type, "type", (byte)0); + oc.write(type, "type", (byte) 0); switch (type) { - case 0: + case TYPE_INTEGER: int i = (Integer) value; oc.write(i, "intVal", 0); break; - case 1: + case TYPE_FLOAT: float f = (Float) value; oc.write(f, "floatVal", 0f); break; - case 2: + case TYPE_BOOLEAN: boolean b = (Boolean) value; oc.write(b, "boolVal", false); break; - case 3: + case TYPE_STRING: String s = (String) value; oc.write(s, "strVal", null); break; - case 4: + case TYPE_LONG: Long l = (Long) value; oc.write(l, "longVal", 0l); break; + case TYPE_SAVABLE: + Savable sav = (Savable) value; + oc.write(sav, "savableVal", null); + break; + case TYPE_LIST: + this.writeList(oc, (List) value, "0"); + break; + case TYPE_MAP: + Map map = (Map) value; + this.writeList(oc, map.keySet(), "0"); + this.writeList(oc, map.values(), "1"); + break; + case TYPE_ARRAY: + this.writeList(oc, Arrays.asList((Object[]) value), "0"); + break; default: - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Unsupported value type: " + value.getClass()); } } public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); type = ic.readByte("type", (byte) 0); - switch (type) { - case 0: + case TYPE_INTEGER: value = ic.readInt("intVal", 0); break; - case 1: + case TYPE_FLOAT: value = ic.readFloat("floatVal", 0f); break; - case 2: + case TYPE_BOOLEAN: value = ic.readBoolean("boolVal", false); break; - case 3: + case TYPE_STRING: value = ic.readString("strVal", null); break; - case 4: + case TYPE_LONG: value = ic.readLong("longVal", 0l); break; + case TYPE_SAVABLE: + value = ic.readSavable("savableVal", null); + break; + case TYPE_LIST: + value = this.readList(ic, "0"); + break; + case TYPE_MAP: + Map map = new HashMap(); + List keys = this.readList(ic, "0"); + List values = this.readList(ic, "1"); + for (int i = 0; i < keys.size(); ++i) { + map.put(keys.get(i), values.get(i)); + } + value = map; + break; + case TYPE_ARRAY: + value = this.readList(ic, "0").toArray(); + break; default: - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Unknown type of stored data: " + type); + } + } + + /** + * The method stores a list in the capsule. + * @param oc + * output capsule + * @param list + * the list to be stored + * @throws IOException + */ + private void writeList(OutputCapsule oc, Collection list, String listName) throws IOException { + if (list != null) { + oc.write(list.size(), listName + "size", 0); + int counter = 0; + for (Object o : list) { + // t is for 'type'; v is for 'value' + if (o instanceof Integer) { + oc.write(TYPE_INTEGER, listName + "t" + counter, 0); + oc.write((Integer) o, listName + "v" + counter, 0); + } else if (o instanceof Float) { + oc.write(TYPE_FLOAT, listName + "t" + counter, 0); + oc.write((Float) o, listName + "v" + counter, 0f); + } else if (o instanceof Boolean) { + oc.write(TYPE_BOOLEAN, listName + "t" + counter, 0); + oc.write((Boolean) o, listName + "v" + counter, false); + } else if (o instanceof String || o == null) {// treat null's like Strings just to store them and keep the List like the user intended + oc.write(TYPE_STRING, listName + "t" + counter, 0); + oc.write((String) o, listName + "v" + counter, null); + } else if (o instanceof Long) { + oc.write(TYPE_LONG, listName + "t" + counter, 0); + oc.write((Long) o, listName + "v" + counter, 0L); + } else if (o instanceof Savable) { + oc.write(TYPE_SAVABLE, listName + "t" + counter, 0); + oc.write((Savable) o, listName + "v" + counter, null); + } else if(o instanceof Object[]) { + oc.write(TYPE_ARRAY, listName + "t" + counter, 0); + this.writeList(oc, Arrays.asList((Object[]) o), listName + "v" + counter); + } else if(o instanceof List) { + oc.write(TYPE_LIST, listName + "t" + counter, 0); + this.writeList(oc, (List) o, listName + "v" + counter); + } else if(o instanceof Map) { + oc.write(TYPE_MAP, listName + "t" + counter, 0); + Map map = (Map) o; + this.writeList(oc, map.keySet(), listName + "v(keys)" + counter); + this.writeList(oc, map.values(), listName + "v(vals)" + counter); + } else { + throw new UnsupportedOperationException("Unsupported type stored in the list: " + o.getClass()); + } + + ++counter; + } + } else { + oc.write(0, "size", 0); + } + } + + /** + * The method loads a list from the given input capsule. + * @param ic + * the input capsule + * @return loaded list (an empty list in case its size is 0) + * @throws IOException + */ + private List readList(InputCapsule ic, String listName) throws IOException { + int size = ic.readInt(listName + "size", 0); + List list = new ArrayList(size); + for (int i = 0; i < size; ++i) { + int type = ic.readInt(listName + "t" + i, 0); + switch (type) { + case TYPE_INTEGER: + list.add(ic.readInt(listName + "v" + i, 0)); + break; + case TYPE_FLOAT: + list.add(ic.readFloat(listName + "v" + i, 0)); + break; + case TYPE_BOOLEAN: + list.add(ic.readBoolean(listName + "v" + i, false)); + break; + case TYPE_STRING: + list.add(ic.readString(listName + "v" + i, null)); + break; + case TYPE_LONG: + list.add(ic.readLong(listName + "v" + i, 0L)); + break; + case TYPE_SAVABLE: + list.add(ic.readSavable(listName + "v" + i, null)); + break; + case TYPE_ARRAY: + list.add(this.readList(ic, listName + "v" + i).toArray()); + break; + case TYPE_LIST: + list.add(this.readList(ic, listName + "v" + i)); + break; + case TYPE_MAP: + Map map = new HashMap(); + List keys = this.readList(ic, listName + "v(keys)" + i); + List values = this.readList(ic, listName + "v(vals)" + i); + for (int j = 0; j < keys.size(); ++j) { + map.put(keys.get(j), values.get(j)); + } + list.add(map); + break; + default: + throw new UnsupportedOperationException("Unknown type of stored data in a list: " + type); + } } + return list; } }